General purpose Language Server that can spawn formatters and linters.
This is a fork efm-langserver that will maintain and develop separately. It is a cleaned up and simplified version of the original. It supports the original configuration but only for formatting and linting. No code actions, completions, hover etc.
Notable changes from the original:
- no config.yaml, settings need to be passed via DidChangeConfiguration
- only linting and formatting (for now)
- better diffs handling for formatting (no more "format twice to remove an extra newline")
- external, maintained diff library used for that
- support for errorformat's end line and end column
- added tests (always in progress)
- refactored, cleaned and more maintainable code (always in progress)
- fixed and applied sane defaults for options like
LintAfterOpen,LintOnSaveetc. - removed explicit support for
LintWorkspace(linters that lint the whole workspace and do not need filename)- it may be implemented back in the future if needed, but I've no usage of such linters, and a quick search through
creativenull/efmls-configs-nvimshowed no usage of this property - tracked in #11
- it may be implemented back in the future if needed, but I've no usage of such linters, and a quick search through
go install github.com/konradmalik/efm-langserver@latestor via nix. Flake provided in this repo.
nix buildUsage of efm-langserver:
-h Show help
-logfile string
File to save logs into. If provided stderr won't be used anymore.
-loglevel int
Set the log level. Max is 5, min is 0. (default 1)
-q Run quiet
-v Print the version
Configuration can be done through a DidChangeConfiguration
notification from the client.
DidChangeConfiguration can be called any time and will overwrite only provided
properties (note though that per language configuration will be overwritten as a whole array).
DidChangeConfiguration cannot set LogFile.
efm-langserver does not include formatters/linters for any language. You must install these manually,
e.g.
- lua: LuaFormatter
- python: yapf isort
- vint for Vim script
- markdownlint-cli for Markdown
- etc...
Because the configuration can be updated on the fly, capabilities might change throughout the lifetime of the server. To enable support for capabilities that will be available later, set them in the InitializeParams
Example
{
"initializationOptions": {
"documentFormatting": true,
"documentRangeFormatting": true
}
}{
"settings": {
"rootMarkers": [".git/"],
"languages": {
"lua": {
"formatCommand": "lua-format -i",
"formatStdin": true
}
}
}
}type Config struct {
Version int `json:"version,omitempty"`
LogLevel int `json:"logLevel,omitempty"`
Languages *map[string][]Language `json:"languages,omitempty"`
RootMarkers *[]string `json:"rootMarkers,omitempty"`
LintDebounce time.Duration `json:"lintDebounce,omitempty"`
FormatDebounce time.Duration `json:"formatDebounce,omitempty"`
}
type Language struct {
Prefix string `json:"prefix,omitempty"`
LintFormats []string `json:"lintFormats,omitempty"`
LintStdin bool `json:"lintStdin,omitempty"`
// warning: this will be subtracted from the line reported by the linter
LintOffset int `json:"lintOffset,omitempty"`
// warning: this will be added to the column reported by the linter
LintOffsetColumns int `json:"lintOffsetColumns,omitempty"`
LintCommand string `json:"lintCommand,omitempty"`
LintIgnoreExitCode bool `json:"lintIgnoreExitCode,omitempty"`
LintCategoryMap map[string]string `json:"lintCategoryMap,omitempty"`
LintSource string `json:"lintSource,omitempty"`
LintSeverity DiagnosticSeverity `json:"lintSeverity,omitempty"`
// defaults to true if not provided as a sanity default
LintAfterOpen *bool `json:"lintAfterOpen,omitempty"`
// defaults to true if not provided as a sanity default
LintOnChange *bool `json:"lintOnChange,omitempty"`
// defaults to true if not provided as a sanity default
LintOnSave *bool `json:"lintOnSave,omitempty"`
FormatCommand string `json:"formatCommand,omitempty"`
FormatCanRange bool `json:"formatCanRange,omitempty"`
FormatStdin bool `json:"formatStdin,omitempty"`
Env []string `json:"env,omitempty"`
RootMarkers []string `json:"rootMarkers,omitempty"`
RequireMarker bool `json:"requireMarker,omitempty"`
}Also note that there's a wildcard for language name =. So if you want to define some config entry for all languages,
you can use = as a key.
Note that while it's always possible to use multiple formatters, only formatters with formatStdin will work in such
cases. If a formatter formats file on disk, multiple formatters will simply always use the original text. When
formatStdin is true, then formatters can use previous formatter output as input which does what we want.
The first formatter can always be non-stdin.
Configuration for neovim builtin LSP with nvim-lspconfig
Neovim's built-in LSP client sends DidChangeConfiguration.
init.lua example (settings follows schema.md):
require "lspconfig".efm.setup {
init_options = {documentFormatting = true},
settings = {
rootMarkers = {".git/"},
languages = {
lua = {
{formatCommand = "lua-format -i", formatStdin = true}
}
}
}
}You can get premade tool definitions from creativenull/efmls-configs-nvim:
lua = {
require('efmls-configs.linters.luacheck'),
require('efmls-configs.formatters.stylua'),
}If you define your own, make sure to define as a table of tables:
lua = {
{formatCommand = "lua-format -i", formatStdin = true}
}
-- and for multiple formatters, add to the table
lua = {
{formatCommand = "lua-format -i", formatStdin = true},
{formatCommand = "lua-pretty -i"}
}Configuration for coc.nvim
coc-settings.json
Configuration for VSCode
Example settings.json (change to fit your local installs):
{
"glspc.languageId": "lua",
"glspc.serverCommand": "/Users/me/.local/share/nvim/mason/bin/efm-langserver",
"glspc.pathPrepend": "/Users/me/.local/share/rtx/installs/python/3.11.4/bin:/Users/me/.local/share/rtx/installs/node/20.3.1/bin"
}Configuration for Helix
~/.config/helix/languages.toml
[language-server.efm]
command = "efm-langserver"
[[language]]
name = "typescript"
language-servers = [
{ name = "efm", only-features = [ "diagnostics", "format" ] },
{ name = "typescript-language-server", except-features = [ "format" ] }
]Configuration for SublimeText LSP
Open Preferences: LSP Settings command from the Command Palette (Ctrl+Shift+P)
{
"clients": {
"efm-langserver": {
"enabled": true,
"command": ["efm-langserver"],
"selector": "source.c | source.php | source.python" // see https://www.sublimetext.com/docs/3/selectors.html
}
}
}
Configuration for vim-lsp
augroup LspEFM
au!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'efm-langserver',
\ 'cmd': {server_info->['efm-langserver']},
\ 'allowlist': ['vim', 'eruby', 'markdown', 'yaml'],
\ })
augroup ENDvim-lsp-settings provide installer for efm-langserver.
Configuration for Eglot (Emacs)
Add to eglot-server-programs with major mode you want.
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(markdown-mode . ("efm-langserver"))))MIT
- Yasuhiro Matsumoto (a.k.a. mattn) before 2025-04-29 (original efm-langserver author)
- Konrad Malik after 2025-04-29 (author and maintainer of this fork)
