πΈ A Neovim plugin for Salesforce development
- Features
- Intro video
- Prerequisites
- Installation
- Configuration
- Keys
- Full doc
- List/retrieve metadata
- Apex test
- Display target org and code coverage
- Integrated term
- Apex jump
- Contributions
In a nutshell, All features are supplied via the list of Sf.*
functionalities in
init.lua. You can surf the code comments as the manual or :h sf.nvim
, which is auto-generated from those comments.
All the features are categorized as:
- π₯ Apex/Lwc/Aura: push, retrieve, create
- π» Integrated term
- π File diff: local v.s. org
- π€© Target-org icon
- π Org Metadata browsing
- π€ Quick apex test run
- β¨ Test report and code coverage info
- π¦ Enhanced jump-to-definition (Apex)
In addition to the features, user commands and default hotkeys are also supplied, see Keys.
π‘ Plugin comes with a health check feature. Run :check sf
to auto-check prerequiste statistics.
- π Salesforce CLI
- π’ Nvim v0.10 or newer (why must > 0.10?)
- π¦ Salesforce relevant parsers (i.e. "apex", "soql", "sosl", and "sflog") in Nvim-treesitter. Install them like in my settings
- π (Optional) fzf-lua plugin for executing
:SF md list
andSFListMdTypeToRetrieve
(Why not telescope.nvim? Because its UI is slow) - π (Optional) universal ctags is used to enhance Apex jump
Install using Lazy.nvim by adding the following configuration to your setup:
return {
'xixiaofinland/sf.nvim',
dependencies = {
'nvim-treesitter/nvim-treesitter',
'ibhagwan/fzf-lua', -- no need if you don't use listing metadata feature
},
config = function()
require('sf').setup() -- Important to call setup() to initialize the plugin!
end
}
π¨ Notice:
The hotkeys are disabled by default!
The user commands are ONLY enabled when your current path (:h cwd
) or the current opened file
is inside a sf project folder (i.e. has .forceignore
or sfdx-project.json
in the root path).
Run :lua require'sf.util'.get_sf_root()
to verify if the current opened file
resides in sf project folder. When no error is printed it means the file is in a sf project folder.
Custom configuration can be passed into setup()
Below are the default
settings:
require('sf').setup({
-- Unless you want to customize, no need to copy-paste any of these
-- They are applied automatically
-- This plugin has many default hotkey mappings supplied
-- This flag enable/disable these hotkeys defined
-- It's highly recommended to set this to `false` and define your own key mappings
-- Set to `true` if you don't mind any potential key mapping conflicts with your own
enable_hotkeys = false,
-- this setting takes effect only when You have "enable_hotkeys = true"(i.e. use default supplied hotkeys).
-- In the default hotkeys, some hotkeys are on "project level" thus always enabled. Examples: "set default org", "fetch org info".
-- Other hotkeys are enabled when only metadata filetypes are loaded in the current buffer. Example: "push/retrieve current metadata file"
-- This list defines what metadata filetypes have the "other hotkeys" enabled.
-- For example, if you want to push/retrieve css files, it needs to be added into this list.
hotkeys_in_filetypes = {
"apex", "sosl", "soql", "javascript", "html"
},
-- When Nvim is initiated, the sf org list is automatically fetched and target_org is set (if available) by `:SF org fetchList`
-- You can set it to `false` and have a manual control
fetch_org_list_at_nvim_start = true,
-- Define what metadata to be listed in `list_md_to_retrieve()` (<leader>ml)
-- Salesforce has numerous metadata types. We narrow down the scope of `list_md_to_retrieve()`.
types_to_retrieve = {
"ApexClass",
"ApexTrigger",
"StaticResource",
"LightningComponentBundle"
},
-- Configuration for the integrated terminal
term_config = {
blend = 10, -- background transparency: 0 is fully opaque; 100 is fully transparent
dimensions = {
height = 0.4, -- proportional of the editor height. 0.4 means 40%.
width = 0.8, -- proportional of the editor width. 0.8 means 80%.
x = 0.5, -- starting position of width. Details in `get_dimension()` in raw_term.lua source code.
y = 0.9, -- starting position of height. Details in `get_dimension()` in raw_term.lua source code.
},
},
-- the sf project metadata folder, update this in case you diverged from the default sf folder structure
default_dir = '/force-app/main/default/',
-- the folder this plugin uses to store intermediate data. It's under the sf project root directory.
plugin_folder_name = '/sf_cache/',
-- after the test running with code coverage completes, display uncovered line sign automatically.
-- you can set it to `false`, then manually run toggle_sign command.
auto_display_code_sign = true,
-- code coverage sign icon colors
code_sign_highlight = {
covered = { fg = "#b7f071" }, -- set `fg = ""` to disable this sign icon
uncovered = { fg = "#f07178" }, -- set `fg = ""` to disable this sign icon
},
})
This plugin supplies both user commands (:h user-commands
) and default hotkeys(:h mapping
).
Note! Default hotkeys are disabled by default in the config setting.
User commands are categories into two level subcommands (:SF sub_cmd1 sub_cmd2
) to leverage the tab
suggestion.
For example,
- type
:SF<space>
and hittab
to list available categories(i.e.sub_cmd1
) as screenshot 1. - Then select
test<space>
and hittab
again to list the availablesub_cmd2
options intest
category as screenshot 2 - Finally choose
:SF test allTestsInThisFile
and hit<enter>
to run all Apex tests in the current file.
This plugin comes with many default hotkeys (all defined in this file), which may conflict and overwrite your existing hotkeys. Thus these hotkeys are disabled by default in the config setting.
It is also recommended to disable them and define the ones as you wish.
The toggling is in the configuration by setting the enable_hotkeys
option.
For example,
return {
'xixiaofinland/sf.nvim',
dependencies = {
'nvim-treesitter/nvim-treesitter',
"ibhagwan/fzf-lua",
},
config = function()
require('sf').setup()
-- all your key definitions put below
local Sf = require('sf')
vim.keymap.set('n', '<leader>ss', Sf.set_target_org, { desc = "set local" })
vim.keymap.set('n', '<leader>sS', Sf.set_global_target_org, { desc = "set global" })
end
}
In case you decide to go with the default hotkeys:
Default key | function name | Explain |
---|---|---|
<leader>ss |
set_target_org | set target_org |
<leader>sf |
fetch_org_list | fetch/refresh orgs info |
<leader><leader> |
toggle_term | terminal toggle |
<leader>sp |
save_and_push | push current file |
<leader>sr |
retrieve | retrieve current file |
<leader>ta |
run_all_tests_in_this_file | run all Apex tests in current file |
<leader>tt |
run_current_test | test this under cursor |
<leader>tr |
repeat_last_tests | repeat the last test |
<leader>to |
open_test_select | open a buffer to select tests |
<leader>ct |
create_ctags | create ctags file |
<leader>sq |
run_highlighted_soql | Deault key is only enabled in visual model. Highlight selected text will be run as SOQL in the term |
\s |
toggle_sign | Show/hide line coverage sign icon |
]v |
uncovered_jump_forward | jump to next test uncovered hunk |
[v |
`uncovered_jump_backward | jump to last test uncovered hunk |
All keys are listed in :h sf.nvim
or help.txt file.
Example:
- If you have which-key or a similar plugin installed, pressing
<leader>s
will hint to you what keys are enabled as shown in the screenshot below. Remember that default hotkeys are **disabled by default.
The integrated term (i.e. SFTerm) is a general purpose one, you can pass any shell command into
run()
method to execute it terminal. For instance, require('sf').run('ls -la')
, then define it
as your key:
vim.keymap.set('n', '<leader>sk', require('sf').run('ls -la'), { noremap = true, silent = true, desc = 'run ls -la in the terminal' })
Checking all features via :h sf.nvim
or help.txt file.
Sometimes you don't know what metadata the target org contains, and you want to list them and fetch specific ones. Steps:
- Retrieve the metadata data by running the user command
:SF md pull
. - Run
:SF md list
(orrequire('sf').list_md_to_retrieve()
) to show the list in a pop-up (requires the fzf-lua plugin) and select one to download to local.
Sometimes you want to fetch all files of a certain metadata type (Apex class, LWC, Aura, etc.). You can list them and fetch all of a specific type. Steps:
- Retrieve the metadata types by running the user command
:SF mdtype pull
. - Run
:SF mdtype list
(orrequire('sf').list_md_type_to_retrieve()
) to show the list in a pop-up (requires the fzf-lua plugin) and select one to download all metadata of this type to local.
There are two categories of test actions.
β¨ Use-case 1: without code coverage info
You can,
- Run all tests in the current file by
<leader>ta
- Run the test under the cursor by
<leader>tt
- Select tests from the current file by
<leader>to
These commands quickly run and verify the pass/fail result.
π©οΈ Use-case 2: with code coverage info
Use the same hotkeys but capitalize the last letter:
<leader>tA
<leader>tT
<leader>tO
These test results contains code coverage information.
After running these commands successfully, the test result is saved locally, and the covered/uncovered lines are illustrated as sign-icons next to the line number (screenshot below).
𧩠Screenshot
Test finishes in CrudTest.cls
with UNCOVERED LINES: 9,10,11,13,14
and the line coverage in Crud.cls
is indicated with green/red icon
signs.
π Note.
- The line coverage icon shows automatically if the
auto_display_code_sign
setting is set totrue
(default). - Toggle sign icon on/off with the
\s
hotkey (orrequire'sf'.toggle_sign()
).
ποΈ Jump to next uncovered hunk
- Use
]v
and[v
to jump to the next/previous uncovered hunk.
Upon starting Nvim, Sf.nvim executes :SF org fetchList
to fetch and save
authenticated org names. Display the target_org in your status line to
facilitate command execution against the target org.
If you don't have a default target_org, then this value is empty. You can use <leader>ss
to set it.
Example configuration using lualine.nvim with target_org(xixiao100
):
sections = {
lualine_c = { 'filename', {
"require'sf'.get_target_org()",
} },
require('sf').covered_percent()
has the current Apex file code coverage information.
You can
display it as you want. For example, I display it (92
) in my status line next to target_org (devhub
), configured in lualine.nvim here
The integrated terminal is designed to
- accept input from hotkeys and user commands, such as "retrieve current metadata file"
<leader>sr
- be a read-only buffer. It's, by design, not allowed to manually type commands
- be disposable. The output text of the previous command is removed when a new command is invoked
- be auto-prompt, in case the terminal is hidden at the moment the command execution completes. This is handy when you have a long-running command.
You can pass any shell command into run()
method to execute it in the integrated
terminal. For instance, require('sf').run('ls -la')
.
Salesforce's Apex LSP (apex-jorje-lsp.jar) offers a jump-to-definition feature, but it's not perfect. You may encounter cases where it doesn't function correctly in certain codebases. To address this, the LSP jump-to-definition is enhanced by ctags.
If you don't yet know what ctags is, it's wise to google "ctags in vim" to prepare a bit more.
Ctags is ideal in this scenario because:
- It is natively supported by Nvim/Vim, although you need to install
ctags
yourself - The default
<C-]>
key in Nvim will first attempt to jump with LSP and fall back to ctags if LSP fails
There are several versions of ctags, this repo uses universal ctags. So you need to install it to use this feature.
:SF create ctags
or require('sf').create_ctags()
generates the ctags file in the project root.
Using the <C-]>
key for jump-to-definition will automatically use both LSP and ctags in order.
:SF create ctagsAndList
or require('sf').create_and_list_ctags()
will update ctags and list the tags symbols in fzf-lua
plugin.
Please create an issue to discuss your PR before submitting it. This ensures that the PR will be merged.
The PR should be done against main
branch.
Commit messages should follow Conventional Commits, or the
PR would fail in the status check.
For example,
- If it's a new feature, the commit message could be "feat: add a new user command".
- If it's a bug fix, the commit message could be "fix: eliminate the error".
The help.txt
file is auto-generated from the comments with the ---
suffix
before each function in init.lua. The plugin
uses mini.doc
to automatically generate help.txt
from these ---
suffixed comments. Therefore,
add your doc content in init.lua
without touching help.txt
is sufficient.
Thanks to the following people for contributing to this project:
MIT.