README
¶
flint-ls
General purpose Language Server that can spawn formatters and linters.

Description
This is a fork of efm-langserver that will maintain and develop separately. It is a cleaned up and simplified version of the original. It supports a subset of original configuration - 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)
- all formatters must support stdin, non-stdin formatters won't work. Option
formatStdinwas removed. - fixed behavior of
LintIgnoreExitCode- when true, output is parsed for errors even if exit code is 0. Previously each lint command that resulted in exit code 0 was considered a problem, but exit code 0 is ok in situations when there's no lint issues. - 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
- added Lsp Progress notifications, sent only to clients that advertise
window.workDoneProgress - removed
RootMarkersfrom root settings. They can only be provided per language now. The use of this was questionable.
Sections
Installation
go install github.com/konradmalik/flint-ls@latest
or via nix. Flake provided in this repo.
nix build
Usage
Usage of flint-ls:
-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 3 (debug), min is 0 (error). Higher number logs less. Set <0 for no logs. (default 2)
-v Print the version
Configuration
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.
flint-ls 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...
InitializeParams
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
}
}
Example for DidChangeConfiguration notification
{
"settings": {
"languages": {
"lua": {
"formatCommand": "lua-format -i"
}
}
}
}
Full config
type Config struct {
Languages map[string][]Language `json:"languages,omitempty"`
// how long a document must be idle before it is linted, in nanoseconds.
// defaults to 100ms; debouncing is per document
LintDebounce time.Duration `json:"lintDebounce,omitempty"`
}
type Language struct {
Env []string `json:"env,omitempty"`
RootMarkers []string `json:"rootMarkers,omitempty"`
RequireMarker bool `json:"requireMarker,omitempty"`
// prefix for lint message
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"`
// whether formatCommand can format a range; only such formatters run for a
// range formatting request
FormatCanRange bool `json:"formatCanRange,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.
Placeholders
lintCommand and formatCommand may use the following placeholders:
| placeholder | value |
|---|---|
${INPUT} |
path of the document being processed, with / separators |
${FILENAME} |
the same path, with the platform's separators |
${ROOT} |
working directory the command runs in |
${FILEEXT} |
extension of the document, without the leading dot |
Commands run through a shell. A path placeholder that you leave bare is quoted for you, so paths containing
spaces or other characters the shell would act on still reach the tool as a single argument. A placeholder you
quote yourself is substituted as-is, since the quoting it needs is already there — both styles work, and
configs written for efm-langserver need no changes:
"lintCommand": "eslint -f visualstudio --stdin --stdin-filename ${INPUT}", // quoted for you
"lintCommand": "cspell lint --no-color \"${INPUT}\"", // your quotes are kept
${FILEEXT} is never quoted, as it is meant to be substituted mid-word (foo.${FILEEXT}).
Formatting
All formatters must support stdin. When a formatter uses non-stdin in replaces file contents on disk which leads to confusing and unpredictable results.
Client Setup
Configuration for neovim builtin LSP with nvim-lspconfig
Neovim's built-in LSP client sends DidChangeConfiguration.
require "lspconfig".flint_ls.setup {
init_options = {documentFormatting = true},
settings = {
languages = {
lua = {
{formatCommand = "lua-format -i"}
}
}
}
}
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"}
}
-- and for multiple formatters, add to the table
lua = {
{formatCommand = "lua-format -i"},
{formatCommand = "lua-pretty -i"}
}
Configuration for coc.nvim
coc-settings.json
// languageserver
"languageserver": {
"flint-ls": {
"command": "flint-ls",
"args": [],
"filetypes": ["vim", "eruby", "markdown", "yaml"]
}
},
Configuration for VSCode
Example settings.json (change to fit your local installs):
{
"glspc.languageId": "lua",
"glspc.serverCommand": "/Users/me/.local/share/nvim/mason/bin/flint-ls",
"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.flint-ls]
command = "flint-ls"
[[language]]
name = "typescript"
language-servers = [
{ name = "flint-ls", 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": {
"flint-ls": {
"enabled": true,
"command": ["flint-ls"],
"selector": "source.c | source.php | source.python" // see https://www.sublimetext.com/docs/3/selectors.html
}
}
}
Configuration for vim-lsp
augroup LspFlint
au!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'flint-ls',
\ 'cmd': {server_info->['flint-ls']},
\ 'allowlist': ['vim', 'eruby', 'markdown', 'yaml'],
\ })
augroup END
vim-lsp-settings provide installer for flint-ls.
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 . ("flint-ls"))))
License
MIT
Authors
- Yasuhiro Matsumoto (a.k.a. mattn) before 2025-04-29 (original flint-ls author)
- Konrad Malik after 2025-04-29 (author and maintainer of this fork)
Documentation
¶
There is no documentation for this package.