aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Anders <greg@gpanders.com>2021-09-06 20:21:18 -0600
committerGregory Anders <greg@gpanders.com>2021-09-15 14:09:47 -0600
commita5bbb932f9094098bd656d3f6be3c58344576709 (patch)
tree80f35362c9e94853e9e8898416120a3ede623362
parent6188926e00081ae4b1a33d5fd12692a6749a2144 (diff)
downloadrneovim-a5bbb932f9094098bd656d3f6be3c58344576709.tar.gz
rneovim-a5bbb932f9094098bd656d3f6be3c58344576709.tar.bz2
rneovim-a5bbb932f9094098bd656d3f6be3c58344576709.zip
refactor: move vim.lsp.diagnostic to vim.diagnostic
This generalizes diagnostic handling outside of just the scope of LSP. LSP clients are now a specific case of a diagnostic producer, but the diagnostic subsystem is decoupled from the LSP subsystem (or will be, eventually). More discussion at [1]. [1]: https://github.com/neovim/neovim/pull/15585
-rw-r--r--runtime/doc/deprecated.txt44
-rw-r--r--runtime/doc/diagnostic.txt483
-rw-r--r--runtime/doc/lsp.txt594
-rw-r--r--runtime/lua/vim/diagnostic.lua1178
-rw-r--r--runtime/lua/vim/lsp.lua3
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua1469
-rw-r--r--runtime/lua/vim/lsp/handlers.lua10
-rw-r--r--runtime/lua/vim/lsp/util.lua51
-rw-r--r--runtime/plugin/diagnostic.vim48
-rwxr-xr-xscripts/gen_vimdoc.py17
-rw-r--r--src/nvim/lua/vim.lua3
-rw-r--r--test/README.md2
-rw-r--r--test/functional/lua/diagnostic_spec.lua810
-rw-r--r--test/functional/plugin/lsp/diagnostic_spec.lua115
-rw-r--r--test/functional/plugin/lsp_spec.lua25
15 files changed, 3054 insertions, 1798 deletions
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 861aed4884..d1f26c8c81 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -55,6 +55,50 @@ Functions ~
without stopping the job. Use chanclose(id) to close
any socket.
+LSP Diagnostics ~
+
+For each of the functions below, use the corresponding function in
+|vim.diagnostic| instead (unless otherwise noted). For example, use
+|vim.diagnostic.get()| instead of |vim.lsp.diagnostic.get()|.
+
+*vim.lsp.diagnostic.clear()* Use |vim.diagnostic.hide()| instead.
+*vim.lsp.diagnostic.disable()*
+*vim.lsp.diagnostic.display()* Use |vim.diagnostic.show()| instead.
+*vim.lsp.diagnostic.enable()*
+*vim.lsp.diagnostic.get()*
+*vim.lsp.diagnostic.get_all()* Use |vim.diagnostic.get()| instead.
+*vim.lsp.diagnostic.get_count()* Use |vim.diagnostic.get()| instead.
+*vim.lsp.diagnostic.get_line_diagnostics()*
+ Use |vim.diagnostic.get()| instead.
+*vim.lsp.diagnostic.get_next()*
+*vim.lsp.diagnostic.get_next_pos()*
+*vim.lsp.diagnostic.get_prev()*
+*vim.lsp.diagnostic.get_prev_pos()*
+*vim.lsp.diagnostic.get_virtual_text_chunks_for_line()*
+ Use |vim.diagnostic.get_virt_text_chunks()| instead.
+*vim.lsp.diagnostic.goto_next()*
+*vim.lsp.diagnostic.goto_prev()*
+*vim.lsp.diagnostic.redraw()* Use |vim.diagnostic.show()| instead.
+*vim.lsp.diagnostic.reset()*
+*vim.lsp.diagnostic.save()* Use |vim.diagnostic.set()| instead.
+*vim.lsp.diagnostic.set_loclist()* Use |vim.diagnostic.setloclist()| instead.
+*vim.lsp.diagnostic.set_qflist()* Use |vim.diagnostic.setqflist()| instead.
+*vim.lsp.diagnostic.show_line_diagnostics()*
+*vim.lsp.diagnostic.show_position_diagnostics()*
+
+The following are deprecated without replacement. These functions are moved
+internally and are no longer exposed as part of the API. Instead, use
+|vim.diagnostic.config()| and |vim.diagnostic.show()|.
+
+*vim.lsp.diagnostic.set_signs()*
+*vim.lsp.diagnostic.set_underline()*
+*vim.lsp.diagnostic.set_virtual_text()*
+
+LSP Utility Functions ~
+
+*vim.lsp.util.set_qflist()* Use |setqflist()| instead.
+*vim.lsp.util.set_loclist()* Use |setloclist()| instead.
+
Lua ~
*vim.register_keystroke_callback()* Use |vim.on_key()| instead.
diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
new file mode 100644
index 0000000000..f4975b187f
--- /dev/null
+++ b/runtime/doc/diagnostic.txt
@@ -0,0 +1,483 @@
+*diagnostic.txt* Diagnostics
+
+
+ NVIM REFERENCE MANUAL
+
+
+Diagnostic framework *vim.diagnostic*
+
+Nvim provides a framework for displaying errors or warnings from external
+tools, otherwise known as "diagnostics". These diagnostics can come from a
+variety of sources, such as linters or LSP servers. The diagnostic framework
+is an extension to existing error handling functionality such as the
+|quickfix| list.
+
+ Type |gO| to see the table of contents.
+
+==============================================================================
+QUICKSTART *diagnostic-quickstart*
+
+Anything that reports diagnostics is referred to below as a "diagnostic
+producer". Diagnostic producers need only follow a few simple steps to
+report diagnostics:
+
+1. Create a namespace |nvim_create_namespace()|. Note that the namespace must
+ have a name. Anonymous namespaces WILL NOT WORK.
+2. (Optional) Configure options for the diagnostic namespace
+ |vim.diagnostic.config()|.
+3. Generate diagnostics.
+4. Set the diagnostics for the buffer |vim.diagnostic.set()|.
+5. Repeat from step 3.
+
+Generally speaking, the API is split between functions meant to be used by
+diagnostic producers and those meant for diagnostic consumers (i.e. end users
+who want to read and view the diagnostics for a buffer). The APIs for
+producers require a {namespace} as their first argument, while those for
+consumers generally do not require a namespace (though often one may be
+optionally supplied). A good rule of thumb is that if a method is meant to
+modify the diagnostics for a buffer (e.g. |vim.diagnostic.set()|) then it
+requires a namespace.
+
+ *diagnostic-structure*
+A diagnostic is a Lua table with the following keys:
+
+ lnum: The starting line of the diagnostic
+ end_lnum: The final line of the diagnostic
+ col: The starting column of the diagnostic
+ end_col: The final column of the diagnostic
+ severity: The severity of the diagnostic |vim.diagnostic.severity|
+ message: The diagnostic text
+
+Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based
+rows and columns). |api-indexing|
+
+ *vim.diagnostic.severity* *diagnostic-severity*
+The "severity" key in a diagnostic is one of the values defined in
+`vim.diagnostic.severity`:
+
+ vim.diagnostic.severity.ERROR
+ vim.diagnostic.severity.WARN
+ vim.diagnostic.severity.INFO
+ vim.diagnostic.severity.HINT
+
+Functions that take a severity as an optional parameter (e.g.
+|vim.diagnostic.get()|) accept one of two forms:
+
+1. A single |vim.diagnostic.severity| value: >
+
+ vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN })
+
+2. A table with a "min" or "max" key (or both): >
+
+ vim.diagnostic.get(0, { severity = {min=vim.diagnostic.severity.WARN})
+
+The latter form allows users to specify a range of severities.
+
+==============================================================================
+HIGHLIGHTS *diagnostic-highlights*
+
+All highlights defined for diagnostics begin with `Diagnostic` followed by
+the type of highlight (e.g., `Sign`, `Underline`, etc.) and the severity (e.g.
+`Error`, `Warn`, etc.)
+
+Sign, underline and virtual text highlights (by default) are linked to their
+corresponding default highlight.
+
+For example, the default highlighting for |hl-DiagnosticSignError| is linked
+to |hl-DiagnosticError|. To change the default (and therefore the linked
+highlights), use the |:highlight| command: >
+
+ highlight DiagnosticError guifg="BrightRed"
+<
+ *hl-DiagnosticError*
+DiagnosticError
+ Used as the base highlight group.
+ Other Diagnostic highlights link to this by default (except Underline)
+
+ *hl-DiagnosticWarn*
+DiagnosticWarn
+ Used as the base highlight group.
+ Other Diagnostic highlights link to this by default (except Underline)
+
+ *hl-DiagnosticInfo*
+DiagnosticInfo
+ Used as the base highlight group.
+ Other Diagnostic highlights link to this by default (except Underline)
+
+ *hl-DiagnosticHint*
+DiagnosticHint
+ Used as the base highlight group.
+ Other Diagnostic highlights link to this by default (except Underline)
+
+ *hl-DiagnosticVirtualTextError*
+DiagnosticVirtualTextError
+ Used for "Error" diagnostic virtual text.
+
+ *hl-DiagnosticVirtualTextWarn*
+DiagnosticVirtualTextWarn
+ Used for "Warn" diagnostic virtual text.
+
+ *hl-DiagnosticVirtualTextInfo*
+DiagnosticVirtualTextInfo
+ Used for "Info" diagnostic virtual text.
+
+ *hl-DiagnosticVirtualTextHint*
+DiagnosticVirtualTextHint
+ Used for "Hint" diagnostic virtual text.
+
+ *hl-DiagnosticUnderlineError*
+DiagnosticUnderlineError
+ Used to underline "Error" diagnostics.
+
+ *hl-DiagnosticUnderlineWarn*
+DiagnosticUnderlineWarn
+ Used to underline "Warn" diagnostics.
+
+ *hl-DiagnosticUnderlineInfo*
+DiagnosticUnderlineInfo
+ Used to underline "Info" diagnostics.
+
+ *hl-DiagnosticUnderlineHint*
+DiagnosticUnderlineHint
+ Used to underline "Hint" diagnostics.
+
+ *hl-DiagnosticFloatingError*
+DiagnosticFloatingError
+ Used to color "Error" diagnostic messages in diagnostics float.
+ See |vim.diagnostic.show_line_diagnostics()|
+
+ *hl-DiagnosticFloatingWarn*
+DiagnosticFloatingWarn
+ Used to color "Warn" diagnostic messages in diagnostics float.
+
+ *hl-DiagnosticFloatingInfo*
+DiagnosticFloatingInfo
+ Used to color "Info" diagnostic messages in diagnostics float.
+
+ *hl-DiagnosticFloatingHint*
+DiagnosticFloatingHint
+ Used to color "Hint" diagnostic messages in diagnostics float.
+
+ *hl-DiagnosticSignError*
+DiagnosticSignError
+ Used for "Error" signs in sign column.
+
+ *hl-DiagnosticSignWarn*
+DiagnosticSignWarn
+ Used for "Warn" signs in sign column.
+
+ *hl-DiagnosticSignInfo*
+DiagnosticSignInfo
+ Used for "Info" signs in sign column.
+
+ *hl-DiagnosticSignHint*
+DiagnosticSignHint
+ Used for "Hint" signs in sign column.
+
+==============================================================================
+SIGNS *diagnostic-signs*
+
+Signs are defined for each diagnostic severity. The default text for each sign
+is the first letter of the severity name (for example, "E" for ERROR). Signs
+can be customized using the following: >
+
+ sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl=
+ sign define DiagnosticSignWarning text=W texthl=DiagnosticSignWarning linehl= numhl=
+ sign define DiagnosticSignInformation text=I texthl=DiagnosticSignInformation linehl= numhl=
+ sign define DiagnosticSignHint text=H texthl=DiagnosticSignHint linehl= numhl=
+
+==============================================================================
+EVENTS *diagnostic-events*
+
+ *DiagnosticsChanged*
+DiagnosticsChanged After diagnostics have changed.
+
+Example: >
+ autocmd User DiagnosticsChanged lua vim.diagnostic.setqflist({open = false })
+<
+
+==============================================================================
+Lua module: vim.diagnostic *diagnostic-api*
+
+config({opts}, {namespace}) *vim.diagnostic.config()*
+ Configure diagnostic options globally or for a specific
+ diagnostic namespace.
+
+ Note:
+ Each of the configuration options below accepts one of the
+ following:
+ • `false` : Disable this feature
+ • `true` : Enable this feature, use default settings.
+ • `table` : Enable this feature with overrides.
+ • `function` : Function with signature (namespace, bufnr)
+ that returns any of the above.
+
+ Parameters: ~
+ {opts} table Configuration table with the following
+ keys:
+ • underline: (default true) Use underline for
+ diagnostics
+ • virtual_text: (default true) Use virtual
+ text for diagnostics
+ • signs: (default true) Use signs for
+ diagnostics
+ • update_in_insert: (default false) Update
+ diagnostics in Insert mode (if false,
+ diagnostics are updated on InsertLeave)
+ • severity_sort: (default false) Sort
+ diagnostics by severity. This affects the
+ order in which signs and virtual text are
+ displayed
+ {namespace} number|nil Update the options for the given
+ namespace. When omitted, update the global
+ diagnostic options.
+
+disable({bufnr}, {namespace}) *vim.diagnostic.disable()*
+ Disable diagnostics in the given buffer.
+
+ Parameters: ~
+ {bufnr} number|nil Buffer number. Defaults to the
+ current buffer.
+ {namespace} number|nil Only disable diagnostics for the
+ given namespace.
+
+enable({bufnr}, {namespace}) *vim.diagnostic.enable()*
+ Enable diagnostics in the given buffer.
+
+ Parameters: ~
+ {bufnr} number|nil Buffer number. Defaults to the
+ current buffer.
+ {namespace} number|nil Only enable diagnostics for the
+ given namespace.
+
+get({bufnr}, {opts}) *vim.diagnostic.get()*
+ Get current diagnostics.
+
+ Parameters: ~
+ {bufnr} number|nil Buffer number to get diagnistics from.
+ Use 0 for current buffer or nil for all buffers.
+ {opts} table|nil A table with the following keys:
+ • namespace: (number) Limit diagnostics to the
+ given namespace.
+ • lnum: (number) Limit diagnostics to the given
+ line number.
+ • severity: See |diagnostic-severity|.
+
+ Return: ~
+ table A list of diagnostic items |diagnostic-structure|.
+
+get_next({opts}) *vim.diagnostic.get_next()*
+ Get the next diagnostic closest to the cursor position.
+
+ Parameters: ~
+ {opts} table See |vim.diagnostic.goto_next()|
+
+ Return: ~
+ table Next diagnostic
+
+get_next_pos({opts}) *vim.diagnostic.get_next_pos()*
+ Return the position of the next diagnostic in the current
+ buffer.
+
+ Parameters: ~
+ {opts} table See |vim.diagnostic.goto_next()|
+
+ Return: ~
+ table Next diagnostic position as a (row, col) tuple.
+
+get_prev({opts}) *vim.diagnostic.get_prev()*
+ Get the previous diagnostic closest to the cursor position.
+
+ Parameters: ~
+ {opts} table See |vim.diagnostic.goto_next()|
+
+ Return: ~
+ table Previous diagnostic
+
+get_prev_pos({opts}) *vim.diagnostic.get_prev_pos()*
+ Return the position of the previous diagnostic in the current
+ buffer.
+
+ Parameters: ~
+ {opts} table See |vim.diagnostic.goto_next()|
+
+ Return: ~
+ table Previous diagnostic position as a (row, col) tuple.
+
+ *vim.diagnostic.get_virt_text_chunks()*
+get_virt_text_chunks({line_diags}, {opts})
+ Get virtual text chunks to display using
+ |nvim_buf_set_extmark()|.
+
+ Parameters: ~
+ {line_diags} table The diagnostics associated with the
+ line.
+ {opts} table|nil Configuration table with the
+ following keys:
+ • prefix: (string) Prefix to display before
+ virtual text on line.
+ • spacing: (number) Number of spaces to
+ insert before virtual text.
+
+ Return: ~
+ an array of [text, hl_group] arrays. This can be passed
+ directly to the {virt_text} option of
+ |nvim_buf_set_extmark()|.
+
+goto_next({opts}) *vim.diagnostic.goto_next()*
+ Move to the next diagnostic.
+
+ Parameters: ~
+ {opts} table|nil Configuration table with the following
+ keys:
+ • namespace: (number) Only consider diagnostics
+ from the given namespace.
+ • cursor_position: (cursor position) Cursor
+ position as a (row, col) tuple. See
+ |nvim_win_get_cursor()|. Defaults to the current
+ cursor position.
+ • wrap: (boolean, default true) Whether to loop
+ around file or not. Similar to 'wrapscan'.
+ • severity: See |diagnostic-severity|.
+ • enable_popup: (boolean, default true) Call
+ |vim.diagnostic.show_line_diagnostics()| on
+ jump.
+ • popup_opts: (table) Table to pass as {opts}
+ parameter to
+ |vim.diagnostic.show_line_diagnostics()|
+ • win_id: (number, default 0) Window ID
+
+goto_prev({opts}) *vim.diagnostic.goto_prev()*
+ Move to the previous diagnostic in the current buffer.
+
+ Parameters: ~
+ {opts} table See |vim.diagnostic.goto_next()|
+
+hide({namespace}, {bufnr}) *vim.diagnostic.hide()*
+ Hide currently displayed diagnostics.
+
+ This only clears the decorations displayed in the buffer.
+ Diagnostics can be redisplayed with |vim.diagnostic.show()|.
+ To completely remove diagnostics, use
+ |vim.diagnostic.reset()|.
+
+ To hide diagnostics and prevent them from re-displaying, use
+ |vim.diagnostic.disable()|.
+
+ Parameters: ~
+ {namespace} number The diagnostic namespace
+ {bufnr} number|nil Buffer number. Defaults to the
+ current buffer.
+
+reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
+ Remove all diagnostics from the given namespace.
+
+ Unlike |vim.diagnostic.hide()|, this function removes all
+ saved diagnostics. They cannot be redisplayed using
+ |vim.diagnostic.show()|. To simply remove diagnostic
+ decorations in a way that they can be re-displayed, use
+ |vim.diagnostic.hide()|.
+
+ Parameters: ~
+ {namespace} number
+ {bufnr} number|nil Remove diagnostics for the given
+ buffer. When omitted, diagnostics are removed
+ for all buffers.
+
+set({namespace}, {bufnr}, {diagnostics}, {opts}) *vim.diagnostic.set()*
+ Set diagnostics for the given namespace and buffer.
+
+ Parameters: ~
+ {namespace} number The diagnostic namespace
+ {bufnr} number Buffer number
+ {diagnostics} table A list of diagnostic items
+ |diagnostic-structure|
+ {opts} table|nil Display options to pass to
+ |vim.diagnostic.show()|
+
+setloclist({opts}) *vim.diagnostic.setloclist()*
+ Add buffer diagnostics to the location list.
+
+ Parameters: ~
+ {opts} table|nil Configuration table with the following
+ keys:
+ • namespace: (number) Only add diagnostics from
+ the given namespace.
+ • winnr: (number, default 0) Window number to set
+ location list for.
+ • open: (boolean, default true) Open the location
+ list after setting.
+ • title: (string) Title of the location list.
+ Defaults to "Diagnostics".
+ • severity: See |diagnostic-severity|.
+
+setqflist({opts}) *vim.diagnostic.setqflist()*
+ Add all diagnostics to the quickfix list.
+
+ Parameters: ~
+ {opts} table|nil Configuration table with the following
+ keys:
+ • namespace: (number) Only add diagnostics from
+ the given namespace.
+ • open: (boolean, default true) Open quickfix list
+ after setting.
+ • title: (string) Title of quickfix list. Defaults
+ to "Diagnostics".
+ • severity: See |diagnostic-severity|.
+
+ *vim.diagnostic.show()*
+show({namespace}, {bufnr}, {diagnostics}, {opts})
+ Display diagnostics for the given namespace and buffer.
+
+ Parameters: ~
+ {namespace} number Diagnostic namespace
+ {bufnr} number|nil Buffer number. Defaults to the
+ current buffer.
+ {diagnostics} table|nil The diagnostics to display. When
+ omitted, use the saved diagnostics for the
+ given namespace and buffer. This can be
+ used to display a list of diagnostics
+ without saving them or to display only a
+ subset of diagnostics.
+ {opts} table|nil Display options. See
+ |vim.diagnostic.config()|.
+
+ *vim.diagnostic.show_line_diagnostics()*
+show_line_diagnostics({opts}, {bufnr}, {lnum})
+ Open a floating window with the diagnostics from the given
+ line.
+
+ Parameters: ~
+ {opts} table Configuration table. See
+ |vim.diagnostic.show_position_diagnostics()|.
+ {bufnr} number|nil Buffer number. Defaults to the current
+ buffer.
+ {lnum} number|nil Line number. Defaults to line number
+ of cursor.
+
+ Return: ~
+ A ({popup_bufnr}, {win_id}) tuple
+
+ *vim.diagnostic.show_position_diagnostics()*
+show_position_diagnostics({opts}, {bufnr}, {position})
+ Open a floating window with the diagnostics at the given
+ position.
+
+ Parameters: ~
+ {opts} table|nil Configuration table with the same
+ keys as |vim.lsp.util.open_floatin_preview()|
+ in addition to the following:
+ • namespace: (number) Limit diagnostics to the
+ given namespace
+ • severity: See |diagnostic-severity|.
+ • show_header: (boolean, default true) Show
+ "Diagnostics:" header
+ {bufnr} number|nil Buffer number. Defaults to the
+ current buffer.
+ {position} table|nil The (0,0)-indexed position. Defaults
+ to the current cursor position.
+
+ Return: ~
+ A ({popup_bufnr}, {win_id}) tuple
+
+ vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index e76e224596..48d65a22b6 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -424,121 +424,6 @@ LspReferenceRead used for highlighting "read" references
LspReferenceWrite used for highlighting "write" references
- *lsp-highlight-diagnostics*
-All highlights defined for diagnostics begin with `LspDiagnostics` followed by
-the type of highlight (e.g., `Sign`, `Underline`, etc.) and then the Severity
-of the highlight (e.g. `Error`, `Warning`, etc.)
-
-Sign, underline and virtual text highlights (by default) are linked to their
-corresponding LspDiagnosticsDefault highlight.
-
-For example, the default highlighting for |hl-LspDiagnosticsSignError| is
-linked to |hl-LspDiagnosticsDefaultError|. To change the default (and
-therefore the linked highlights), use the |:highlight| command: >
-
- highlight LspDiagnosticsDefaultError guifg="BrightRed"
-<
-
- *hl-LspDiagnosticsDefaultError*
-LspDiagnosticsDefaultError
- Used as the base highlight group.
- Other LspDiagnostic highlights link to this by default (except Underline)
-
- *hl-LspDiagnosticsDefaultWarning*
-LspDiagnosticsDefaultWarning
- Used as the base highlight group.
- Other LspDiagnostic highlights link to this by default (except Underline)
-
- *hl-LspDiagnosticsDefaultInformation*
-LspDiagnosticsDefaultInformation
- Used as the base highlight group.
- Other LspDiagnostic highlights link to this by default (except Underline)
-
- *hl-LspDiagnosticsDefaultHint*
-LspDiagnosticsDefaultHint
- Used as the base highlight group.
- Other LspDiagnostic highlights link to this by default (except Underline)
-
- *hl-LspDiagnosticsVirtualTextError*
-LspDiagnosticsVirtualTextError
- Used for "Error" diagnostic virtual text.
- See |vim.lsp.diagnostic.set_virtual_text()|
-
- *hl-LspDiagnosticsVirtualTextWarning*
-LspDiagnosticsVirtualTextWarning
- Used for "Warning" diagnostic virtual text.
- See |vim.lsp.diagnostic.set_virtual_text()|
-
- *hl-LspDiagnosticsVirtualTextInformation*
-LspDiagnosticsVirtualTextInformation
- Used for "Information" diagnostic virtual text.
- See |vim.lsp.diagnostic.set_virtual_text()|
-
- *hl-LspDiagnosticsVirtualTextHint*
-LspDiagnosticsVirtualTextHint
- Used for "Hint" diagnostic virtual text.
- See |vim.lsp.diagnostic.set_virtual_text()|
-
- *hl-LspDiagnosticsUnderlineError*
-LspDiagnosticsUnderlineError
- Used to underline "Error" diagnostics.
- See |vim.lsp.diagnostic.set_underline()|
-
- *hl-LspDiagnosticsUnderlineWarning*
-LspDiagnosticsUnderlineWarning
- Used to underline "Warning" diagnostics.
- See |vim.lsp.diagnostic.set_underline()|
-
- *hl-LspDiagnosticsUnderlineInformation*
-LspDiagnosticsUnderlineInformation
- Used to underline "Information" diagnostics.
- See |vim.lsp.diagnostic.set_underline()|
-
- *hl-LspDiagnosticsUnderlineHint*
-LspDiagnosticsUnderlineHint
- Used to underline "Hint" diagnostics.
- See |vim.lsp.diagnostic.set_underline()|
-
- *hl-LspDiagnosticsFloatingError*
-LspDiagnosticsFloatingError
- Used to color "Error" diagnostic messages in diagnostics float.
- See |vim.lsp.diagnostic.show_line_diagnostics()|
-
- *hl-LspDiagnosticsFloatingWarning*
-LspDiagnosticsFloatingWarning
- Used to color "Warning" diagnostic messages in diagnostics float.
- See |vim.lsp.diagnostic.show_line_diagnostics()|
-
- *hl-LspDiagnosticsFloatingInformation*
-LspDiagnosticsFloatingInformation
- Used to color "Information" diagnostic messages in diagnostics float.
- See |vim.lsp.diagnostic.show_line_diagnostics()|
-
- *hl-LspDiagnosticsFloatingHint*
-LspDiagnosticsFloatingHint
- Used to color "Hint" diagnostic messages in diagnostics float.
- See |vim.lsp.diagnostic.show_line_diagnostics()|
-
- *hl-LspDiagnosticsSignError*
-LspDiagnosticsSignError
- Used for "Error" signs in sign column.
- See |vim.lsp.diagnostic.set_signs()|
-
- *hl-LspDiagnosticsSignWarning*
-LspDiagnosticsSignWarning
- Used for "Warning" signs in sign column.
- See |vim.lsp.diagnostic.set_signs()|
-
- *hl-LspDiagnosticsSignInformation*
-LspDiagnosticsSignInformation
- Used for "Information" signs in sign column.
- See |vim.lsp.diagnostic.set_signs()|
-
- *hl-LspDiagnosticsSignHint*
-LspDiagnosticsSignHint
- Used for "Hint" signs in sign column.
- See |vim.lsp.diagnostic.set_signs()|
-
*lsp-highlight-codelens*
Highlight groups related to |lsp-codelens| functionality.
@@ -561,13 +446,6 @@ LspSignatureActiveParameter
|vim.lsp.handlers.signature_help()|.
==============================================================================
-AUTOCOMMANDS *lsp-autocommands*
-
- *LspDiagnosticsChanged*
-LspDiagnosticsChanged After receiving publishDiagnostics server response
-
-
-==============================================================================
Lua module: vim.lsp *lsp-core*
buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
@@ -1207,220 +1085,20 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()*
==============================================================================
Lua module: vim.lsp.diagnostic *lsp-diagnostic*
- *vim.lsp.diagnostic.clear()*
-clear({bufnr}, {client_id}, {diagnostic_ns}, {sign_ns})
- Clears the currently displayed diagnostics
-
- Parameters: ~
- {bufnr} number The buffer number
- {client_id} number the client id
- {diagnostic_ns} number|nil Associated diagnostic
- namespace
- {sign_ns} number|nil Associated sign namespace
-
-disable({bufnr}, {client_id}) *vim.lsp.diagnostic.disable()*
- Disable diagnostics for the given buffer and client
-
- Parameters: ~
- {bufnr} (optional, number): Buffer handle, defaults
- to current
- {client_id} (optional, number): Disable diagnostics for
- the given client. The default is to disable
- diagnostics for all attached clients.
-
-enable({bufnr}, {client_id}) *vim.lsp.diagnostic.enable()*
- Enable diagnostics for the given buffer and client
-
- Parameters: ~
- {bufnr} (optional, number): Buffer handle, defaults
- to current
- {client_id} (optional, number): Enable diagnostics for
- the given client. The default is to enable
- diagnostics for all attached clients.
-
-get({bufnr}, {client_id}, {predicate}) *vim.lsp.diagnostic.get()*
- Return associated diagnostics for bufnr
-
- Parameters: ~
- {bufnr} number
- {client_id} number|nil If nil, then return all of the
- diagnostics. Else, return just the
- diagnostics associated with the client_id.
- {predicate} function|nil Optional function for filtering
- diagnostics
-
-get_all({client_id}) *vim.lsp.diagnostic.get_all()*
- Get all diagnostics for clients
-
- Parameters: ~
- {client_id} number Restrict included diagnostics to the
- client If nil, diagnostics of all clients are
- included.
-
- Return: ~
- table with diagnostics grouped by bufnr (bufnr:Diagnostic[])
-
- *vim.lsp.diagnostic.get_count()*
-get_count({bufnr}, {severity}, {client_id})
- Get the counts for a particular severity
-
- Useful for showing diagnostic counts in statusline. eg:
->
-
- function! LspStatus() abort
- let sl = ''
- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
- let sl.='%#MyStatuslineLSP#E:'
- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Error]])")}'
- let sl.='%#MyStatuslineLSP# W:'
- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Warning]])")}'
- else
- let sl.='%#MyStatuslineLSPErrors#off'
- endif
- return sl
- endfunction
- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
-<
-
- Parameters: ~
- {bufnr} number The buffer number
- {severity} DiagnosticSeverity
- {client_id} number the client id
-
- *vim.lsp.diagnostic.get_line_diagnostics()*
-get_line_diagnostics({bufnr}, {line_nr}, {opts}, {client_id})
- Get the diagnostics by line
-
- Parameters: ~
- {bufnr} number|nil The buffer number
- {line_nr} number|nil The line number
- {opts} table|nil Configuration keys
- • severity: (DiagnosticSeverity, default nil)
- • Only return diagnostics with this
- severity. Overrides severity_limit
-
- • severity_limit: (DiagnosticSeverity, default nil)
- • Limit severity of diagnostics found.
- E.g. "Warning" means { "Error",
- "Warning" } will be valid.
- {client_id|nil} number the client id
-
- Return: ~
- table Table with map of line number to list of
- diagnostics.
-
-get_next({opts}) *vim.lsp.diagnostic.get_next()*
- Get the next diagnostic closest to the cursor_position
-
- Parameters: ~
- {opts} table See |vim.lsp.diagnostic.goto_next()|
-
- Return: ~
- table Next diagnostic
-
-get_next_pos({opts}) *vim.lsp.diagnostic.get_next_pos()*
- Return the pos, {row, col}, for the next diagnostic in the
- current buffer.
-
- Parameters: ~
- {opts} table See |vim.lsp.diagnostic.goto_next()|
-
- Return: ~
- table Next diagnostic position
-
-get_prev({opts}) *vim.lsp.diagnostic.get_prev()*
- Get the previous diagnostic closest to the cursor_position
-
- Parameters: ~
- {opts} table See |vim.lsp.diagnostic.goto_next()|
-
- Return: ~
- table Previous diagnostic
-
-get_prev_pos({opts}) *vim.lsp.diagnostic.get_prev_pos()*
- Return the pos, {row, col}, for the prev diagnostic in the
- current buffer.
-
- Parameters: ~
- {opts} table See |vim.lsp.diagnostic.goto_next()|
-
- Return: ~
- table Previous diagnostic position
-
- *vim.lsp.diagnostic.get_virtual_text_chunks_for_line()*
-get_virtual_text_chunks_for_line({bufnr}, {line}, {line_diags}, {opts})
- Default function to get text chunks to display using
- |nvim_buf_set_extmark()|.
-
- Parameters: ~
- {bufnr} number The buffer to display the virtual
- text in
- {line} number The line number to display the
- virtual text on
- {line_diags} Diagnostic [] The diagnostics associated with the line
- {opts} table See {opts} from
- |vim.lsp.diagnostic.set_virtual_text()|
-
- Return: ~
- an array of [text, hl_group] arrays. This can be passed
- directly to the {virt_text} option of
- |nvim_buf_set_extmark()|.
-
-goto_next({opts}) *vim.lsp.diagnostic.goto_next()*
- Move to the next diagnostic
-
- Parameters: ~
- {opts} table|nil Configuration table. Keys:
- • {client_id}: (number)
- • If nil, will consider all clients attached to
- buffer.
-
- • {cursor_position}: (Position, default current
- position)
- • See |nvim_win_get_cursor()|
-
- • {wrap}: (boolean, default true)
- • Whether to loop around file or not. Similar to
- 'wrapscan'
-
- • {severity}: (DiagnosticSeverity)
- • Exclusive severity to consider. Overrides
- {severity_limit}
-
- • {severity_limit}: (DiagnosticSeverity)
- • Limit severity of diagnostics found. E.g.
- "Warning" means { "Error", "Warning" } will be
- valid.
-
- • {enable_popup}: (boolean, default true)
- • Call
- |vim.lsp.diagnostic.show_line_diagnostics()|
- on jump
-
- • {popup_opts}: (table)
- • Table to pass as {opts} parameter to
- |vim.lsp.diagnostic.show_line_diagnostics()|
-
- • {win_id}: (number, default 0)
- • Window ID
-
-goto_prev({opts}) *vim.lsp.diagnostic.goto_prev()*
- Move to the previous diagnostic
+get_namespace({client_id}) *vim.lsp.diagnostic.get_namespace()*
+ Get the diagnostic namespace associated with an LSP client
+ |vim.diagnostic|.
Parameters: ~
- {opts} table See |vim.lsp.diagnostic.goto_next()|
+ {client_id} number The id of the LSP client
*vim.lsp.diagnostic.on_publish_diagnostics()*
on_publish_diagnostics({_}, {result}, {ctx}, {config})
|lsp-handler| for the method "textDocument/publishDiagnostics"
- Note:
- Each of the configuration options accepts:
- • `false` : Disable this feature
- • `true` : Enable this feature, use default settings.
- • `table` : Enable this feature, use overrides.
- • `function`: Function with signature (bufnr, client_id) that
- returns any of the above.>
+ See |vim.diagnostic.config()| for configuration options.
+ Handler-specific configuration can be set using
+ |vim.lsp.with()|: >
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
@@ -1442,229 +1120,8 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config})
<
Parameters: ~
- {config} table Configuration table.
- • underline: (default=true)
- • Apply underlines to diagnostics.
- • See |vim.lsp.diagnostic.set_underline()|
-
- • virtual_text: (default=true)
- • Apply virtual text to line endings.
- • See |vim.lsp.diagnostic.set_virtual_text()|
-
- • signs: (default=true)
- • Apply signs for diagnostics.
- • See |vim.lsp.diagnostic.set_signs()|
-
- • update_in_insert: (default=false)
- • Update diagnostics in InsertMode or wait
- until InsertLeave
-
- • severity_sort: (default=false)
- • Sort diagnostics (and thus signs and virtual
- text)
-
-redraw({bufnr}, {client_id}) *vim.lsp.diagnostic.redraw()*
- Redraw diagnostics for the given buffer and client
-
- This calls the "textDocument/publishDiagnostics" handler
- manually using the cached diagnostics already received from
- the server. This can be useful for redrawing diagnostics after
- making changes in diagnostics configuration.
- |lsp-handler-configuration|
-
- Parameters: ~
- {bufnr} (optional, number): Buffer handle, defaults
- to current
- {client_id} (optional, number): Redraw diagnostics for
- the given client. The default is to redraw
- diagnostics for all attached clients.
-
-reset({client_id}, {buffer_client_map}) *vim.lsp.diagnostic.reset()*
- Clear diagnotics and diagnostic cache
-
- Handles saving diagnostics from multiple clients in the same
- buffer.
-
- Parameters: ~
- {client_id} number
- {buffer_client_map} table map of buffers to active
- clients
-
-save({diagnostics}, {bufnr}, {client_id}) *vim.lsp.diagnostic.save()*
- Save diagnostics to the current buffer.
-
- Handles saving diagnostics from multiple clients in the same
- buffer.
-
- Parameters: ~
- {diagnostics} Diagnostic []
- {bufnr} number
- {client_id} number
-
-set_loclist({opts}) *vim.lsp.diagnostic.set_loclist()*
- Sets the location list
-
- Parameters: ~
- {opts} table|nil Configuration table. Keys:
- • {open}: (boolean, default true)
- • Open loclist after set
-
- • {client_id}: (number)
- • If nil, will consider all clients attached to
- buffer.
-
- • {severity}: (DiagnosticSeverity)
- • Exclusive severity to consider. Overrides
- {severity_limit}
-
- • {severity_limit}: (DiagnosticSeverity)
- • Limit severity of diagnostics found. E.g.
- "Warning" means { "Error", "Warning" } will be
- valid.
-
- • {workspace}: (boolean, default false)
- • Set the list with workspace diagnostics
-
-set_qflist({opts}) *vim.lsp.diagnostic.set_qflist()*
- Sets the quickfix list
-
- Parameters: ~
- {opts} table|nil Configuration table. Keys:
- • {open}: (boolean, default true)
- • Open quickfix list after set
-
- • {client_id}: (number)
- • If nil, will consider all clients attached to
- buffer.
-
- • {severity}: (DiagnosticSeverity)
- • Exclusive severity to consider. Overrides
- {severity_limit}
-
- • {severity_limit}: (DiagnosticSeverity)
- • Limit severity of diagnostics found. E.g.
- "Warning" means { "Error", "Warning" } will be
- valid.
-
- • {workspace}: (boolean, default true)
- • Set the list with workspace diagnostics
-
- *vim.lsp.diagnostic.set_signs()*
-set_signs({diagnostics}, {bufnr}, {client_id}, {sign_ns}, {opts})
- Set signs for given diagnostics
-
- Sign characters can be customized with the following commands:
->
-
- sign define LspDiagnosticsSignError text=E texthl=LspDiagnosticsSignError linehl= numhl=
- sign define LspDiagnosticsSignWarning text=W texthl=LspDiagnosticsSignWarning linehl= numhl=
- sign define LspDiagnosticsSignInformation text=I texthl=LspDiagnosticsSignInformation linehl= numhl=
- sign define LspDiagnosticsSignHint text=H texthl=LspDiagnosticsSignHint linehl= numhl=
-<
-
- Parameters: ~
- {diagnostics} Diagnostic []
- {bufnr} number The buffer number
- {client_id} number the client id
- {sign_ns} number|nil
- {opts} table Configuration for signs. Keys:
- • priority: Set the priority of the signs.
- • severity_limit (DiagnosticSeverity):
- • Limit severity of diagnostics found.
- E.g. "Warning" means { "Error",
- "Warning" } will be valid.
-
- *vim.lsp.diagnostic.set_underline()*
-set_underline({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts})
- Set underline for given diagnostics
-
- Underline highlights can be customized by changing the
- following |:highlight| groups.
->
-
- LspDiagnosticsUnderlineError
- LspDiagnosticsUnderlineWarning
- LspDiagnosticsUnderlineInformation
- LspDiagnosticsUnderlineHint
-<
-
- Parameters: ~
- {diagnostics} Diagnostic []
- {bufnr} number: The buffer number
- {client_id} number: The client id
- {diagnostic_ns} number|nil: The namespace
- {opts} table: Configuration table:
- • severity_limit (DiagnosticSeverity):
- • Limit severity of diagnostics found.
- E.g. "Warning" means { "Error",
- "Warning" } will be valid.
-
- *vim.lsp.diagnostic.set_virtual_text()*
-set_virtual_text({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts})
- Set virtual text given diagnostics
-
- Virtual text highlights can be customized by changing the
- following |:highlight| groups.
->
-
- LspDiagnosticsVirtualTextError
- LspDiagnosticsVirtualTextWarning
- LspDiagnosticsVirtualTextInformation
- LspDiagnosticsVirtualTextHint
-<
-
- Parameters: ~
- {diagnostics} Diagnostic []
- {bufnr} number
- {client_id} number
- {diagnostic_ns} number
- {opts} table Options on how to display virtual
- text. Keys:
- • prefix (string): Prefix to display
- before virtual text on line
- • spacing (number): Number of spaces to
- insert before virtual text
- • severity_limit (DiagnosticSeverity):
- • Limit severity of diagnostics found.
- E.g. "Warning" means { "Error",
- "Warning" } will be valid.
-
- *vim.lsp.diagnostic.show_line_diagnostics()*
-show_line_diagnostics({opts}, {buf_nr}, {line_nr}, {client_id})
- Parameters: ~
- {opts} table Configuration table
- • all opts for
- |vim.lsp.diagnostic.get_line_diagnostics()|
- and |show_diagnostics()| can be used here
- {buf_nr} number|nil The buffer number
- {line_nr} number|nil The line number
- {client_id} number|nil the client id
-
- Return: ~
- table {popup_bufnr, win_id}
-
- *vim.lsp.diagnostic.show_position_diagnostics()*
-show_position_diagnostics({opts}, {buf_nr}, {position})
- Open a floating window with the diagnostics from {position}
-
- Parameters: ~
- {opts} table|nil Configuration keys
- • severity: (DiagnosticSeverity, default nil)
- • Only return diagnostics with this
- severity. Overrides severity_limit
-
- • severity_limit: (DiagnosticSeverity, default nil)
- • Limit severity of diagnostics found. E.g.
- "Warning" means { "Error", "Warning" }
- will be valid.
-
- • all opts for |show_diagnostics()| can be
- used here
- {buf_nr} number|nil The buffer number
- {position} table|nil The (0,0)-indexed position
-
- Return: ~
- table {popup_bufnr, win_id}
+ {config} table Configuration table (see
+ |vim.diagnostic.config()|).
==============================================================================
@@ -1903,21 +1360,6 @@ create_file({change}) *vim.lsp.util.create_file()*
delete_file({change}) *vim.lsp.util.delete_file()*
TODO: Documentation
- *vim.lsp.util.diagnostics_to_items()*
-diagnostics_to_items({diagnostics_by_bufnr}, {predicate})
- Convert diagnostics grouped by bufnr to a list of items for
- use in the quickfix or location list.
-
- Parameters: ~
- {diagnostics_by_bufnr} table bufnr -> Diagnostic []
- {predicate} an optional function to filter the
- diagnostics. If present, only
- diagnostic items matching will be
- included.
-
- Return: ~
- table (A list of items)
-
*vim.lsp.util.extract_completion_items()*
extract_completion_items({result})
Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null.
@@ -1982,6 +1424,9 @@ locations_to_items({locations}) *vim.lsp.util.locations_to_items()*
and in sorted order, for display in quickfix and location
lists.
+ The result can be passed to the {list} argument of
+ |setqflist()| or |setloclist()|.
+
Parameters: ~
{locations} (table) list of `Location` s or
`LocationLink` s
@@ -2166,21 +1611,6 @@ set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()*
Return: ~
(table) The modified {lines} object
-set_loclist({items}, {win_id}) *vim.lsp.util.set_loclist()*
- Fills target window's location list with given list of items.
- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
- Defaults to current window.
-
- Parameters: ~
- {items} (table) list of items
-
-set_qflist({items}) *vim.lsp.util.set_qflist()*
- Fills quickfix list with given list of items. Can be obtained
- with e.g. |vim.lsp.util.locations_to_items()|.
-
- Parameters: ~
- {items} (table) list of items
-
*vim.lsp.util.stylize_markdown()*
stylize_markdown({bufnr}, {contents}, {opts})
Converts markdown into syntax highlighted regions by stripping
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
new file mode 100644
index 0000000000..62809b5622
--- /dev/null
+++ b/runtime/lua/vim/diagnostic.lua
@@ -0,0 +1,1178 @@
+local M = {}
+
+M.severity = {
+ ERROR = 1,
+ WARN = 2,
+ INFO = 3,
+ HINT = 4,
+}
+
+vim.tbl_add_reverse_lookup(M.severity)
+
+local global_diagnostic_options = {
+ signs = true,
+ underline = true,
+ virtual_text = true,
+ update_in_insert = false,
+ severity_sort = false,
+}
+
+-- Local functions {{{
+
+---@private
+local function to_severity(severity)
+ return type(severity) == 'string' and M.severity[string.upper(severity)] or severity
+end
+
+---@private
+local function filter_by_severity(severity, diagnostics)
+ if not severity then
+ return diagnostics
+ end
+
+ if type(severity) ~= "table" then
+ severity = to_severity(severity)
+ return vim.tbl_filter(function(t) return t.severity == severity end, diagnostics)
+ end
+
+ local min_severity = to_severity(severity.min) or M.severity.HINT
+ local max_severity = to_severity(severity.max) or M.severity.ERROR
+
+ return vim.tbl_filter(function(t) return t.severity <= min_severity and t.severity >= max_severity end, diagnostics)
+end
+
+---@private
+local function resolve_optional_value(option, namespace, bufnr)
+ local enabled_val = {}
+
+ if not option then
+ return false
+ elseif option == true then
+ return enabled_val
+ elseif type(option) == 'function' then
+ local val = option(namespace, bufnr)
+ if val == true then
+ return enabled_val
+ else
+ return val
+ end
+ elseif type(option) == 'table' then
+ return option
+ else
+ error("Unexpected option type: " .. vim.inspect(option))
+ end
+end
+
+local all_namespaces = {}
+
+---@private
+local function get_namespace(ns)
+ if not all_namespaces[ns] then
+ local name
+ for k, v in pairs(vim.api.nvim_get_namespaces()) do
+ if ns == v then
+ name = k
+ break
+ end
+ end
+
+ if not name then
+ return vim.notify("namespace does not exist or is anonymous", vim.log.levels.ERROR)
+ end
+
+ all_namespaces[ns] = {
+ name = name,
+ sign_group = string.format("vim.diagnostic.%s", name),
+ opts = {}
+ }
+ end
+ return all_namespaces[ns]
+end
+
+---@private
+local function get_resolved_options(opts, namespace, bufnr)
+ local ns = get_namespace(namespace)
+ local resolved = vim.tbl_extend('keep', opts or {}, ns.opts, global_diagnostic_options)
+ for k in pairs(global_diagnostic_options) do
+ if resolved[k] ~= nil then
+ resolved[k] = resolve_optional_value(resolved[k], namespace, bufnr)
+ end
+ end
+ return resolved
+end
+
+-- Default diagnostic highlights
+local diagnostic_severities = {
+ [M.severity.ERROR] = { ctermfg = 1, guifg = "Red" };
+ [M.severity.WARN] = { ctermfg = 3, guifg = "Orange" };
+ [M.severity.INFO] = { ctermfg = 4, guifg = "LightBlue" };
+ [M.severity.HINT] = { ctermfg = 7, guifg = "LightGrey" };
+}
+
+-- Make a map from DiagnosticSeverity -> Highlight Name
+---@private
+local function make_highlight_map(base_name)
+ local result = {}
+ for k in pairs(diagnostic_severities) do
+ local name = M.severity[k]
+ name = name:sub(1, 1) .. name:sub(2):lower()
+ result[k] = "Diagnostic" .. base_name .. name
+ end
+
+ return result
+end
+
+local virtual_text_highlight_map = make_highlight_map("VirtualText")
+local underline_highlight_map = make_highlight_map("Underline")
+local floating_highlight_map = make_highlight_map("Floating")
+local sign_highlight_map = make_highlight_map("Sign")
+
+---@private
+local define_default_signs = (function()
+ local signs_defined = false
+ return function()
+ if signs_defined then
+ return
+ end
+
+ for severity, sign_hl_name in pairs(sign_highlight_map) do
+ local severity_name = M.severity[severity]
+ vim.fn.sign_define(sign_hl_name, {
+ text = (severity_name or 'U'):sub(1, 1),
+ texthl = sign_hl_name,
+ linehl = '',
+ numhl = '',
+ })
+ end
+
+ signs_defined = true
+ end
+end)()
+
+---@private
+local function get_bufnr(bufnr)
+ if not bufnr or bufnr == 0 then
+ return vim.api.nvim_get_current_buf()
+ end
+ return bufnr
+end
+
+-- Metatable that automatically creates an empty table when assigning to a missing key
+local bufnr_and_namespace_cacher_mt = {
+ __index = function(t, bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+
+ if rawget(t, bufnr) == nil then
+ rawset(t, bufnr, {})
+ end
+
+ return rawget(t, bufnr)
+ end,
+
+ __newindex = function(t, bufnr, v)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+
+ rawset(t, bufnr, v)
+ end,
+}
+
+local diagnostic_cleanup = setmetatable({}, bufnr_and_namespace_cacher_mt)
+local diagnostic_cache = setmetatable({}, bufnr_and_namespace_cacher_mt)
+local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt)
+local diagnostic_attached_buffers = {}
+local diagnostic_disabled = {}
+local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
+
+---@private
+local function is_disabled(namespace, bufnr)
+ if type(diagnostic_disabled[bufnr]) == "table" then
+ return diagnostic_disabled[bufnr][namespace]
+ end
+ return diagnostic_disabled[bufnr]
+end
+
+---@private
+local function diagnostic_lines(diagnostics)
+ if not diagnostics then
+ return
+ end
+
+ local diagnostics_by_line = {}
+ for _, diagnostic in ipairs(diagnostics) do
+ local line_diagnostics = diagnostics_by_line[diagnostic.lnum]
+ if not line_diagnostics then
+ line_diagnostics = {}
+ diagnostics_by_line[diagnostic.lnum] = line_diagnostics
+ end
+ table.insert(line_diagnostics, diagnostic)
+ end
+ return diagnostics_by_line
+end
+
+---@private
+local function set_diagnostic_cache(namespace, diagnostics, bufnr)
+ local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
+ for _, diagnostic in ipairs(diagnostics) do
+ if diagnostic.severity == nil then
+ diagnostic.severity = M.severity.ERROR
+ end
+
+ diagnostic.namespace = namespace
+ diagnostic.bufnr = bufnr
+
+ if buf_line_count > 0 then
+ diagnostic.lnum = math.max(math.min(
+ diagnostic.lnum, buf_line_count - 1
+ ), 0)
+ diagnostic.end_lnum = math.max(math.min(
+ diagnostic.end_lnum, buf_line_count - 1
+ ), 0)
+ end
+ end
+
+ diagnostic_cache[bufnr][namespace] = diagnostics
+end
+
+---@private
+local function clear_diagnostic_cache(namespace, bufnr)
+ diagnostic_cache[bufnr][namespace] = nil
+end
+
+---@private
+local function restore_extmarks(bufnr, last)
+ for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
+ local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
+ local found = {}
+ for _, extmark in ipairs(extmarks_current) do
+ -- nvim_buf_set_lines will move any extmark to the line after the last
+ -- nvim_buf_set_text will move any extmark to the last line
+ if extmark[2] ~= last + 1 then
+ found[extmark[1]] = true
+ end
+ end
+ for _, extmark in ipairs(extmarks) do
+ if not found[extmark[1]] then
+ local opts = extmark[4]
+ opts.id = extmark[1]
+ -- HACK: end_row should be end_line
+ if opts.end_row then
+ opts.end_line = opts.end_row
+ opts.end_row = nil
+ end
+ pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
+ end
+ end
+ end
+end
+
+---@private
+local function save_extmarks(namespace, bufnr)
+ bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr
+ if not diagnostic_attached_buffers[bufnr] then
+ vim.api.nvim_buf_attach(bufnr, false, {
+ on_lines = function(_, _, _, _, _, last)
+ restore_extmarks(bufnr, last - 1)
+ end,
+ on_detach = function()
+ diagnostic_cache_extmarks[bufnr] = nil
+ end})
+ diagnostic_attached_buffers[bufnr] = true
+ end
+ diagnostic_cache_extmarks[bufnr][namespace] = vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, {details = true})
+end
+
+local registered_autocmds = {}
+
+---@private
+local function make_augroup_key(namespace, bufnr)
+ local ns = get_namespace(namespace)
+ return string.format("DiagnosticInsertLeave:%s:%s", bufnr, ns.name)
+end
+
+--- Table of autocmd events to fire the update for displaying new diagnostic information
+local insert_leave_auto_cmds = { "InsertLeave", "CursorHoldI" }
+
+---@private
+local function schedule_display(namespace, bufnr, args)
+ bufs_waiting_to_update[bufnr][namespace] = args
+
+ local key = make_augroup_key(namespace, bufnr)
+ if not registered_autocmds[key] then
+ vim.cmd(string.format("augroup %s", key))
+ vim.cmd(" au!")
+ vim.cmd(
+ string.format(
+ [[autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)]],
+ table.concat(insert_leave_auto_cmds, ","),
+ bufnr,
+ namespace,
+ bufnr
+ )
+ )
+ vim.cmd("augroup END")
+
+ registered_autocmds[key] = true
+ end
+end
+
+---@private
+local function clear_scheduled_display(namespace, bufnr)
+ local key = make_augroup_key(namespace, bufnr)
+
+ if registered_autocmds[key] then
+ vim.cmd(string.format("augroup %s", key))
+ vim.cmd(" au!")
+ vim.cmd("augroup END")
+
+ registered_autocmds[key] = nil
+ end
+end
+
+---@private
+--- Open a floating window with the provided diagnostics
+---@param opts table Configuration table
+--- - show_header (boolean, default true): Show "Diagnostics:" header
+--- - all opts for |vim.util.open_floating_preview()| can be used here
+---@param diagnostics table: The diagnostics to display
+---@return table {popup_bufnr, win_id}
+local function show_diagnostics(opts, diagnostics)
+ if vim.tbl_isempty(diagnostics) then return end
+ local lines = {}
+ local highlights = {}
+ local show_header = vim.F.if_nil(opts.show_header, true)
+ if show_header then
+ table.insert(lines, "Diagnostics:")
+ table.insert(highlights, {0, "Bold"})
+ end
+
+ for i, diagnostic in ipairs(diagnostics) do
+ local prefix = string.format("%d. ", i)
+ local hiname = floating_highlight_map[diagnostic.severity]
+ assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
+
+ local message_lines = vim.split(diagnostic.message, '\n', true)
+ table.insert(lines, prefix..message_lines[1])
+ table.insert(highlights, {#prefix, hiname})
+ for j = 2, #message_lines do
+ table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
+ table.insert(highlights, {0, hiname})
+ end
+ end
+
+ local popup_bufnr, winnr = require('vim.lsp.util').open_floating_preview(lines, 'plaintext', opts)
+ for i, hi in ipairs(highlights) do
+ local prefixlen, hiname = unpack(hi)
+ -- Start highlight after the prefix
+ vim.api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1)
+ end
+
+ return popup_bufnr, winnr
+end
+
+local errlist_type_map = {
+ [M.severity.ERROR] = 'E',
+ [M.severity.WARN] = 'W',
+ [M.severity.INFO] = 'I',
+ [M.severity.HINT] = 'I',
+}
+
+---@private
+local function diagnostics_to_list_items(diagnostics)
+ local items = {}
+ for _, d in pairs(diagnostics) do
+ table.insert(items, {
+ bufnr = d.bufnr,
+ lnum = d.lnum + 1,
+ col = d.col + 1,
+ text = d.message,
+ type = errlist_type_map[d.severity or M.severity.ERROR] or 'E'
+ })
+ end
+ table.sort(items, function(a, b)
+ if a.bufnr == b.bufnr then
+ return a.lnum < b.lnum
+ else
+ return a.bufnr < b.bufnr
+ end
+ end)
+ return items
+end
+
+---@private
+local function set_list(loclist, opts)
+ opts = opts or {}
+ local open = vim.F.if_nil(opts.open, true)
+ local title = opts.title or "Diagnostics"
+ local winnr = opts.winnr or 0
+ local diagnostics = M.get(loclist and vim.api.nvim_win_get_buf(winnr), opts)
+ local items = diagnostics_to_list_items(diagnostics)
+ if loclist then
+ vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items })
+ else
+ vim.fn.setqflist({}, ' ', { title = title, items = items })
+ end
+ if open then
+ vim.api.nvim_command(loclist and "lopen" or "copen")
+ end
+end
+
+-- }}}
+
+-- Public API {{{
+
+--- Configure diagnostic options globally or for a specific diagnostic
+--- namespace.
+---
+---@note Each of the configuration options below accepts one of the following:
+--- - `false`: Disable this feature
+--- - `true`: Enable this feature, use default settings.
+--- - `table`: Enable this feature with overrides.
+--- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
+---
+---@param opts table Configuration table with the following keys:
+--- - underline: (default true) Use underline for diagnostics
+--- - virtual_text: (default true) Use virtual text for diagnostics
+--- - signs: (default true) Use signs for diagnostics
+--- - update_in_insert: (default false) Update diagnostics in Insert mode (if false,
+--- diagnostics are updated on InsertLeave)
+--- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in
+--- which signs and virtual text are displayed
+---@param namespace number|nil Update the options for the given namespace. When omitted, update the
+--- global diagnostic options.
+function M.config(opts, namespace)
+ vim.validate {
+ opts = { opts, 't' },
+ namespace = { namespace, 'n', true },
+ }
+
+ local t
+ if namespace then
+ local ns = get_namespace(namespace)
+ t = ns.opts
+ else
+ t = global_diagnostic_options
+ end
+
+ for opt in pairs(global_diagnostic_options) do
+ if opts[opt] ~= nil then
+ t[opt] = opts[opt]
+ end
+ end
+
+ if namespace then
+ for bufnr, v in pairs(diagnostic_cache) do
+ if v[namespace] then
+ M.show(namespace, bufnr)
+ end
+ end
+ else
+ for bufnr, v in pairs(diagnostic_cache) do
+ for ns in pairs(v) do
+ M.show(ns, bufnr)
+ end
+ end
+ end
+end
+
+--- Set diagnostics for the given namespace and buffer.
+---
+---@param namespace number The diagnostic namespace
+---@param bufnr number Buffer number
+---@param diagnostics table A list of diagnostic items |diagnostic-structure|
+---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
+function M.set(namespace, bufnr, diagnostics, opts)
+ vim.validate {
+ namespace = {namespace, 'n'},
+ bufnr = {bufnr, 'n'},
+ diagnostics = {diagnostics, 't'},
+ opts = {opts, 't', true},
+ }
+
+ if vim.tbl_isempty(diagnostics) then
+ return M.reset(namespace, bufnr)
+ end
+
+ if not diagnostic_cleanup[bufnr][namespace] then
+ diagnostic_cleanup[bufnr][namespace] = true
+
+ -- Clean up our data when the buffer unloads.
+ vim.api.nvim_buf_attach(bufnr, false, {
+ on_detach = function(_, b)
+ clear_diagnostic_cache(b, namespace)
+ diagnostic_cleanup[b][namespace] = nil
+ end
+ })
+ end
+
+ set_diagnostic_cache(namespace, diagnostics, bufnr)
+
+ if opts then
+ M.config(opts, namespace)
+ end
+
+ if vim.api.nvim_buf_is_loaded(bufnr) then
+ M.show(namespace, bufnr)
+ end
+
+ vim.api.nvim_command("doautocmd <nomodeline> User DiagnosticsChanged")
+end
+
+--- Get current diagnostics.
+---
+---@param bufnr number|nil Buffer number to get diagnistics from. Use 0 for
+--- current buffer or nil for all buffers.
+---@param opts table|nil A table with the following keys:
+--- - namespace: (number) Limit diagnostics to the given namespace.
+--- - lnum: (number) Limit diagnostics to the given line number.
+--- - severity: See |diagnostic-severity|.
+---@return table A list of diagnostic items |diagnostic-structure|.
+function M.get(bufnr, opts)
+ vim.validate {
+ bufnr = { bufnr, 'n', true },
+ opts = { opts, 't', true },
+ }
+
+ opts = opts or {}
+
+ local namespace = opts.namespace
+ local diagnostics = {}
+
+ ---@private
+ local function add(d)
+ if not opts.lnum or d.lnum == opts.lnum then
+ table.insert(diagnostics, d)
+ end
+ end
+
+ if namespace == nil and bufnr == nil then
+ for _, t in pairs(diagnostic_cache) do
+ for _, v in pairs(t) do
+ for _, diagnostic in pairs(v) do
+ add(diagnostic)
+ end
+ end
+ end
+ elseif namespace == nil then
+ for iter_namespace in pairs(diagnostic_cache[bufnr]) do
+ for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do
+ add(diagnostic)
+ end
+ end
+ elseif bufnr == nil then
+ for _, t in pairs(diagnostic_cache) do
+ for _, diagnostic in pairs(t[namespace] or {}) do
+ add(diagnostic)
+ end
+ end
+ else
+ for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do
+ add(diagnostic)
+ end
+ end
+
+ if opts.severity then
+ diagnostics = filter_by_severity(opts.severity, diagnostics)
+ end
+
+ return diagnostics
+end
+
+-- Diagnostic Movements {{{
+
+local next_diagnostic = function(position, search_forward, bufnr, opts, namespace)
+ position[1] = position[1] - 1
+ bufnr = bufnr or vim.api.nvim_get_current_buf()
+ local wrap = vim.F.if_nil(opts.wrap, true)
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ opts.namespace = namespace
+ for i = 0, line_count do
+ local offset = i * (search_forward and 1 or -1)
+ local lnum = position[1] + offset
+ if lnum < 0 or lnum >= line_count then
+ if not wrap then
+ return
+ end
+ lnum = (lnum + line_count) % line_count
+ end
+ opts.lnum = lnum
+ local line_diagnostics = M.get(bufnr, opts)
+ if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
+ local sort_diagnostics, is_next
+ if search_forward then
+ sort_diagnostics = function(a, b) return a.col < b.col end
+ is_next = function(diagnostic) return diagnostic.col > position[2] end
+ else
+ sort_diagnostics = function(a, b) return a.col > b.col end
+ is_next = function(diagnostic) return diagnostic.col < position[2] end
+ end
+ table.sort(line_diagnostics, sort_diagnostics)
+ if i == 0 then
+ for _, v in pairs(line_diagnostics) do
+ if is_next(v) then
+ return v
+ end
+ end
+ else
+ return line_diagnostics[1]
+ end
+ end
+ end
+end
+
+---@private
+local function diagnostic_pos(opts, diagnostic)
+ opts = opts or {}
+
+ local win_id = opts.win_id or vim.api.nvim_get_current_win()
+ local bufnr = vim.api.nvim_win_get_buf(win_id)
+
+ if not diagnostic then
+ return false
+ end
+
+ local lnum, col = diagnostic.lnum, diagnostic.col
+ if col > 0 then
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+
+ local line = vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
+ if line then
+ local ok, result = pcall(vim.str_byteindex, line, col)
+
+ if ok then
+ col = result
+ end
+ end
+ end
+
+ return {lnum, col}
+end
+
+---@private
+local function diagnostic_move_pos(name, opts, pos)
+ opts = opts or {}
+
+ local enable_popup = vim.F.if_nil(opts.enable_popup, true)
+ local win_id = opts.win_id or vim.api.nvim_get_current_win()
+
+ if not pos then
+ print(string.format("%s: No more valid diagnostics to move to.", name))
+ return
+ end
+
+ vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]})
+
+ if enable_popup then
+ -- This is a bit weird... I'm surprised that we need to wait til the next tick to do this.
+ vim.schedule(function()
+ M.show_position_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id))
+ end)
+ end
+end
+
+--- Get the previous diagnostic closest to the cursor position.
+---
+---@param opts table See |vim.diagnostic.goto_next()|
+---@return table Previous diagnostic
+function M.get_prev(opts)
+ opts = opts or {}
+
+ local win_id = opts.win_id or vim.api.nvim_get_current_win()
+ local bufnr = vim.api.nvim_win_get_buf(win_id)
+ local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
+
+ return next_diagnostic(cursor_position, false, bufnr, opts, opts.namespace)
+end
+
+--- Return the position of the previous diagnostic in the current buffer.
+---
+---@param opts table See |vim.diagnostic.goto_next()|
+---@return table Previous diagnostic position as a (row, col) tuple.
+function M.get_prev_pos(opts)
+ return diagnostic_pos(
+ opts,
+ M.get_prev(opts)
+ )
+end
+
+--- Move to the previous diagnostic in the current buffer.
+---@param opts table See |vim.diagnostic.goto_next()|
+function M.goto_prev(opts)
+ return diagnostic_move_pos(
+ "DiagnosticPrevious",
+ opts,
+ M.get_prev_pos(opts)
+ )
+end
+
+--- Get the next diagnostic closest to the cursor position.
+---
+---@param opts table See |vim.diagnostic.goto_next()|
+---@return table Next diagnostic
+function M.get_next(opts)
+ opts = opts or {}
+
+ local win_id = opts.win_id or vim.api.nvim_get_current_win()
+ local bufnr = vim.api.nvim_win_get_buf(win_id)
+ local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
+
+ return next_diagnostic(cursor_position, true, bufnr, opts, opts.namespace)
+end
+
+--- Return the position of the next diagnostic in the current buffer.
+---
+---@param opts table See |vim.diagnostic.goto_next()|
+---@return table Next diagnostic position as a (row, col) tuple.
+function M.get_next_pos(opts)
+ return diagnostic_pos(
+ opts,
+ M.get_next(opts)
+ )
+end
+
+--- Move to the next diagnostic.
+---
+---@param opts table|nil Configuration table with the following keys:
+--- - namespace: (number) Only consider diagnostics from the given namespace.
+--- - cursor_position: (cursor position) Cursor position as a (row, col) tuple. See
+--- |nvim_win_get_cursor()|. Defaults to the current cursor position.
+--- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'.
+--- - severity: See |diagnostic-severity|.
+--- - enable_popup: (boolean, default true) Call |vim.diagnostic.show_line_diagnostics()|
+--- on jump.
+--- - popup_opts: (table) Table to pass as {opts} parameter to
+--- |vim.diagnostic.show_line_diagnostics()|
+--- - win_id: (number, default 0) Window ID
+function M.goto_next(opts)
+ return diagnostic_move_pos(
+ "DiagnosticNext",
+ opts,
+ M.get_next_pos(opts)
+ )
+end
+
+-- Diagnostic Setters {{{
+
+--- Set signs for given diagnostics.
+---
+---@param namespace number The diagnostic namespace
+---@param bufnr number Buffer number
+---@param diagnostics table A list of diagnostic items |diagnostic-structure|. When omitted the
+--- current diagnostics in the given buffer are used.
+---@param opts table Configuration table with the following keys:
+--- - priority: Set the priority of the signs |sign-priority|.
+---@private
+function M._set_signs(namespace, bufnr, diagnostics, opts)
+ vim.validate {
+ namespace = {namespace, 'n'},
+ bufnr = {bufnr, 'n'},
+ diagnostics = {diagnostics, 't'},
+ opts = {opts, 't', true},
+ }
+
+ bufnr = get_bufnr(bufnr)
+ opts = get_resolved_options({ signs = opts }, namespace, bufnr).signs
+
+ if opts and opts.severity then
+ diagnostics = filter_by_severity(opts.severity, diagnostics)
+ end
+
+ local ns = get_namespace(namespace)
+
+ define_default_signs()
+
+ for _, diagnostic in ipairs(diagnostics) do
+ vim.fn.sign_place(
+ 0,
+ ns.sign_group,
+ sign_highlight_map[diagnostic.severity],
+ bufnr,
+ {
+ priority = opts and opts.priority,
+ lnum = diagnostic.lnum + 1
+ }
+ )
+ end
+end
+
+--- Set underline for given diagnostics.
+---
+---@param namespace number The diagnostic namespace
+---@param bufnr number Buffer number
+---@param diagnostics table A list of diagnostic items |diagnostic-structure|. When omitted the
+--- current diagnostics in the given buffer are used.
+---@param opts table Configuration table. Currently unused.
+---@private
+function M._set_underline(namespace, bufnr, diagnostics, opts)
+ vim.validate {
+ namespace = {namespace, 'n'},
+ bufnr = {bufnr, 'n'},
+ diagnostics = {diagnostics, 't'},
+ opts = {opts, 't', true},
+ }
+
+ bufnr = get_bufnr(bufnr)
+ opts = get_resolved_options({ underline = opts }, namespace, bufnr).underline
+
+ if opts and opts.severity then
+ diagnostics = filter_by_severity(opts.severity, diagnostics)
+ end
+
+ for _, diagnostic in ipairs(diagnostics) do
+ local higroup = underline_highlight_map[diagnostic.severity]
+
+ if higroup == nil then
+ -- Default to error if we don't have a highlight associated
+ higroup = underline_highlight_map.Error
+ end
+
+ vim.highlight.range(
+ bufnr,
+ namespace,
+ higroup,
+ { diagnostic.lnum, diagnostic.col },
+ { diagnostic.end_lnum, diagnostic.end_col }
+ )
+ end
+end
+
+--- Set virtual text for given diagnostics.
+---
+---@param namespace number The diagnostic namespace
+---@param bufnr number Buffer number
+---@param diagnostics table A list of diagnostic items |diagnostic-structure|. When omitted the
+--- current diagnostics in the given buffer are used.
+---@param opts table|nil Configuration table with the following keys:
+--- - prefix: (string) Prefix to display before virtual text on line.
+--- - spacing: (number) Number of spaces to insert before virtual text.
+---@private
+function M._set_virtual_text(namespace, bufnr, diagnostics, opts)
+ vim.validate {
+ namespace = {namespace, 'n'},
+ bufnr = {bufnr, 'n'},
+ diagnostics = {diagnostics, 't'},
+ opts = {opts, 't', true},
+ }
+
+ bufnr = get_bufnr(bufnr)
+ opts = get_resolved_options({ virtual_text = opts }, namespace, bufnr).virtual_text
+
+ local buffer_line_diagnostics = diagnostic_lines(diagnostics)
+ for line, line_diagnostics in pairs(buffer_line_diagnostics) do
+ if opts and opts.severity then
+ line_diagnostics = filter_by_severity(opts.severity, line_diagnostics)
+ end
+ local virt_texts = M.get_virt_text_chunks(line_diagnostics, opts)
+
+ if virt_texts then
+ vim.api.nvim_buf_set_extmark(bufnr, namespace, line, 0, {
+ hl_mode = "combine",
+ virt_text = virt_texts,
+ })
+ end
+ end
+end
+
+--- Get virtual text chunks to display using |nvim_buf_set_extmark()|.
+---
+---@param line_diags table The diagnostics associated with the line.
+---@param opts table|nil Configuration table with the following keys:
+--- - prefix: (string) Prefix to display before virtual text on line.
+--- - spacing: (number) Number of spaces to insert before virtual text.
+---@return an array of [text, hl_group] arrays. This can be passed directly to
+--- the {virt_text} option of |nvim_buf_set_extmark()|.
+function M.get_virt_text_chunks(line_diags, opts)
+ if #line_diags == 0 then
+ return nil
+ end
+
+ opts = opts or {}
+ local prefix = opts.prefix or "■"
+ local spacing = opts.spacing or 4
+
+ -- Create a little more space between virtual text and contents
+ local virt_texts = {{string.rep(" ", spacing)}}
+
+ for i = 1, #line_diags - 1 do
+ table.insert(virt_texts, {prefix, virtual_text_highlight_map[line_diags[i].severity]})
+ end
+ local last = line_diags[#line_diags]
+
+ -- TODO(tjdevries): Allow different servers to be shown first somehow?
+ -- TODO(tjdevries): Display server name associated with these?
+ if last.message then
+ table.insert(
+ virt_texts,
+ {
+ string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")),
+ virtual_text_highlight_map[last.severity]
+ }
+ )
+
+ return virt_texts
+ end
+end
+
+--- Callback scheduled when leaving Insert mode.
+---
+--- This function must be exported publicly so that it is available to be
+--- called from the Vimscript autocommand.
+---
+--- See @ref schedule_display()
+---
+---@private
+function M._execute_scheduled_display(namespace, bufnr)
+ local args = bufs_waiting_to_update[bufnr][namespace]
+ if not args then
+ return
+ end
+
+ -- Clear the args so we don't display unnecessarily.
+ bufs_waiting_to_update[bufnr][namespace] = nil
+
+ M.show(namespace, bufnr, nil, args)
+end
+
+--- Hide currently displayed diagnostics.
+---
+--- This only clears the decorations displayed in the buffer. Diagnostics can
+--- be redisplayed with |vim.diagnostic.show()|. To completely remove
+--- diagnostics, use |vim.diagnostic.reset()|.
+---
+--- To hide diagnostics and prevent them from re-displaying, use
+--- |vim.diagnostic.disable()|.
+---
+---@param namespace number The diagnostic namespace
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+function M.hide(namespace, bufnr)
+ vim.validate {
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n', true },
+ }
+
+ bufnr = get_bufnr(bufnr)
+ diagnostic_cache_extmarks[bufnr][namespace] = {}
+
+ local ns = get_namespace(namespace)
+
+ -- clear sign group
+ vim.fn.sign_unplace(ns.sign_group, {buffer=bufnr})
+
+ -- clear virtual text namespace
+ vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
+end
+
+
+--- Display diagnostics for the given namespace and buffer.
+---
+---@param namespace number Diagnostic namespace
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+---@param diagnostics table|nil The diagnostics to display. When omitted, use the
+--- saved diagnostics for the given namespace and
+--- buffer. This can be used to display a list of diagnostics
+--- without saving them or to display only a subset of
+--- diagnostics.
+---@param opts table|nil Display options. See |vim.diagnostic.config()|.
+function M.show(namespace, bufnr, diagnostics, opts)
+ vim.validate {
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n', true },
+ diagnostics = { diagnostics, 't', true },
+ opts = { opts, 't', true },
+ }
+
+ bufnr = get_bufnr(bufnr)
+ if is_disabled(namespace, bufnr) then
+ return
+ end
+
+ M.hide(namespace, bufnr)
+
+ diagnostics = diagnostics or M.get(bufnr, {namespace=namespace})
+
+ if not diagnostics or vim.tbl_isempty(diagnostics) then
+ return
+ end
+
+ opts = get_resolved_options(opts, namespace, bufnr)
+
+ if opts.update_in_insert then
+ clear_scheduled_display(namespace, bufnr)
+ else
+ local mode = vim.api.nvim_get_mode()
+ if string.sub(mode.mode, 1, 1) == 'i' then
+ schedule_display(namespace, bufnr, opts)
+ return
+ end
+ end
+
+ if opts.underline then
+ M._set_underline(namespace, bufnr, diagnostics, opts.underline)
+ end
+
+ if opts.virtual_text then
+ M._set_virtual_text(namespace, bufnr, diagnostics, opts.virtual_text)
+ end
+
+ if opts.signs then
+ M._set_signs(namespace, bufnr, diagnostics, opts.signs)
+ end
+
+ save_extmarks(namespace, bufnr)
+end
+
+--- Open a floating window with the diagnostics at the given position.
+---
+---@param opts table|nil Configuration table with the same keys as
+--- |vim.lsp.util.open_floatin_preview()| in addition to the following:
+--- - namespace: (number) Limit diagnostics to the given namespace
+--- - severity: See |diagnostic-severity|.
+--- - show_header: (boolean, default true) Show "Diagnostics:" header
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+---@param position table|nil The (0,0)-indexed position. Defaults to the current cursor position.
+---@return A ({popup_bufnr}, {win_id}) tuple
+function M.show_position_diagnostics(opts, bufnr, position)
+ vim.validate {
+ opts = { opts, 't', true },
+ bufnr = { bufnr, 'n', true },
+ position = { position, 't', true },
+ }
+
+ opts = opts or {}
+
+ opts.focus_id = "position_diagnostics"
+ bufnr = get_bufnr(bufnr)
+ if not position then
+ local curr_position = vim.api.nvim_win_get_cursor(0)
+ curr_position[1] = curr_position[1] - 1
+ position = curr_position
+ end
+ local match_position_predicate = function(diag)
+ return position[1] == diag.lnum and
+ position[2] >= diag.col and
+ (position[2] <= diag.end_col or position[1] < diag.end_lnum)
+ end
+ local position_diagnostics = vim.tbl_filter(match_position_predicate, M.get(bufnr, opts))
+ table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end)
+ return show_diagnostics(opts, position_diagnostics)
+end
+
+--- Open a floating window with the diagnostics from the given line.
+---
+---@param opts table Configuration table. See |vim.diagnostic.show_position_diagnostics()|.
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+---@param lnum number|nil Line number. Defaults to line number of cursor.
+---@return A ({popup_bufnr}, {win_id}) tuple
+function M.show_line_diagnostics(opts, bufnr, lnum)
+ vim.validate {
+ opts = { opts, 't', true },
+ bufnr = { bufnr, 'n', true },
+ lnum = { lnum, 'n', true },
+ }
+
+ opts = opts or {}
+ opts.focus_id = "line_diagnostics"
+ opts.lnum = lnum
+ local line_diagnostics = M.get(bufnr, opts)
+ return show_diagnostics(opts, line_diagnostics)
+end
+
+--- Remove all diagnostics from the given namespace.
+---
+--- Unlike |vim.diagnostic.hide()|, this function removes all saved
+--- diagnostics. They cannot be redisplayed using |vim.diagnostic.show()|. To
+--- simply remove diagnostic decorations in a way that they can be
+--- re-displayed, use |vim.diagnostic.hide()|.
+---
+---@param namespace number
+---@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
+--- diagnostics are removed for all buffers.
+function M.reset(namespace, bufnr)
+ if bufnr == nil then
+ for iter_bufnr, namespaces in pairs(diagnostic_cache) do
+ if namespaces[namespace] then
+ M.reset(namespace, iter_bufnr)
+ end
+ end
+ else
+ clear_diagnostic_cache(namespace, bufnr)
+ M.hide(namespace, bufnr)
+ end
+
+ vim.api.nvim_command("doautocmd <nomodeline> User DiagnosticsChanged")
+end
+
+--- Add all diagnostics to the quickfix list.
+---
+---@param opts table|nil Configuration table with the following keys:
+--- - namespace: (number) Only add diagnostics from the given namespace.
+--- - open: (boolean, default true) Open quickfix list after setting.
+--- - title: (string) Title of quickfix list. Defaults to "Diagnostics".
+--- - severity: See |diagnostic-severity|.
+function M.setqflist(opts)
+ set_list(false, opts)
+end
+
+--- Add buffer diagnostics to the location list.
+---
+---@param opts table|nil Configuration table with the following keys:
+--- - namespace: (number) Only add diagnostics from the given namespace.
+--- - winnr: (number, default 0) Window number to set location list for.
+--- - open: (boolean, default true) Open the location list after setting.
+--- - title: (string) Title of the location list. Defaults to "Diagnostics".
+--- - severity: See |diagnostic-severity|.
+function M.setloclist(opts)
+ set_list(true, opts)
+end
+
+--- Disable diagnostics in the given buffer.
+---
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+---@param namespace number|nil Only disable diagnostics for the given namespace.
+function M.disable(bufnr, namespace)
+ vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
+ bufnr = get_bufnr(bufnr)
+ if namespace == nil then
+ diagnostic_disabled[bufnr] = true
+ for ns in pairs(diagnostic_cache[bufnr]) do
+ M.hide(ns, bufnr)
+ end
+ else
+ if type(diagnostic_disabled[bufnr]) ~= "table" then
+ diagnostic_disabled[bufnr] = {}
+ end
+ diagnostic_disabled[bufnr][namespace] = true
+ M.hide(namespace, bufnr)
+ end
+end
+
+--- Enable diagnostics in the given buffer.
+---
+---@param bufnr number|nil Buffer number. Defaults to the current buffer.
+---@param namespace number|nil Only enable diagnostics for the given namespace.
+function M.enable(bufnr, namespace)
+ vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
+ bufnr = get_bufnr(bufnr)
+ if namespace == nil then
+ diagnostic_disabled[bufnr] = nil
+ for ns in pairs(diagnostic_cache[bufnr]) do
+ M.show(ns, bufnr)
+ end
+ else
+ if type(diagnostic_disabled[bufnr]) ~= "table" then
+ return
+ end
+ diagnostic_disabled[bufnr][namespace] = nil
+ M.show(namespace, bufnr)
+ end
+end
+
+-- }}}
+
+
+return M
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 0fdd43e210..72cf7866bb 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1534,8 +1534,5 @@ function lsp._with_extend(name, options, user_config)
return resulting_config
end
--- Define the LspDiagnostics signs if they're not defined already.
-require('vim.lsp.diagnostic')._define_default_signs_and_highlights()
-
return lsp
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index ccd325b1ac..338491701f 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1,48 +1,4 @@
-local api = vim.api
-local validate = vim.validate
-
-local highlight = vim.highlight
local log = require('vim.lsp.log')
-local protocol = require('vim.lsp.protocol')
-local util = require('vim.lsp.util')
-
-local if_nil = vim.F.if_nil
-
----@class DiagnosticSeverity
-local DiagnosticSeverity = protocol.DiagnosticSeverity
-
-local to_severity = function(severity)
- if not severity then return nil end
- return type(severity) == 'string' and DiagnosticSeverity[severity] or severity
-end
-
-local filter_to_severity_limit = function(severity, diagnostics)
- local filter_level = to_severity(severity)
- if not filter_level then
- return diagnostics
- end
-
- return vim.tbl_filter(function(t) return t.severity == filter_level end, diagnostics)
-end
-
-local filter_by_severity_limit = function(severity_limit, diagnostics)
- local filter_level = to_severity(severity_limit)
- if not filter_level then
- return diagnostics
- end
-
- return vim.tbl_filter(function(t) return t.severity <= filter_level end, diagnostics)
-end
-
-local to_position = function(position, bufnr)
- vim.validate { position = {position, 't'} }
-
- return {
- position.line,
- util._get_line_byte_from_position(bufnr, position)
- }
-end
-
---@brief lsp-diagnostic
---
@@ -57,70 +13,9 @@ end
local M = {}
--- Diagnostic Highlights {{{
-
--- TODO(tjdevries): Determine how to generate documentation for these
--- and how to configure them to be easy for users.
---
--- For now, just use the following script. It should work pretty good.
---[[
-local levels = {"Error", "Warning", "Information", "Hint" }
-
-local all_info = {
- { "Default", "Used as the base highlight group, other highlight groups link to", },
- { "VirtualText", 'Used for "%s" diagnostic virtual text.\n See |vim.lsp.diagnostic.set_virtual_text()|', },
- { "Underline", 'Used to underline "%s" diagnostics.\n See |vim.lsp.diagnostic.set_underline()|', },
- { "Floating", 'Used to color "%s" diagnostic messages in diagnostics float.\n See |vim.lsp.diagnostic.show_line_diagnostics()|', },
- { "Sign", 'Used for "%s" signs in sing column.\n See |vim.lsp.diagnostic.set_signs()|', },
-}
-
-local results = {}
-for _, info in ipairs(all_info) do
- for _, level in ipairs(levels) do
- local name = info[1]
- local description = info[2]
- local fullname = string.format("Lsp%s%s", name, level)
- table.insert(results, string.format(
- "%78s", string.format("*hl-%s*", fullname))
- )
-
- table.insert(results, fullname)
- table.insert(results, string.format(" %s", description))
- table.insert(results, "")
- end
-end
-
--- print(table.concat(results, '\n'))
-vim.fn.setreg("*", table.concat(results, '\n'))
---]]
-
-local diagnostic_severities = {
- [DiagnosticSeverity.Error] = { guifg = "Red" };
- [DiagnosticSeverity.Warning] = { guifg = "Orange" };
- [DiagnosticSeverity.Information] = { guifg = "LightBlue" };
- [DiagnosticSeverity.Hint] = { guifg = "LightGrey" };
-}
-
--- Make a map from DiagnosticSeverity -> Highlight Name
-local make_highlight_map = function(base_name)
- local result = {}
- for k, _ in pairs(diagnostic_severities) do
- result[k] = "LspDiagnostics" .. base_name .. DiagnosticSeverity[k]
- end
-
- return result
-end
-
-local default_highlight_map = make_highlight_map("Default")
-local virtual_text_highlight_map = make_highlight_map("VirtualText")
-local underline_highlight_map = make_highlight_map("Underline")
-local floating_highlight_map = make_highlight_map("Floating")
-local sign_highlight_map = make_highlight_map("Sign")
-
--- }}}
--- Diagnostic Namespaces {{{
local DEFAULT_CLIENT_ID = -1
-local get_client_id = function(client_id)
+---@private
+local function get_client_id(client_id)
if client_id == nil then
client_id = DEFAULT_CLIENT_ID
end
@@ -128,179 +23,112 @@ local get_client_id = function(client_id)
return client_id
end
-local get_bufnr = function(bufnr)
+---@private
+local function get_bufnr(bufnr)
if not bufnr then
- return api.nvim_get_current_buf()
+ return vim.api.nvim_get_current_buf()
elseif bufnr == 0 then
- return api.nvim_get_current_buf()
+ return vim.api.nvim_get_current_buf()
end
return bufnr
end
-
---- Create a namespace table, used to track a client's buffer local items
-local _make_namespace_table = function(namespace, api_namespace)
- vim.validate { namespace = { namespace, 's' } }
-
- return setmetatable({
- [DEFAULT_CLIENT_ID] = api.nvim_create_namespace(namespace)
- }, {
- __index = function(t, client_id)
- client_id = get_client_id(client_id)
-
- if rawget(t, client_id) == nil then
- local value = string.format("%s:%s", namespace, client_id)
-
- if api_namespace then
- value = api.nvim_create_namespace(value)
- end
-
- rawset(t, client_id, value)
- end
-
- return rawget(t, client_id)
- end
- })
-end
-
-local _diagnostic_namespaces = _make_namespace_table("vim_lsp_diagnostics", true)
-local _sign_namespaces = _make_namespace_table("vim_lsp_signs", false)
-
---@private
-function M._get_diagnostic_namespace(client_id)
- return _diagnostic_namespaces[client_id]
+local function severity_lsp_to_vim(severity)
+ if type(severity) == 'string' then
+ severity = vim.lsp.protocol.DiagnosticSeverity[severity]
+ end
+ return severity
end
---@private
-function M._get_sign_namespace(client_id)
- return _sign_namespaces[client_id]
-end
--- }}}
--- Diagnostic Buffer & Client metatables {{{
-local bufnr_and_client_cacher_mt = {
- __index = function(t, bufnr)
- if bufnr == 0 or bufnr == nil then
- bufnr = vim.api.nvim_get_current_buf()
- end
-
- if rawget(t, bufnr) == nil then
- rawset(t, bufnr, {})
- end
-
- return rawget(t, bufnr)
- end,
-
- __newindex = function(t, bufnr, v)
- if bufnr == 0 or bufnr == nil then
- bufnr = vim.api.nvim_get_current_buf()
- end
-
- rawset(t, bufnr, v)
- end,
-}
--- }}}
--- Diagnostic Saving & Caching {{{
-local _diagnostic_cleanup = setmetatable({}, bufnr_and_client_cacher_mt)
-local diagnostic_cache = setmetatable({}, bufnr_and_client_cacher_mt)
-local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_client_cacher_mt)
-local diagnostic_cache_lines = setmetatable({}, bufnr_and_client_cacher_mt)
-local diagnostic_cache_counts = setmetatable({}, bufnr_and_client_cacher_mt)
-local diagnostic_attached_buffers = {}
-
--- Disabled buffers and clients
-local diagnostic_disabled = setmetatable({}, bufnr_and_client_cacher_mt)
-
-local _bufs_waiting_to_update = setmetatable({}, bufnr_and_client_cacher_mt)
-
---- Store Diagnostic[] by line
----
----@param diagnostics Diagnostic[]
----@return table<number, Diagnostic[]>
-local _diagnostic_lines = function(diagnostics)
- if not diagnostics then return end
-
- local diagnostics_by_line = {}
- for _, diagnostic in ipairs(diagnostics) do
- local start = diagnostic.range.start
- local line_diagnostics = diagnostics_by_line[start.line]
- if not line_diagnostics then
- line_diagnostics = {}
- diagnostics_by_line[start.line] = line_diagnostics
- end
- table.insert(line_diagnostics, diagnostic)
+local function severity_vim_to_lsp(severity)
+ if type(severity) == 'string' then
+ severity = vim.diagnostic.severity[severity]
end
- return diagnostics_by_line
+ return severity
end
---- Get the count of M by Severity
----
----@param diagnostics Diagnostic[]
----@return table<DiagnosticSeverity, number>
-local _diagnostic_counts = function(diagnostics)
- if not diagnostics then return end
-
- local counts = {}
- for _, diagnostic in pairs(diagnostics) do
- if diagnostic.severity then
- local val = counts[diagnostic.severity]
- if val == nil then
- val = 0
- end
+---@private
+local function line_byte_from_position(lines, lnum, col, offset_encoding)
+ if offset_encoding == "utf-8" then
+ return col
+ end
- counts[diagnostic.severity] = val + 1
- end
+ local line = lines[lnum + 1]
+ local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == "utf-16")
+ if ok then
+ return result
end
- return counts
+ return col
end
---@private
---- Set the different diagnostic cache after `textDocument/publishDiagnostics`
----@param diagnostics Diagnostic[]
----@param bufnr number
----@param client_id number
----@return nil
-local function set_diagnostic_cache(diagnostics, bufnr, client_id)
- client_id = get_client_id(client_id)
-
- -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
- --
- -- The diagnostic's severity. Can be omitted. If omitted it is up to the
- -- client to interpret diagnostics as error, warning, info or hint.
- -- TODO: Replace this with server-specific heuristics to infer severity.
- local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
- for _, diagnostic in ipairs(diagnostics) do
- if diagnostic.severity == nil then
- diagnostic.severity = DiagnosticSeverity.Error
- end
- -- Account for servers that place diagnostics on terminating newline
- if buf_line_count > 0 then
- diagnostic.range.start.line = math.max(math.min(
- diagnostic.range.start.line, buf_line_count - 1
- ), 0)
- diagnostic.range["end"].line = math.max(math.min(
- diagnostic.range["end"].line, buf_line_count - 1
- ), 0)
- end
+local function get_buf_lines(bufnr)
+ if vim.api.nvim_buf_is_loaded(bufnr) then
+ return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
end
- diagnostic_cache[bufnr][client_id] = diagnostics
- diagnostic_cache_lines[bufnr][client_id] = _diagnostic_lines(diagnostics)
- diagnostic_cache_counts[bufnr][client_id] = _diagnostic_counts(diagnostics)
+ local filename = vim.api.nvim_buf_get_name(bufnr)
+ local f = io.open(filename)
+ local lines = vim.split(f:read("*a"), "\n")
+ f:close()
+ return lines
end
+---@private
+local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
+ local buf_lines = get_buf_lines(bufnr)
+ local client = vim.lsp.get_client_by_id(client_id)
+ local offset_encoding = client and client.offset_encoding or "utf-16"
+ return vim.tbl_map(function(diagnostic)
+ local start = diagnostic.range.start
+ local _end = diagnostic.range["end"]
+ return {
+ lnum = start.line,
+ col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
+ end_lnum = _end.line,
+ end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding),
+ severity = severity_lsp_to_vim(diagnostic.severity),
+ message = diagnostic.message
+ }
+ end, diagnostics)
+end
---@private
---- Clear the cached diagnostics
----@param bufnr number
----@param client_id number
-local function clear_diagnostic_cache(bufnr, client_id)
- client_id = get_client_id(client_id)
+local function diagnostic_vim_to_lsp(diagnostics)
+ return vim.tbl_map(function(diagnostic)
+ return {
+ range = {
+ start = {
+ line = diagnostic.lnum,
+ character = diagnostic.col,
+ },
+ ["end"] = {
+ line = diagnostic.end_lnum,
+ character = diagnostic.end_col,
+ },
+ },
+ severity = severity_vim_to_lsp(diagnostic.severity),
+ message = diagnostic.message,
+ }
+ end, diagnostics)
+end
- diagnostic_cache[bufnr][client_id] = nil
- diagnostic_cache_lines[bufnr][client_id] = nil
- diagnostic_cache_counts[bufnr][client_id] = nil
+local _client_namespaces = {}
+
+--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|.
+---
+---@param client_id number The id of the LSP client
+function M.get_namespace(client_id)
+ vim.validate { client_id = { client_id, 'n' } }
+ if not _client_namespaces[client_id] then
+ local name = string.format("vim.lsp.client-%d", client_id)
+ _client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
+ end
+ return _client_namespaces[client_id]
end
--- Save diagnostics to the current buffer.
@@ -309,86 +137,146 @@ end
---@param diagnostics Diagnostic[]
---@param bufnr number
---@param client_id number
+---@private
function M.save(diagnostics, bufnr, client_id)
- validate {
- diagnostics = {diagnostics, 't'},
- bufnr = {bufnr, 'n'},
- client_id = {client_id, 'n', true},
- }
+ local namespace = M.get_namespace(client_id)
+ vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+end
+-- }}}
+
+--- |lsp-handler| for the method "textDocument/publishDiagnostics"
+---
+--- See |vim.diagnostic.config()| for configuration options. Handler-specific
+--- configuration can be set using |vim.lsp.with()|:
+--- <pre>
+--- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
+--- vim.lsp.diagnostic.on_publish_diagnostics, {
+--- -- Enable underline, use default values
+--- underline = true,
+--- -- Enable virtual text, override spacing to 4
+--- virtual_text = {
+--- spacing = 4,
+--- },
+--- -- Use a function to dynamically turn signs off
+--- -- and on, using buffer local variables
+--- signs = function(bufnr, client_id)
+--- return vim.bo[bufnr].show_signs == false
+--- end,
+--- -- Disable a feature
+--- update_in_insert = false,
+--- }
+--- )
+--- </pre>
+---
+---@param config table Configuration table (see |vim.diagnostic.config()|).
+function M.on_publish_diagnostics(_, result, ctx, config)
+ local client_id = ctx.client_id
+ local uri = result.uri
+ local bufnr = vim.uri_to_bufnr(uri)
- if not diagnostics then return end
+ if not bufnr then
+ return
+ end
- bufnr = get_bufnr(bufnr)
client_id = get_client_id(client_id)
+ local namespace = M.get_namespace(client_id)
+ local diagnostics = result.diagnostics
- if not _diagnostic_cleanup[bufnr][client_id] then
- _diagnostic_cleanup[bufnr][client_id] = true
+ if config then
+ if vim.F.if_nil(config.severity_sort, false) then
+ table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
+ end
- -- Clean up our data when the buffer unloads.
- api.nvim_buf_attach(bufnr, false, {
- on_detach = function(_, b)
- clear_diagnostic_cache(b, client_id)
- _diagnostic_cleanup[b][client_id] = nil
+ for _, opt in pairs(config) do
+ if type(opt) == 'table' then
+ if not opt.severity and opt.severity_limit then
+ opt.severity = {min=severity_lsp_to_vim(opt.severity_limit)}
+ end
end
- })
+ end
+
+ vim.diagnostic.config(config, namespace)
end
- set_diagnostic_cache(diagnostics, bufnr, client_id)
+ vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+
+ -- Keep old autocmd for back compat. This should eventually be removed.
+ vim.api.nvim_command("doautocmd <nomodeline> User LspDiagnosticsChanged")
+end
+
+--- Clear diagnotics and diagnostic cache.
+---
+--- Diagnostic producers should prefer |vim.diagnostic.reset()|. However,
+--- this method signature is still used internally in some parts of the LSP
+--- implementation so it's simply marked @private rather than @deprecated.
+---
+---@param client_id number
+---@param buffer_client_map table map of buffers to active clients
+---@private
+function M.reset(client_id, buffer_client_map)
+ buffer_client_map = vim.deepcopy(buffer_client_map)
+ vim.schedule(function()
+ for bufnr, client_ids in pairs(buffer_client_map) do
+ if client_ids[client_id] then
+ local namespace = M.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
+ end
+ end
+ end)
end
--- }}}
--- Diagnostic Retrieval {{{
+-- Deprecated Functions {{{
--- Get all diagnostics for clients
---
+---@deprecated Prefer |vim.diagnostic.get()|
+---
---@param client_id number Restrict included diagnostics to the client
--- If nil, diagnostics of all clients are included.
---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
function M.get_all(client_id)
- local diagnostics_by_bufnr = {}
- for bufnr, buf_diagnostics in pairs(diagnostic_cache) do
- diagnostics_by_bufnr[bufnr] = {}
- for cid, client_diagnostics in pairs(buf_diagnostics) do
- if client_id == nil or cid == client_id then
- vim.list_extend(diagnostics_by_bufnr[bufnr], client_diagnostics)
- end
- end
+ local result = {}
+ local namespace
+ if client_id then
+ namespace = M.get_namespace(client_id)
+ end
+ for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
+ local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, {namespace = namespace}))
+ result[bufnr] = diagnostics
end
- return diagnostics_by_bufnr
+ return result
end
--- Return associated diagnostics for bufnr
---
+---@deprecated Prefer |vim.diagnostic.get()|
+---
---@param bufnr number
---@param client_id number|nil If nil, then return all of the diagnostics.
--- Else, return just the diagnostics associated with the client_id.
---@param predicate function|nil Optional function for filtering diagnostics
function M.get(bufnr, client_id, predicate)
+ predicate = predicate or function() return true end
if client_id == nil then
local all_diagnostics = {}
- for iter_client_id, _ in pairs(diagnostic_cache[bufnr]) do
- local iter_diagnostics = M.get(bufnr, iter_client_id, predicate)
-
+ vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _)
+ local iter_diagnostics = vim.tbl_filter(predicate, M.get(bufnr, iter_client_id))
for _, diagnostic in ipairs(iter_diagnostics) do
table.insert(all_diagnostics, diagnostic)
end
- end
-
- return all_diagnostics
+ end)
+ return diagnostic_vim_to_lsp(all_diagnostics)
end
- predicate = predicate or function(_) return true end
- local client_diagnostics = {}
- for _, diagnostic in ipairs(diagnostic_cache[bufnr][client_id] or {}) do
- if predicate(diagnostic) then
- table.insert(client_diagnostics, diagnostic)
- end
- end
- return client_diagnostics
+ local namespace = M.get_namespace(client_id)
+ return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, {namespace=namespace})))
end
--- Get the diagnostics by line
---
+--- Marked private as this is used internally by the LSP subsystem, but
+--- most users should instead prefer |vim.diagnostic.get()|.
+---
---@param bufnr number|nil The buffer number
---@param line_nr number|nil The line number
---@param opts table|nil Configuration keys
@@ -398,216 +286,134 @@ end
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---@param client_id|nil number the client id
---@return table Table with map of line number to list of diagnostics.
--- Structured: { [1] = {...}, [5] = {.... } }
+--- Structured: { [1] = {...}, [5] = {.... } }
+---@private
function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
opts = opts or {}
-
- bufnr = bufnr or vim.api.nvim_get_current_buf()
- line_nr = line_nr or vim.api.nvim_win_get_cursor(0)[1] - 1
-
- local client_get_diags = function(iter_client_id)
- return (diagnostic_cache_lines[bufnr][iter_client_id] or {})[line_nr] or {}
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end
- local line_diagnostics
- if client_id == nil then
- line_diagnostics = {}
- for iter_client_id, _ in pairs(diagnostic_cache_lines[bufnr]) do
- for _, diagnostic in ipairs(client_get_diags(iter_client_id)) do
- table.insert(line_diagnostics, diagnostic)
- end
- end
- else
- line_diagnostics = vim.deepcopy(client_get_diags(client_id))
+ if client_id then
+ opts.namespace = M.get_namespace(client_id)
end
- if opts.severity then
- line_diagnostics = filter_to_severity_limit(opts.severity, line_diagnostics)
- elseif opts.severity_limit then
- line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics)
+ if not line_nr then
+ line_nr = vim.api.nvim_win_get_cursor(0)[1] - 1
end
- table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end)
+ opts.lnum = line_nr
- return line_diagnostics
+ return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts))
end
--- Get the counts for a particular severity
---
---- Useful for showing diagnostic counts in statusline. eg:
----
---- <pre>
---- function! LspStatus() abort
---- let sl = ''
---- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
---- let sl.='%#MyStatuslineLSP#E:'
---- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Error]])")}'
---- let sl.='%#MyStatuslineLSP# W:'
---- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Warning]])")}'
---- else
---- let sl.='%#MyStatuslineLSPErrors#off'
---- endif
---- return sl
---- endfunction
---- autocmd BufWinEnter * let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
---- </pre>
+---@deprecated Prefer |vim.diagnostic.get_count()|
---
---@param bufnr number The buffer number
---@param severity DiagnosticSeverity
---@param client_id number the client id
function M.get_count(bufnr, severity, client_id)
- if client_id == nil then
- local total = 0
- for iter_client_id, _ in pairs(diagnostic_cache_counts[bufnr]) do
- total = total + M.get_count(bufnr, severity, iter_client_id)
- end
-
- return total
- end
-
- return (diagnostic_cache_counts[bufnr][client_id] or {})[DiagnosticSeverity[severity]] or 0
-end
-
-
--- }}}
--- Diagnostic Movements {{{
-
---- Helper function to find the next diagnostic relative to a position
----@return table the next diagnostic if found
-local _next_diagnostic = function(position, search_forward, bufnr, opts, client_id)
- position[1] = position[1] - 1
- bufnr = bufnr or vim.api.nvim_get_current_buf()
- local wrap = if_nil(opts.wrap, true)
- local line_count = vim.api.nvim_buf_line_count(bufnr)
- for i = 0, line_count do
- local offset = i * (search_forward and 1 or -1)
- local line_nr = position[1] + offset
- if line_nr < 0 or line_nr >= line_count then
- if not wrap then
- return
- end
- line_nr = (line_nr + line_count) % line_count
- end
- local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
- if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
- local sort_diagnostics, is_next
- if search_forward then
- sort_diagnostics = function(a, b) return a.range.start.character < b.range.start.character end
- is_next = function(diagnostic) return diagnostic.range.start.character > position[2] end
- else
- sort_diagnostics = function(a, b) return a.range.start.character > b.range.start.character end
- is_next = function(diagnostic) return diagnostic.range.start.character < position[2] end
- end
- table.sort(line_diagnostics, sort_diagnostics)
- if i == 0 then
- for _, v in pairs(line_diagnostics) do
- if is_next(v) then
- return v
- end
- end
- else
- return line_diagnostics[1]
- end
- end
- end
-end
-
----@private
---- Helper function to return a position from a diagnostic
----
----@return table {row, col}
-local function _diagnostic_pos(opts, diagnostic)
- opts = opts or {}
-
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
- local bufnr = vim.api.nvim_win_get_buf(win_id)
-
- if not diagnostic then return false end
-
- return to_position(diagnostic.range.start, bufnr)
-end
-
----@private
--- Move to the diagnostic position
-local function _diagnostic_move_pos(name, opts, pos)
- opts = opts or {}
-
- local enable_popup = if_nil(opts.enable_popup, true)
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
-
- if not pos then
- print(string.format("%s: No more valid diagnostics to move to.", name))
- return
+ severity = severity_lsp_to_vim(severity)
+ local opts = { severity = severity }
+ if client_id ~= nil then
+ opts.namespace = M.get_namespace(client_id)
end
- vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]})
-
- if enable_popup then
- -- This is a bit weird... I'm surprised that we need to wait til the next tick to do this.
- vim.schedule(function()
- M.show_position_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id))
- end)
- end
+ return #vim.diagnostic.get(bufnr, opts)
end
--- Get the previous diagnostic closest to the cursor_position
---
+---@deprecated Prefer |vim.diagnostic.get_prev()|
+---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic
function M.get_prev(opts)
- opts = opts or {}
-
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
- local bufnr = vim.api.nvim_win_get_buf(win_id)
- local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
-
- return _next_diagnostic(cursor_position, false, bufnr, opts, opts.client_id)
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return diagnostic_vim_to_lsp({vim.diagnostic.get_prev(opts)})[1]
end
--- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
+---
+---@deprecated Prefer |vim.diagnostic.get_prev_pos()|
+---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic position
function M.get_prev_pos(opts)
- return _diagnostic_pos(
- opts,
- M.get_prev(opts)
- )
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return vim.diagnostic.get_prev_pos(opts)
end
--- Move to the previous diagnostic
+---
+---@deprecated Prefer |vim.diagnostic.goto_prev()|
+---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
function M.goto_prev(opts)
- return _diagnostic_move_pos(
- "DiagnosticPrevious",
- opts,
- M.get_prev_pos(opts)
- )
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return vim.diagnostic.goto_prev(opts)
end
--- Get the next diagnostic closest to the cursor_position
+---
+---@deprecated Prefer |vim.diagnostic.get_next()|
+---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic
function M.get_next(opts)
- opts = opts or {}
-
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
- local bufnr = vim.api.nvim_win_get_buf(win_id)
- local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
-
- return _next_diagnostic(cursor_position, true, bufnr, opts, opts.client_id)
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return diagnostic_vim_to_lsp({vim.diagnostic.get_next(opts)})[1]
end
--- Return the pos, {row, col}, for the next diagnostic in the current buffer.
+---
+---@deprecated Prefer |vim.diagnostic.get_next_pos()|
+---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic position
function M.get_next_pos(opts)
- return _diagnostic_pos(
- opts,
- M.get_next(opts)
- )
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return vim.diagnostic.get_next_pos(opts)
end
--- Move to the next diagnostic
+---
+---@deprecated Prefer |vim.diagnostic.goto_next()|
+---
---@param opts table|nil Configuration table. Keys:
--- - {client_id}: (number)
--- - If nil, will consider all clients attached to buffer.
@@ -626,17 +432,20 @@ end
--- - {win_id}: (number, default 0)
--- - Window ID
function M.goto_next(opts)
- return _diagnostic_move_pos(
- "DiagnosticNext",
- opts,
- M.get_next_pos(opts)
- )
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ end
+ return vim.diagnostic.goto_next(opts)
end
--- }}}
--- Diagnostic Setters {{{
--- Set signs for given diagnostics
---
+---@deprecated Prefer |vim.diagnostic._set_signs()|
+---
--- Sign characters can be customized with the following commands:
---
--- <pre>
@@ -654,35 +463,12 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts)
- opts = opts or {}
- sign_ns = sign_ns or M._get_sign_namespace(client_id)
-
- if not diagnostics then
- diagnostics = diagnostic_cache[bufnr][client_id]
- end
-
- if not diagnostics then
- return
- end
-
- bufnr = get_bufnr(bufnr)
- diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics)
-
- local ok = true
- for _, diagnostic in ipairs(diagnostics) do
-
- ok = ok and pcall(vim.fn.sign_place,
- 0,
- sign_ns,
- sign_highlight_map[diagnostic.severity],
- bufnr,
- {
- priority = opts.priority,
- lnum = diagnostic.range.start.line + 1
- }
- )
+ local namespace = M.get_namespace(client_id)
+ if opts and not opts.severity and opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end
+ local ok = vim.diagnostic._set_signs(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
if not ok then
log.debug("Failed to place signs:", diagnostics)
end
@@ -690,6 +476,8 @@ end
--- Set underline for given diagnostics
---
+---@deprecated Prefer |vim.diagnostic._set_underline()|
+---
--- Underline highlights can be customized by changing the following |:highlight| groups.
---
--- <pre>
@@ -707,34 +495,17 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_underline(diagnostics, bufnr, client_id, diagnostic_ns, opts)
- opts = opts or {}
-
- diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id)
- diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics)
-
- for _, diagnostic in ipairs(diagnostics) do
- local start = diagnostic.range["start"]
- local finish = diagnostic.range["end"]
- local higroup = underline_highlight_map[diagnostic.severity]
-
- if higroup == nil then
- -- Default to error if we don't have a highlight associated
- higroup = underline_highlight_map[DiagnosticSeverity.Error]
- end
-
- highlight.range(
- bufnr,
- diagnostic_ns,
- higroup,
- to_position(start, bufnr),
- to_position(finish, bufnr)
- )
+ local namespace = M.get_namespace(client_id)
+ if opts and not opts.severity and opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end
+ return vim.diagnostic._set_underline(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
end
--- Virtual Text {{{
--- Set virtual text given diagnostics
---
+---@deprecated Prefer |vim.diagnostic._set_virtual_text()|
+---
--- Virtual text highlights can be customized by changing the following |:highlight| groups.
---
--- <pre>
@@ -754,35 +525,17 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts)
- opts = opts or {}
-
- client_id = get_client_id(client_id)
- diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id)
-
- local buffer_line_diagnostics
- if diagnostics then
- buffer_line_diagnostics = _diagnostic_lines(diagnostics)
- else
- buffer_line_diagnostics = diagnostic_cache_lines[bufnr][client_id]
- end
-
- if not buffer_line_diagnostics then
- return nil
- end
-
- for line, line_diagnostics in pairs(buffer_line_diagnostics) do
- line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics)
- local virt_texts = M.get_virtual_text_chunks_for_line(bufnr, line, line_diagnostics, opts)
-
- if virt_texts then
- api.nvim_buf_set_extmark(bufnr, diagnostic_ns, line, 0, {
- virt_text = virt_texts,
- })
- end
+ local namespace = M.get_namespace(client_id)
+ if opts and not opts.severity and opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end
+ return vim.diagnostic._set_virtual_text(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
end
--- Default function to get text chunks to display using |nvim_buf_set_extmark()|.
+---
+---@deprecated Prefer |vim.diagnostic.get_virt_text_chunks()|
+---
---@param bufnr number The buffer to display the virtual text in
---@param line number The line number to display the virtual text on
---@param line_diags Diagnostic[] The diagnostics associated with the line
@@ -790,399 +543,56 @@ end
---@return an array of [text, hl_group] arrays. This can be passed directly to
--- the {virt_text} option of |nvim_buf_set_extmark()|.
function M.get_virtual_text_chunks_for_line(bufnr, line, line_diags, opts)
- assert(bufnr or line)
-
- if #line_diags == 0 then
- return nil
- end
-
- opts = opts or {}
- local prefix = opts.prefix or "■"
- local spacing = opts.spacing or 4
-
- -- Create a little more space between virtual text and contents
- local virt_texts = {{string.rep(" ", spacing)}}
-
- for i = 1, #line_diags - 1 do
- table.insert(virt_texts, {prefix, virtual_text_highlight_map[line_diags[i].severity]})
- end
- local last = line_diags[#line_diags]
-
- -- TODO(tjdevries): Allow different servers to be shown first somehow?
- -- TODO(tjdevries): Display server name associated with these?
- if last.message then
- table.insert(
- virt_texts,
- {
- string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")),
- virtual_text_highlight_map[last.severity]
- }
- )
-
- return virt_texts
- end
-end
--- }}}
--- }}}
--- Diagnostic Clear {{{
---- Clears the currently displayed diagnostics
----@param bufnr number The buffer number
----@param client_id number the client id
----@param diagnostic_ns number|nil Associated diagnostic namespace
----@param sign_ns number|nil Associated sign namespace
-function M.clear(bufnr, client_id, diagnostic_ns, sign_ns)
- bufnr = get_bufnr(bufnr)
- if client_id == nil then
- return vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _)
- return M.clear(bufnr, iter_client_id)
- end)
- end
-
- diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id)
- sign_ns = sign_ns or M._get_sign_namespace(client_id)
- diagnostic_cache_extmarks[bufnr][client_id] = {}
-
- assert(bufnr, "bufnr is required")
- assert(diagnostic_ns, "Need diagnostic_ns, got nil")
- assert(sign_ns, string.format("Need sign_ns, got nil %s", sign_ns))
-
- -- clear sign group
- vim.fn.sign_unplace(sign_ns, {buffer=bufnr})
-
- -- clear virtual text namespace
- api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1)
-end
--- }}}
--- Diagnostic Insert Leave Handler {{{
-
---- Callback scheduled for after leaving insert mode
----
---- Used to handle
----@private
-function M._execute_scheduled_display(bufnr, client_id)
- local args = _bufs_waiting_to_update[bufnr][client_id]
- if not args then
- return
- end
-
- -- Clear the args so we don't display unnecessarily.
- _bufs_waiting_to_update[bufnr][client_id] = nil
-
- M.display(nil, bufnr, client_id, args)
-end
-
-local registered = {}
-
-local make_augroup_key = function(bufnr, client_id)
- return string.format("LspDiagnosticInsertLeave:%s:%s", bufnr, client_id)
+ return vim.diagnostic.get_virt_text_chunks(diagnostic_lsp_to_vim(line_diags, bufnr), opts)
end
---- Table of autocmd events to fire the update for displaying new diagnostic information
-M.insert_leave_auto_cmds = { "InsertLeave", "CursorHoldI" }
-
---- Used to schedule diagnostic updates upon leaving insert mode.
+--- Open a floating window with the diagnostics from {position}
---
---- For parameter description, see |M.display()|
-function M._schedule_display(bufnr, client_id, args)
- _bufs_waiting_to_update[bufnr][client_id] = args
-
- local key = make_augroup_key(bufnr, client_id)
- if not registered[key] then
- vim.cmd(string.format("augroup %s", key))
- vim.cmd(" au!")
- vim.cmd(
- string.format(
- [[autocmd %s <buffer=%s> :lua vim.lsp.diagnostic._execute_scheduled_display(%s, %s)]],
- table.concat(M.insert_leave_auto_cmds, ","),
- bufnr,
- bufnr,
- client_id
- )
- )
- vim.cmd("augroup END")
-
- registered[key] = true
- end
-end
-
-
---- Used in tandem with
+---@deprecated Prefer |vim.diagnostic.show_position_diagnostics()|
---
---- For parameter description, see |M.display()|
-function M._clear_scheduled_display(bufnr, client_id)
- local key = make_augroup_key(bufnr, client_id)
-
- if registered[key] then
- vim.cmd(string.format("augroup %s", key))
- vim.cmd(" au!")
- vim.cmd("augroup END")
-
- registered[key] = nil
- end
-end
--- }}}
-
--- Diagnostic Private Highlight Utilies {{{
---- Get the severity highlight name
----@private
-function M._get_severity_highlight_name(severity)
- return virtual_text_highlight_map[severity]
-end
-
---- Get floating severity highlight name
----@private
-function M._get_floating_severity_highlight_name(severity)
- return floating_highlight_map[severity]
-end
-
---- This should be called to update the highlights for the LSP client.
-function M._define_default_signs_and_highlights()
- ---@private
- local function define_default_sign(name, properties)
- if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
- vim.fn.sign_define(name, properties)
+---@param opts table|nil Configuration keys
+--- - severity: (DiagnosticSeverity, default nil)
+--- - Only return diagnostics with this severity. Overrides severity_limit
+--- - severity_limit: (DiagnosticSeverity, default nil)
+--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
+--- - all opts for |show_diagnostics()| can be used here
+---@param buf_nr number|nil The buffer number
+---@param position table|nil The (0,0)-indexed position
+---@return table {popup_bufnr, win_id}
+function M.show_position_diagnostics(opts, buf_nr, position)
+ if opts then
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end
end
-
- -- Initialize default diagnostic highlights
- for severity, hi_info in pairs(diagnostic_severities) do
- local default_highlight_name = default_highlight_map[severity]
- highlight.create(default_highlight_name, hi_info, true)
-
- -- Default link all corresponding highlights to the default highlight
- highlight.link(virtual_text_highlight_map[severity], default_highlight_name, false)
- highlight.link(floating_highlight_map[severity], default_highlight_name, false)
- highlight.link(sign_highlight_map[severity], default_highlight_name, false)
- end
-
- -- Create all signs
- for severity, sign_hl_name in pairs(sign_highlight_map) do
- local severity_name = DiagnosticSeverity[severity]
-
- define_default_sign(sign_hl_name, {
- text = (severity_name or 'U'):sub(1, 1),
- texthl = sign_hl_name,
- linehl = '',
- numhl = '',
- })
- end
-
- -- Initialize Underline highlights
- for severity, underline_highlight_name in pairs(underline_highlight_map) do
- highlight.create(underline_highlight_name, {
- cterm = 'underline',
- gui = 'underline',
- guisp = diagnostic_severities[severity].guifg
- }, true)
- end
+ return vim.diagnostic.show_position_diagnostics(opts, buf_nr, position)
end
--- }}}
--- Diagnostic Display {{{
---- |lsp-handler| for the method "textDocument/publishDiagnostics"
+--- Open a floating window with the diagnostics from {line_nr}
---
----@note Each of the configuration options accepts:
---- - `false`: Disable this feature
---- - `true`: Enable this feature, use default settings.
---- - `table`: Enable this feature, use overrides.
---- - `function`: Function with signature (bufnr, client_id) that returns any of the above.
---- <pre>
---- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
---- vim.lsp.diagnostic.on_publish_diagnostics, {
---- -- Enable underline, use default values
---- underline = true,
---- -- Enable virtual text, override spacing to 4
---- virtual_text = {
---- spacing = 4,
---- },
---- -- Use a function to dynamically turn signs off
---- -- and on, using buffer local variables
---- signs = function(bufnr, client_id)
---- return vim.bo[bufnr].show_signs == false
---- end,
---- -- Disable a feature
---- update_in_insert = false,
---- }
---- )
---- </pre>
+---@deprecated Prefer |vim.diagnostic.show_line_diagnostics()|
---
----@param config table Configuration table.
---- - underline: (default=true)
---- - Apply underlines to diagnostics.
---- - See |vim.lsp.diagnostic.set_underline()|
---- - virtual_text: (default=true)
---- - Apply virtual text to line endings.
---- - See |vim.lsp.diagnostic.set_virtual_text()|
---- - signs: (default=true)
---- - Apply signs for diagnostics.
---- - See |vim.lsp.diagnostic.set_signs()|
---- - update_in_insert: (default=false)
---- - Update diagnostics in InsertMode or wait until InsertLeave
---- - severity_sort: (default=false)
---- - Sort diagnostics (and thus signs and virtual text)
-function M.on_publish_diagnostics(_, result, ctx, config)
- local client_id = ctx.client_id
- local uri = result.uri
- local bufnr = vim.uri_to_bufnr(uri)
-
- if not bufnr then
- return
- end
-
- local diagnostics = result.diagnostics
-
- if config and if_nil(config.severity_sort, false) then
- table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
- end
-
- -- Always save the diagnostics, even if the buf is not loaded.
- -- Language servers may report compile or build errors via diagnostics
- -- Users should be able to find these, even if they're in files which
- -- are not loaded.
- M.save(diagnostics, bufnr, client_id)
-
- -- Unloaded buffers should not handle diagnostics.
- -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
- -- This should trigger another publish of the diagnostics.
- --
- -- In particular, this stops a ton of spam when first starting a server for current
- -- unloaded buffers.
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
-
- M.display(diagnostics, bufnr, client_id, config)
-end
-
--- restores the extmarks set by M.display
----@param last number last line that was changed
----@private
-local function restore_extmarks(bufnr, last)
- for client_id, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
- local ns = M._get_diagnostic_namespace(client_id)
- local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
- local found = {}
- for _, extmark in ipairs(extmarks_current) do
- -- nvim_buf_set_lines will move any extmark to the line after the last
- -- nvim_buf_set_text will move any extmark to the last line
- if extmark[2] ~= last + 1 then
- found[extmark[1]] = true
- end
- end
- for _, extmark in ipairs(extmarks) do
- if not found[extmark[1]] then
- local opts = extmark[4]
- opts.id = extmark[1]
- -- HACK: end_row should be end_line
- if opts.end_row then
- opts.end_line = opts.end_row
- opts.end_row = nil
- end
- pcall(api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
- end
- end
- end
-end
-
--- caches the extmarks set by M.display
----@private
-local function save_extmarks(bufnr, client_id)
- bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
- if not diagnostic_attached_buffers[bufnr] then
- api.nvim_buf_attach(bufnr, false, {
- on_lines = function(_, _, _, _, _, last)
- restore_extmarks(bufnr, last - 1)
- end,
- on_detach = function()
- diagnostic_cache_extmarks[bufnr] = nil
- end})
- diagnostic_attached_buffers[bufnr] = true
- end
- local ns = M._get_diagnostic_namespace(client_id)
- diagnostic_cache_extmarks[bufnr][client_id] = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
-end
-
----@private
---- Display diagnostics for the buffer, given a configuration.
-function M.display(diagnostics, bufnr, client_id, config)
- if diagnostic_disabled[bufnr][client_id] then
- return
- end
-
- config = vim.lsp._with_extend('vim.lsp.diagnostic.on_publish_diagnostics', {
- signs = true,
- underline = true,
- virtual_text = true,
- update_in_insert = false,
- severity_sort = false,
- }, config)
-
- -- TODO(tjdevries): Consider how we can make this a "standardized" kind of thing for |lsp-handlers|.
- -- It seems like we would probably want to do this more often as we expose more of them.
- -- It provides a very nice functional interface for people to override configuration.
- local resolve_optional_value = function(option)
- local enabled_val = {}
-
- if not option then
- return false
- elseif option == true then
- return enabled_val
- elseif type(option) == 'function' then
- local val = option(bufnr, client_id)
- if val == true then
- return enabled_val
- else
- return val
- end
- elseif type(option) == 'table' then
- return option
- else
- error("Unexpected option type: " .. vim.inspect(option))
- end
- end
-
- if resolve_optional_value(config.update_in_insert) then
- M._clear_scheduled_display(bufnr, client_id)
- else
- local mode = vim.api.nvim_get_mode()
-
- if string.sub(mode.mode, 1, 1) == 'i' then
- M._schedule_display(bufnr, client_id, config)
- return
- end
- end
-
- M.clear(bufnr, client_id)
-
- diagnostics = diagnostics or M.get(bufnr, client_id)
-
- vim.api.nvim_command("doautocmd <nomodeline> User LspDiagnosticsChanged")
-
- if not diagnostics or vim.tbl_isempty(diagnostics) then
- return
- end
-
- local underline_opts = resolve_optional_value(config.underline)
- if underline_opts then
- M.set_underline(diagnostics, bufnr, client_id, nil, underline_opts)
- end
-
- local virtual_text_opts = resolve_optional_value(config.virtual_text)
- if virtual_text_opts then
- M.set_virtual_text(diagnostics, bufnr, client_id, nil, virtual_text_opts)
- end
-
- local signs_opts = resolve_optional_value(config.signs)
- if signs_opts then
- M.set_signs(diagnostics, bufnr, client_id, nil, signs_opts)
+---@param opts table Configuration table
+--- - all opts for |vim.lsp.diagnostic.get_line_diagnostics()| and
+--- |show_diagnostics()| can be used here
+---@param buf_nr number|nil The buffer number
+---@param line_nr number|nil The line number
+---@param client_id number|nil the client id
+---@return table {popup_bufnr, win_id}
+function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
+ if client_id then
+ opts = opts or {}
+ opts.namespace = M.get_namespace(client_id)
end
-
- -- cache extmarks
- save_extmarks(bufnr, client_id)
+ return vim.diagnostic.show_line_diagnostics(opts, buf_nr, line_nr)
end
--- Redraw diagnostics for the given buffer and client
---
+---@deprecated Prefer |vim.diagnostic.redraw()|
+---
--- This calls the "textDocument/publishDiagnostics" handler manually using
--- the cached diagnostics already received from the server. This can be useful
--- for redrawing diagnostics after making changes in diagnostics
@@ -1200,183 +610,14 @@ function M.redraw(bufnr, client_id)
end)
end
- -- We need to invoke the publishDiagnostics handler directly instead of just
- -- calling M.display so that we can preserve any custom configuration options
- -- the user may have set with vim.lsp.with.
- vim.lsp.handlers["textDocument/publishDiagnostics"](
- nil,
- {
- uri = vim.uri_from_bufnr(bufnr),
- diagnostics = M.get(bufnr, client_id),
- },
- {
- method = "textDocument/publishDiagnostics",
- client_id = client_id,
- bufnr = bufnr,
- }
- )
- end
-
-
----@private
---- Open a floating window with the provided diagnostics
----
---- The floating window can be customized with the following highlight groups:
---- <pre>
---- LspDiagnosticsFloatingError
---- LspDiagnosticsFloatingWarning
---- LspDiagnosticsFloatingInformation
---- LspDiagnosticsFloatingHint
---- </pre>
----@param opts table Configuration table
---- - show_header (boolean, default true): Show "Diagnostics:" header
---- - all opts for |vim.lsp.util.open_floating_preview()| can be used here
----@param diagnostics table: The diagnostics to display
----@return table {popup_bufnr, win_id}
-local function show_diagnostics(opts, diagnostics)
- if vim.tbl_isempty(diagnostics) then return end
- local lines = {}
- local highlights = {}
- local show_header = if_nil(opts.show_header, true)
- if show_header then
- table.insert(lines, "Diagnostics:")
- table.insert(highlights, {0, "Bold"})
- end
-
- for i, diagnostic in ipairs(diagnostics) do
- local prefix = string.format("%d. ", i)
- local hiname = M._get_floating_severity_highlight_name(diagnostic.severity)
- assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
-
- local message_lines = vim.split(diagnostic.message, '\n', true)
- table.insert(lines, prefix..message_lines[1])
- table.insert(highlights, {#prefix, hiname})
- for j = 2, #message_lines do
- table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
- table.insert(highlights, {0, hiname})
- end
- end
-
- local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
- for i, hi in ipairs(highlights) do
- local prefixlen, hiname = unpack(hi)
- -- Start highlight after the prefix
- api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1)
- end
-
- return popup_bufnr, winnr
-end
-
-
--- }}}
--- Diagnostic User Functions {{{
-
---- Open a floating window with the diagnostics from {position}
----@param opts table|nil Configuration keys
---- - severity: (DiagnosticSeverity, default nil)
---- - Only return diagnostics with this severity. Overrides severity_limit
---- - severity_limit: (DiagnosticSeverity, default nil)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---- - all opts for |show_diagnostics()| can be used here
----@param buf_nr number|nil The buffer number
----@param position table|nil The (0,0)-indexed position
----@return table {popup_bufnr, win_id}
-function M.show_position_diagnostics(opts, buf_nr, position)
- opts = opts or {}
- opts.focus_id = "position_diagnostics"
- buf_nr = buf_nr or vim.api.nvim_get_current_buf()
- if not position then
- local curr_position = vim.api.nvim_win_get_cursor(0)
- curr_position[1] = curr_position[1] - 1
- position = curr_position
- end
- local match_position_predicate = function(diag)
- return position[1] == diag.range['start'].line and
- position[2] >= diag.range['start'].character and
- (position[2] <= diag.range['end'].character or position[1] < diag.range['end'].line)
- end
- local position_diagnostics = M.get(buf_nr, nil, match_position_predicate)
- if opts.severity then
- position_diagnostics = filter_to_severity_limit(opts.severity, position_diagnostics)
- elseif opts.severity_limit then
- position_diagnostics = filter_by_severity_limit(opts.severity_limit, position_diagnostics)
- end
- table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end)
- return show_diagnostics(opts, position_diagnostics)
-end
-
---- Open a floating window with the diagnostics from {line_nr}
-
----@param opts table Configuration table
---- - all opts for |vim.lsp.diagnostic.get_line_diagnostics()| and
---- |show_diagnostics()| can be used here
----@param buf_nr number|nil The buffer number
----@param line_nr number|nil The line number
----@param client_id number|nil the client id
----@return table {popup_bufnr, win_id}
-function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
- opts = opts or {}
- opts.focus_id = "line_diagnostics"
- line_nr = line_nr or (vim.api.nvim_win_get_cursor(0)[1] - 1)
- local line_diagnostics = M.get_line_diagnostics(buf_nr, line_nr, opts, client_id)
- return show_diagnostics(opts, line_diagnostics)
-end
-
---- Clear diagnotics and diagnostic cache
----
---- Handles saving diagnostics from multiple clients in the same buffer.
----@param client_id number
----@param buffer_client_map table map of buffers to active clients
-function M.reset(client_id, buffer_client_map)
- buffer_client_map = vim.deepcopy(buffer_client_map)
- vim.schedule(function()
- for bufnr, client_ids in pairs(buffer_client_map) do
- if client_ids[client_id] then
- clear_diagnostic_cache(bufnr, client_id)
- M.clear(bufnr, client_id)
- end
- end
- end)
-end
-
----@private
---- Gets diagnostics, converts them to quickfix/location list items, and applies the item_handler callback to the items.
----@param item_handler function Callback to apply to the diagnostic items
----@param command string|nil Command to execute after applying the item_handler
----@param opts table|nil Configuration table. Keys:
---- - {client_id}: (number)
---- - If nil, will consider all clients attached to buffer.
---- - {severity}: (DiagnosticSeverity)
---- - Exclusive severity to consider. Overrides {severity_limit}
---- - {severity_limit}: (DiagnosticSeverity)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---- - {workspace}: (boolean, default false)
---- - Set the list with workspace diagnostics
-local function apply_to_diagnostic_items(item_handler, command, opts)
- opts = opts or {}
- local current_bufnr = api.nvim_get_current_buf()
- local diags = opts.workspace and M.get_all(opts.client_id) or {
- [current_bufnr] = M.get(current_bufnr, opts.client_id)
- }
- local predicate = function(d)
- local severity = to_severity(opts.severity)
- if severity then
- return d.severity == severity
- end
- local severity_limit = to_severity(opts.severity_limit)
- if severity_limit then
- return d.severity <= severity_limit
- end
- return true
- end
- local items = util.diagnostics_to_items(diags, predicate)
- item_handler(items)
- if command then
- vim.cmd(command)
- end
+ local namespace = M.get_namespace(client_id)
+ return vim.diagnostic.show(namespace, bufnr)
end
--- Sets the quickfix list
+---
+---@deprecated Prefer |vim.diagnostic.setqflist()|
+---
---@param opts table|nil Configuration table. Keys:
--- - {open}: (boolean, default true)
--- - Open quickfix list after set
@@ -1390,13 +631,24 @@ end
--- - Set the list with workspace diagnostics
function M.set_qflist(opts)
opts = opts or {}
- opts.workspace = if_nil(opts.workspace, true)
- local open_qflist = if_nil(opts.open, true)
- local command = open_qflist and [[copen]] or nil
- apply_to_diagnostic_items(util.set_qflist, command, opts)
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ if opts.client_id then
+ opts.client_id = nil
+ opts.namespace = M.get_namespace(opts.client_id)
+ end
+ local workspace = vim.F.if_nil(opts.workspace, true)
+ opts.bufnr = not workspace and 0
+ return vim.diagnostic.setqflist(opts)
end
--- Sets the location list
+---
+---@deprecated Prefer |vim.diagnostic.setloclist()|
+---
---@param opts table|nil Configuration table. Keys:
--- - {open}: (boolean, default true)
--- - Open loclist after set
@@ -1410,12 +662,24 @@ end
--- - Set the list with workspace diagnostics
function M.set_loclist(opts)
opts = opts or {}
- local open_loclist = if_nil(opts.open, true)
- local command = open_loclist and [[lopen]] or nil
- apply_to_diagnostic_items(util.set_loclist, command, opts)
+ if opts.severity then
+ opts.severity = severity_lsp_to_vim(opts.severity)
+ elseif opts.severity_limit then
+ opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ end
+ if opts.client_id then
+ opts.client_id = nil
+ opts.namespace = M.get_namespace(opts.client_id)
+ end
+ local workspace = vim.F.if_nil(opts.workspace, false)
+ opts.bufnr = not workspace and 0
+ return vim.diagnostic.setloclist(opts)
end
--- Disable diagnostics for the given buffer and client
+---
+---@deprecated Prefer |vim.diagnostic.disable()|
+---
---@param bufnr (optional, number): Buffer handle, defaults to current
---@param client_id (optional, number): Disable diagnostics for the given
--- client. The default is to disable diagnostics for all attached
@@ -1430,11 +694,15 @@ function M.disable(bufnr, client_id)
end)
end
- diagnostic_disabled[bufnr][client_id] = true
- M.clear(bufnr, client_id)
+ bufnr = get_bufnr(bufnr)
+ local namespace = M.get_namespace(client_id)
+ return vim.diagnostic.disable(bufnr, namespace)
end
--- Enable diagnostics for the given buffer and client
+---
+---@deprecated Prefer |vim.diagnostic.enable()|
+---
---@param bufnr (optional, number): Buffer handle, defaults to current
---@param client_id (optional, number): Enable diagnostics for the given
--- client. The default is to enable diagnostics for all attached
@@ -1446,14 +714,13 @@ function M.enable(bufnr, client_id)
end)
end
- if not diagnostic_disabled[bufnr][client_id] then
- return
- end
-
- diagnostic_disabled[bufnr][client_id] = nil
-
- M.redraw(bufnr, client_id)
+ bufnr = get_bufnr(bufnr)
+ local namespace = M.get_namespace(client_id)
+ return vim.diagnostic.enable(bufnr, namespace)
end
+
-- }}}
return M
+
+-- vim: fdm=marker
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 8fa6f6d024..97dd02efc2 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -210,10 +210,16 @@ local function response_to_list(map_result, entity)
else
config = config or {}
if config.loclist then
- util.set_loclist(map_result(result, ctx.bufnr))
+ vim.fn.setloclist(0, {}, ' ', {
+ title = 'Language Server';
+ items = map_result(result, ctx.bufnr);
+ })
api.nvim_command("lopen")
else
- util.set_qflist(map_result(result, ctx.bufnr))
+ vim.fn.setqflist({}, ' ', {
+ title = 'Language Server';
+ items = map_result(result, ctx.bufnr);
+ })
api.nvim_command("copen")
end
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 5a22a311e0..9a3ce185a0 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -31,16 +31,6 @@ local default_border = {
{" ", "NormalFloat"},
}
-
-local DiagnosticSeverity = protocol.DiagnosticSeverity
-local loclist_type_map = {
- [DiagnosticSeverity.Error] = 'E',
- [DiagnosticSeverity.Warning] = 'W',
- [DiagnosticSeverity.Information] = 'I',
- [DiagnosticSeverity.Hint] = 'I',
-}
-
-
---@private
--- Check the border given by opts or the default border for the additional
--- size it adds to a float.
@@ -1543,6 +1533,9 @@ end
--- Returns the items with the byte position calculated correctly and in sorted
--- order, for display in quickfix and location lists.
---
+--- The result can be passed to the {list} argument of |setqflist()| or
+--- |setloclist()|.
+---
---@param locations (table) list of `Location`s or `LocationLink`s
---@returns (table) list of items
function M.locations_to_items(locations)
@@ -1601,6 +1594,8 @@ end
--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
--- Defaults to current window.
---
+---@deprecated Use |setloclist()|
+---
---@param items (table) list of items
function M.set_loclist(items, win_id)
vim.fn.setloclist(win_id or 0, {}, ' ', {
@@ -1612,6 +1607,8 @@ end
--- Fills quickfix list with given list of items.
--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
---
+---@deprecated Use |setqflist()|
+---
---@param items (table) list of items
function M.set_qflist(items)
vim.fn.setqflist({}, ' ', {
@@ -1869,40 +1866,6 @@ function M.lookup_section(settings, section)
return settings
end
-
---- Convert diagnostics grouped by bufnr to a list of items for use in the
---- quickfix or location list.
----
----@param diagnostics_by_bufnr table bufnr -> Diagnostic[]
----@param predicate an optional function to filter the diagnostics.
---- If present, only diagnostic items matching will be included.
----@return table (A list of items)
-function M.diagnostics_to_items(diagnostics_by_bufnr, predicate)
- local items = {}
- for bufnr, diagnostics in pairs(diagnostics_by_bufnr or {}) do
- for _, d in pairs(diagnostics) do
- if not predicate or predicate(d) then
- table.insert(items, {
- bufnr = bufnr,
- lnum = d.range.start.line + 1,
- col = d.range.start.character + 1,
- text = d.message,
- type = loclist_type_map[d.severity or DiagnosticSeverity.Error] or 'E'
- })
- end
- end
- end
- table.sort(items, function(a, b)
- if a.bufnr == b.bufnr then
- return a.lnum < b.lnum
- else
- return a.bufnr < b.bufnr
- end
- end)
- return items
-end
-
-
M._get_line_byte_from_position = get_line_byte_from_position
M._warn_once = warn_once
diff --git a/runtime/plugin/diagnostic.vim b/runtime/plugin/diagnostic.vim
new file mode 100644
index 0000000000..569c63bdba
--- /dev/null
+++ b/runtime/plugin/diagnostic.vim
@@ -0,0 +1,48 @@
+" :help vim.diagnostic
+
+hi DiagnosticError ctermfg=1 guifg=Red
+hi DiagnosticWarn ctermfg=3 guifg=Orange
+hi DiagnosticInfo ctermfg=4 guifg=LightBlue
+hi DiagnosticHint ctermfg=7 guifg=LightGrey
+
+hi DiagnosticUnderlineError cterm=underline gui=underline guisp=Red
+hi DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange
+hi DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue
+hi DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey
+
+hi link DiagnosticVirtualTextError DiagnosticError
+hi link DiagnosticVirtualTextWarn DiagnosticWarn
+hi link DiagnosticVirtualTextInfo DiagnosticInfo
+hi link DiagnosticVirtualTextHint DiagnosticHint
+
+hi link DiagnosticFloatingError DiagnosticError
+hi link DiagnosticFloatingWarn DiagnosticWarn
+hi link DiagnosticFloatingInfo DiagnosticInfo
+hi link DiagnosticFloatingHint DiagnosticHint
+
+hi link DiagnosticSignError DiagnosticError
+hi link DiagnosticSignWarn DiagnosticWarn
+hi link DiagnosticSignInfo DiagnosticInfo
+hi link DiagnosticSignHint DiagnosticHint
+
+" Link LspDiagnostics for backward compatibility
+hi link LspDiagnosticsDefaultHint DiagnosticHint
+hi link LspDiagnosticsVirtualTextHint DiagnosticVirtualTextHint
+hi link LspDiagnosticsFloatingHint DiagnosticFloatingHint
+hi link LspDiagnosticsSignHint DiagnosticSignHint
+hi link LspDiagnosticsDefaultError DiagnosticError
+hi link LspDiagnosticsVirtualTextError DiagnosticVirtualTextError
+hi link LspDiagnosticsFloatingError DiagnosticFloatingError
+hi link LspDiagnosticsSignError DiagnosticSignError
+hi link LspDiagnosticsDefaultWarning DiagnosticWarn
+hi link LspDiagnosticsVirtualTextWarning DiagnosticVirtualTextWarn
+hi link LspDiagnosticsFloatingWarning DiagnosticFloatingWarn
+hi link LspDiagnosticsSignWarning DiagnosticSignWarn
+hi link LspDiagnosticsDefaultInformation DiagnosticInfo
+hi link LspDiagnosticsVirtualTextInformation DiagnosticVirtualTextInfo
+hi link LspDiagnosticsFloatingInformation DiagnosticFloatingInfo
+hi link LspDiagnosticsSignInformation DiagnosticSignInfo
+hi link LspDiagnosticsUnderlineError DiagnosticUnderlineError
+hi link LspDiagnosticsUnderlineWarning DiagnosticUnderlineWarn
+hi link LspDiagnosticsUnderlineInformation DiagnosticUnderlineInfo
+hi link LspDiagnosticsUnderlineHint DiagnosticUnderlineHint
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 320c44e860..68ee7bea0f 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -187,6 +187,23 @@ CONFIG = {
'module_override': {},
'append_only': [],
},
+ 'diagnostic': {
+ 'mode': 'lua',
+ 'filename': 'diagnostic.txt',
+ 'section_start_token': '*diagnostic-api*',
+ 'section_order': [
+ 'diagnostic.lua',
+ ],
+ 'files': os.path.join(base_dir, 'runtime/lua/vim/diagnostic.lua'),
+ 'file_patterns': '*.lua',
+ 'fn_name_prefix': '',
+ 'section_name': {'diagnostic.lua': 'diagnostic'},
+ 'section_fmt': lambda _: 'Lua module: vim.diagnostic',
+ 'helptag_fmt': lambda _: '*diagnostic-api*',
+ 'fn_helptag_fmt': lambda fstem, name: f'*vim.{fstem}.{name}()*',
+ 'module_override': {},
+ 'append_only': [],
+ },
'treesitter': {
'mode': 'lua',
'filename': 'treesitter.txt',
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index c6bbdee7ad..ba124c41ad 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -105,6 +105,9 @@ setmetatable(vim, {
elseif key == 'highlight' then
t.highlight = require('vim.highlight')
return t.highlight
+ elseif key == 'diagnostic' then
+ t.diagnostic = require('vim.diagnostic')
+ return t.diagnostic
end
end
})
diff --git a/test/README.md b/test/README.md
index 7a93c45f1c..37aa54c157 100644
--- a/test/README.md
+++ b/test/README.md
@@ -263,6 +263,8 @@ Number; !must be defined to function properly):
- `TEST_TIMEOUT` (FU) (I): specifies maximum time, in seconds, before the test
suite run is killed
+- `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects
+
- `NVIM_PROG`, `NVIM_PRG` (F) (S): override path to Neovim executable (default
to `build/bin/nvim`).
diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua
new file mode 100644
index 0000000000..9747a33b9e
--- /dev/null
+++ b/test/functional/lua/diagnostic_spec.lua
@@ -0,0 +1,810 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local command = helpers.command
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+local nvim = helpers.nvim
+
+describe('vim.diagnostic', function()
+ before_each(function()
+ clear()
+
+ exec_lua [[
+ require('vim.diagnostic')
+
+ function make_error(msg, x1, y1, x2, y2)
+ return {
+ lnum = x1,
+ col = y1,
+ end_lnum = x2,
+ end_col = y2,
+ message = msg,
+ severity = vim.diagnostic.severity.ERROR,
+ }
+ end
+
+ function make_warning(msg, x1, y1, x2, y2)
+ return {
+ lnum = x1,
+ col = y1,
+ end_lnum = x2,
+ end_col = y2,
+ message = msg,
+ severity = vim.diagnostic.severity.WARN,
+ }
+ end
+
+ function make_information(msg, x1, y1, x2, y2)
+ return {
+ lnum = x1,
+ col = y1,
+ end_lnum = x2,
+ end_col = y2,
+ message = msg,
+ severity = vim.diagnostic.severity.INFO,
+ }
+ end
+
+ function count_diagnostics(bufnr, severity, namespace)
+ return #vim.diagnostic.get(bufnr, {severity = severity, namespace = namespace})
+ end
+
+ function count_extmarks(bufnr, namespace)
+ return #vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, {})
+ end
+ ]]
+
+ exec_lua([[
+ diagnostic_ns = vim.api.nvim_create_namespace("diagnostic_spec")
+ other_ns = vim.api.nvim_create_namespace("other_namespace")
+ diagnostic_bufnr = vim.api.nvim_create_buf(true, false)
+ local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"}
+ vim.fn.bufload(diagnostic_bufnr)
+ vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
+ return diagnostic_bufnr
+ ]])
+ end)
+
+ after_each(function()
+ clear()
+ end)
+
+ it('creates highlight groups', function()
+ command('runtime plugin/diagnostic.vim')
+ eq({
+ 'DiagnosticError',
+ 'DiagnosticFloatingError',
+ 'DiagnosticFloatingHint',
+ 'DiagnosticFloatingInfo',
+ 'DiagnosticFloatingWarn',
+ 'DiagnosticHint',
+ 'DiagnosticInfo',
+ 'DiagnosticSignError',
+ 'DiagnosticSignHint',
+ 'DiagnosticSignInfo',
+ 'DiagnosticSignWarn',
+ 'DiagnosticUnderlineError',
+ 'DiagnosticUnderlineHint',
+ 'DiagnosticUnderlineInfo',
+ 'DiagnosticUnderlineWarn',
+ 'DiagnosticVirtualTextError',
+ 'DiagnosticVirtualTextHint',
+ 'DiagnosticVirtualTextInfo',
+ 'DiagnosticVirtualTextWarn',
+ 'DiagnosticWarn',
+ }, exec_lua([[return vim.fn.getcompletion('Diagnostic', 'highlight')]]))
+ end)
+
+ it('retrieves diagnostics from all buffers and namespaces', function()
+ local result = exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, 1, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ make_error('Diagnostic #2', 2, 1, 2, 1),
+ })
+ vim.diagnostic.set(other_ns, 2, {
+ make_error('Diagnostic #3', 3, 1, 3, 1),
+ })
+ return vim.diagnostic.get()
+ ]]
+ eq(3, #result)
+ eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == 1 end, ...)]], result))
+ eq('Diagnostic #1', result[1].message)
+ end)
+
+ it('saves and count a single error', function()
+ eq(1, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)
+ ]])
+ end)
+
+ it('saves and count multiple errors', function()
+ eq(2, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ make_error('Diagnostic #2', 2, 1, 2, 1),
+ })
+ return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)
+ ]])
+ end)
+
+ it('saves and count from multiple namespaces', function()
+ eq({1, 1, 2}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 1', 1, 1, 1, 1),
+ })
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 2', 1, 1, 1, 1),
+ })
+ return {
+ -- First namespace
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ -- Second namespace
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns),
+ -- All namespaces
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR),
+ }
+ ]])
+ end)
+
+ it('saves and count from multiple namespaces with respect to severity', function()
+ eq({3, 0, 3}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
+ make_error('Diagnostic From Server 1:2', 2, 2, 2, 2),
+ make_error('Diagnostic From Server 1:3', 2, 3, 3, 2),
+ })
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, {
+ make_warning('Warning From Server 2', 3, 3, 3, 3),
+ })
+ return {
+ -- Namespace 1
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ -- Namespace 2
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns),
+ -- All namespaces
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR),
+ }
+ ]])
+ end)
+
+ it('handles one namespace clearing highlights while the other still has highlights', function()
+ -- 1 Error (1)
+ -- 1 Warning (2)
+ -- 1 Warning (2) + 1 Warning (1)
+ -- 2 highlights and 2 underlines (since error)
+ -- 1 highlight + 1 underline
+ local all_highlights = {1, 1, 2, 4, 2}
+ eq(all_highlights, exec_lua [[
+ local ns_1_diags = {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 2, 1, 2, 5),
+ }
+ local ns_2_diags = {
+ make_warning("Warning 1", 2, 1, 2, 5),
+ }
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags)
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags)
+
+ return {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]])
+
+ -- Clear diagnostics from namespace 1, and make sure we have the right amount of stuff for namespace 2
+ eq({1, 1, 2, 0, 2}, exec_lua [[
+ vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns)
+ return {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]])
+
+ -- Show diagnostics from namespace 1 again
+ eq(all_highlights, exec_lua([[
+ vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns)
+ return {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]]))
+ end)
+
+ it('does not display diagnostics when disabled', function()
+ eq({0, 2}, exec_lua [[
+ local ns_1_diags = {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 2, 1, 2, 5),
+ }
+ local ns_2_diags = {
+ make_warning("Warning 1", 2, 1, 2, 5),
+ }
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags)
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags)
+
+ vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns)
+
+ return {
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]])
+
+ eq({4, 0}, exec_lua [[
+ vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns)
+ vim.diagnostic.disable(diagnostic_bufnr, other_ns)
+
+ return {
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]])
+ end)
+
+ describe('reset()', function()
+ it('diagnostic count is 0 and displayed diagnostics are 0 after call', function()
+ -- 1 Error (1)
+ -- 1 Warning (2)
+ -- 1 Warning (2) + 1 Warning (1)
+ -- 2 highlights and 2 underlines (since error)
+ -- 1 highlight + 1 underline
+ local all_highlights = {1, 1, 2, 4, 2}
+ eq(all_highlights, exec_lua [[
+ local ns_1_diags = {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 2, 1, 2, 5),
+ }
+ local ns_2_diags = {
+ make_warning("Warning 1", 2, 1, 2, 5),
+ }
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags)
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags)
+
+ return {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ }
+ ]])
+
+ -- Reset diagnostics from namespace 1
+ exec_lua([[ vim.diagnostic.reset(diagnostic_ns) ]])
+
+ -- Make sure we have the right diagnostic count
+ eq({0, 1, 1, 0, 2} , exec_lua [[
+ local diagnostic_count = {}
+ vim.wait(100, function () diagnostic_count = {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ } end )
+ return diagnostic_count
+ ]])
+
+ -- Reset diagnostics from namespace 2
+ exec_lua([[ vim.diagnostic.reset(other_ns) ]])
+
+ -- Make sure we have the right diagnostic count
+ eq({0, 0, 0, 0, 0}, exec_lua [[
+ local diagnostic_count = {}
+ vim.wait(100, function () diagnostic_count = {
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns),
+ count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN),
+ count_extmarks(diagnostic_bufnr, diagnostic_ns),
+ count_extmarks(diagnostic_bufnr, other_ns),
+ } end )
+ return diagnostic_count
+ ]])
+
+ end)
+ end)
+
+ describe('get_next_pos()', function()
+ it('can find the next pos with only one namespace', function()
+ eq({1, 1}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ return vim.diagnostic.get_next_pos()
+ ]])
+ end)
+
+ it('can find next pos with two errors', function()
+ eq({4, 4}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ make_error('Diagnostic #2', 4, 4, 4, 4),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_next_pos { namespace = diagnostic_ns }
+ ]])
+ end)
+
+ it('can cycle when position is past error', function()
+ eq({1, 1}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_next_pos { namespace = diagnostic_ns }
+ ]])
+ end)
+
+ it('will not cycle when wrap is off', function()
+ eq(false, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_next_pos { namespace = diagnostic_ns, wrap = false }
+ ]])
+ end)
+
+ it('can cycle even from the last line', function()
+ eq({4, 4}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #2', 4, 4, 4, 4),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(0), 1})
+ return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns }
+ ]])
+ end)
+ end)
+
+ describe('get_prev_pos()', function()
+ it('can find the prev pos with only one namespace', function()
+ eq({1, 1}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_prev_pos()
+ ]])
+ end)
+
+ it('can find prev pos with two errors', function()
+ eq({1, 1}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ make_error('Diagnostic #2', 4, 4, 4, 4),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns }
+ ]])
+ end)
+
+ it('can cycle when position is past error', function()
+ eq({4, 4}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #2', 4, 4, 4, 4),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns }
+ ]])
+ end)
+
+ it('respects wrap parameter', function()
+ eq(false, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #2', 4, 4, 4, 4),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {3, 1})
+ return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns, wrap = false}
+ ]])
+ end)
+ end)
+
+ describe('get()', function()
+ it('returns an empty table when no diagnostics are present', function()
+ eq({}, exec_lua [[return vim.diagnostic.get(diagnostic_bufnr, {namespace=diagnostic_ns})]])
+ end)
+
+ it('returns all diagnostics when no severity is supplied', function()
+ eq(2, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 1, 1, 2, 5),
+ })
+
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ ]])
+ end)
+
+ it('returns only requested diagnostics when severity is supplied', function()
+ eq(2, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 1, 1, 2, 5),
+ make_information("Ignored information", 1, 1, 2, 5),
+ })
+
+ return #vim.diagnostic.get(diagnostic_bufnr, { severity = {min=vim.diagnostic.severity.WARN} })
+ ]])
+ end)
+
+ it('allows filtering by line', function()
+ eq(1, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 1, 1, 2, 5),
+ make_information("Ignored information", 1, 1, 2, 5),
+ make_error("Error On Other Line", 2, 1, 1, 5),
+ })
+
+ return #vim.diagnostic.get(diagnostic_bufnr, {lnum = 2})
+ ]])
+ end)
+ end)
+
+ describe('config()', function()
+ it('can use functions for config values', function()
+ exec_lua [[
+ vim.diagnostic.config({
+ virtual_text = function() return true end,
+ }, diagnostic_ns)
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+ ]]
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+
+ -- Now, don't enable virtual text.
+ -- We should have one less extmark displayed.
+ exec_lua [[
+ vim.diagnostic.config({
+ virtual_text = function() return false end,
+ }, diagnostic_ns)
+ ]]
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ end)
+
+ it('allows filtering by severity', function()
+ local get_extmark_count_with_severity = function(min_severity)
+ return exec_lua([[
+ vim.diagnostic.config({
+ underline = false,
+ virtual_text = {
+ severity = {min=...},
+ },
+ })
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_warning('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+
+ return count_extmarks(diagnostic_bufnr, diagnostic_ns)
+ ]], min_severity)
+ end
+
+ -- No messages with Error or higher
+ eq(0, get_extmark_count_with_severity("ERROR"))
+
+ -- But now we don't filter it
+ eq(1, get_extmark_count_with_severity("WARN"))
+ eq(1, get_extmark_count_with_severity("HINT"))
+ end)
+ end)
+
+ describe('set()', function()
+ it('can perform updates after insert_leave', function()
+ exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ -- Save the diagnostics
+ exec_lua [[
+ vim.diagnostic.config({
+ update_in_insert = false,
+ })
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+ ]]
+
+ -- No diagnostics displayed yet.
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ end)
+
+ it('does not perform updates when not needed', function()
+ exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ -- Save the diagnostics
+ exec_lua [[
+ vim.diagnostic.config({
+ update_in_insert = false,
+ virtual_text = true,
+ })
+
+ -- Count how many times we call display.
+ SetVirtualTextOriginal = vim.diagnostic._set_virtual_text
+
+ DisplayCount = 0
+ vim.diagnostic._set_virtual_text = function(...)
+ DisplayCount = DisplayCount + 1
+ return SetVirtualTextOriginal(...)
+ end
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+ ]]
+
+ -- No diagnostics displayed yet.
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ eq(0, exec_lua [[return DisplayCount]])
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ eq(1, exec_lua [[return DisplayCount]])
+
+ -- Go in and out of insert mode one more time.
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ -- Should not have set the virtual text again.
+ eq(1, exec_lua [[return DisplayCount]])
+ end)
+
+ it('never sets virtual text, in combination with insert leave', function()
+ exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ -- Save the diagnostics
+ exec_lua [[
+ vim.diagnostic.config({
+ update_in_insert = false,
+ virtual_text = false,
+ })
+
+ -- Count how many times we call display.
+ SetVirtualTextOriginal = vim.diagnostic._set_virtual_text
+
+ DisplayCount = 0
+ vim.diagnostic._set_virtual_text = function(...)
+ DisplayCount = DisplayCount + 1
+ return SetVirtualTextOriginal(...)
+ end
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+ ]]
+
+ -- No diagnostics displayed yet.
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ eq(0, exec_lua [[return DisplayCount]])
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ eq(0, exec_lua [[return DisplayCount]])
+
+ -- Go in and out of insert mode one more time.
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ -- Should not have set the virtual text still.
+ eq(0, exec_lua [[return DisplayCount]])
+ end)
+
+ it('can perform updates while in insert mode, if desired', function()
+ exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
+ nvim("input", "o")
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+
+ -- Save the diagnostics
+ exec_lua [[
+ vim.diagnostic.config({
+ update_in_insert = true,
+ })
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ })
+ ]]
+
+ -- Diagnostics are displayed, because the user wanted them that way!
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+
+ nvim("input", "<esc>")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]])
+ eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]])
+ end)
+
+ it('can set diagnostics without displaying them', function()
+ eq(0, exec_lua [[
+ vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns)
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
+ })
+ return count_extmarks(diagnostic_bufnr, diagnostic_ns)
+ ]])
+
+ eq(2, exec_lua [[
+ vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns)
+ return count_extmarks(diagnostic_bufnr, diagnostic_ns)
+ ]])
+ end)
+
+ it('can set display options', function()
+ eq(0, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
+ }, { virtual_text = false, underline = false })
+ return count_extmarks(diagnostic_bufnr, diagnostic_ns)
+ ]])
+
+ eq(1, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
+ }, { virtual_text = true, underline = false })
+ return count_extmarks(diagnostic_bufnr, diagnostic_ns)
+ ]])
+ end)
+ end)
+
+ describe('show_line_diagnostics()', function()
+ it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function()
+ -- Two lines:
+ -- Diagnostic:
+ -- 1. <msg>
+ eq(2, exec_lua [[
+ local diagnostics = {
+ make_error("Syntax error", 0, 1, 0, 3),
+ }
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
+ local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics()
+ return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
+ ]])
+ end)
+
+ it('creates floating window and returns popup bufnr and winnr without header, if requested', function()
+ -- One line (since no header):
+ -- 1. <msg>
+ eq(1, exec_lua [[
+ local diagnostics = {
+ make_error("Syntax error", 0, 1, 0, 3),
+ }
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
+ local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics {show_header = false}
+ return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
+ ]])
+ end)
+ end)
+
+ describe('set_signs()', function()
+ -- TODO(tjdevries): Find out why signs are not displayed when set from Lua...??
+ pending('sets signs by default', function()
+ exec_lua [[
+ vim.diagnostic.config({
+ update_in_insert = true,
+ signs = true,
+ })
+
+ local diagnostics = {
+ make_error('Delayed Diagnostic', 1, 1, 1, 2),
+ make_error('Delayed Diagnostic', 3, 3, 3, 3),
+ }
+
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
+
+ vim.diagnostic._set_signs(diagnostic_ns, diagnostic_bufnr, diagnostics)
+ -- return vim.fn.sign_getplaced()
+ ]]
+
+ nvim("input", "o")
+ nvim("input", "<esc>")
+
+ -- TODO(tjdevries): Find a way to get the signs to display in the test...
+ eq(nil, exec_lua [[
+ return im.fn.sign_getplaced()[1].signs
+ ]])
+ end)
+ end)
+
+ describe('setloclist()', function()
+ it('sets diagnostics in lnum order', function()
+ local loc_list = exec_lua [[
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Farther Diagnostic', 4, 4, 4, 4),
+ make_error('Lower Diagnostic', 1, 1, 1, 1),
+ })
+
+ vim.diagnostic.setloclist()
+
+ return vim.fn.getloclist(0)
+ ]]
+
+ assert(loc_list[1].lnum < loc_list[2].lnum)
+ end)
+
+ it('sets diagnostics in lnum order, regardless of namespace', function()
+ local loc_list = exec_lua [[
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Lower Diagnostic', 1, 1, 1, 1),
+ })
+
+ vim.diagnostic.set(other_ns, diagnostic_bufnr, {
+ make_warning('Farther Diagnostic', 4, 4, 4, 4),
+ })
+
+ vim.diagnostic.setloclist()
+
+ return vim.fn.getloclist(0)
+ ]]
+
+ assert(loc_list[1].lnum < loc_list[2].lnum)
+ end)
+ end)
+end)
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index e4fe1c1992..b0361580e0 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -1,15 +1,20 @@
local helpers = require('test.functional.helpers')(after_each)
+local command = helpers.command
local clear = helpers.clear
local exec_lua = helpers.exec_lua
local eq = helpers.eq
+local retry = helpers.retry
local nvim = helpers.nvim
describe('vim.lsp.diagnostic', function()
local fake_uri
before_each(function()
- clear()
+ clear {env={
+ NVIM_LUA_NOTRACK="1";
+ VIMRUNTIME=os.getenv"VIMRUNTIME";
+ }}
exec_lua [[
require('vim.lsp')
@@ -44,7 +49,7 @@ describe('vim.lsp.diagnostic', function()
count_of_extmarks_for_client = function(bufnr, client_id)
return #vim.api.nvim_buf_get_extmarks(
- bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {}
+ bufnr, vim.lsp.diagnostic.get_namespace(client_id), 0, -1, {}
)
end
]]
@@ -86,39 +91,6 @@ describe('vim.lsp.diagnostic', function()
eq(2, #result[1])
eq('Diagnostic #1', result[1][1].message)
end)
- it('Can convert diagnostic to quickfix items format', function()
- local bufnr = exec_lua([[
- local fake_uri = ...
- return vim.uri_to_bufnr(fake_uri)
- ]], fake_uri)
- local result = exec_lua([[
- local bufnr = ...
- vim.lsp.diagnostic.save(
- {
- make_error('Diagnostic #1', 1, 1, 1, 1),
- make_error('Diagnostic #2', 2, 1, 2, 1),
- }, bufnr, 1
- )
- return vim.lsp.util.diagnostics_to_items(vim.lsp.diagnostic.get_all())
- ]], bufnr)
- local expected = {
- {
- bufnr = bufnr,
- col = 2,
- lnum = 2,
- text = 'Diagnostic #1',
- type = 'E'
- },
- {
- bufnr = bufnr,
- col = 2,
- lnum = 3,
- text = 'Diagnostic #2',
- type = 'E'
- },
- }
- eq(expected, result)
- end)
it('should be able to save and count a single client error', function()
eq(1, exec_lua [[
vim.lsp.diagnostic.save(
@@ -218,7 +190,7 @@ describe('vim.lsp.diagnostic', function()
-- Clear diagnostics from server 1, and make sure we have the right amount of stuff for client 2
eq({1, 1, 2, 0, 2}, exec_lua [[
- vim.lsp.diagnostic.clear(diagnostic_bufnr, 1)
+ vim.lsp.diagnostic.disable(diagnostic_bufnr, 1)
return {
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
@@ -230,7 +202,7 @@ describe('vim.lsp.diagnostic', function()
-- Show diagnostics from server 1 again
eq(all_highlights, exec_lua([[
- vim.lsp.diagnostic.display(nil, diagnostic_bufnr, 1)
+ vim.lsp.diagnostic.enable(diagnostic_bufnr, 1)
return {
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
@@ -575,10 +547,10 @@ describe('vim.lsp.diagnostic', function()
})
-- Count how many times we call display.
- SetVirtualTextOriginal = vim.lsp.diagnostic.set_virtual_text
+ SetVirtualTextOriginal = vim.diagnostic._set_virtual_text
DisplayCount = 0
- vim.lsp.diagnostic.set_virtual_text = function(...)
+ vim.diagnostic._set_virtual_text = function(...)
DisplayCount = DisplayCount + 1
return SetVirtualTextOriginal(...)
end
@@ -719,7 +691,7 @@ describe('vim.lsp.diagnostic', function()
return vim.api.nvim_buf_get_extmarks(
diagnostic_bufnr,
- vim.lsp.diagnostic._get_diagnostic_namespace(1),
+ vim.lsp.diagnostic.get_namespace(1),
0,
-1,
{ details = true }
@@ -756,7 +728,7 @@ describe('vim.lsp.diagnostic', function()
return vim.api.nvim_buf_get_extmarks(
diagnostic_bufnr,
- vim.lsp.diagnostic._get_diagnostic_namespace(1),
+ vim.lsp.diagnostic.get_namespace(1),
0,
-1,
{ details = true }
@@ -798,6 +770,40 @@ describe('vim.lsp.diagnostic', function()
eq(1, get_extmark_count_with_severity("Warning"))
eq(1, get_extmark_count_with_severity("Hint"))
end)
+
+ it('correctly handles UTF-16 offsets', function()
+ local line = "All 💼 and no 🎉 makes Jack a dull 👦"
+ local result = exec_lua([[
+ local line = ...
+ local client_id = vim.lsp.start_client {
+ cmd_env = {
+ NVIM_LUA_NOTRACK = "1";
+ };
+ cmd = {
+ vim.v.progpath, '-es', '-u', 'NONE', '--headless'
+ };
+ offset_encoding = "utf-16";
+ }
+
+ vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, {line})
+
+ vim.lsp.diagnostic.on_publish_diagnostics(nil, {
+ uri = fake_uri,
+ diagnostics = {
+ make_error('UTF-16 Diagnostic', 0, 7, 0, 8),
+ }
+ }, {client_id=client_id}
+ )
+
+ local diags = vim.diagnostic.get(diagnostic_bufnr)
+ vim.lsp.stop_client(client_id)
+ vim.lsp._vim_exit_handler()
+ return diags
+ ]], line)
+ eq(1, #result)
+ eq(exec_lua([[return vim.str_byteindex(..., 7, true)]], line), result[1].col)
+ eq(exec_lua([[return vim.str_byteindex(..., 8, true)]], line), result[1].end_col)
+ end)
end)
describe('lsp.util.show_line_diagnostics', function()
@@ -940,4 +946,31 @@ describe('vim.lsp.diagnostic', function()
assert(loc_list[1].lnum < loc_list[2].lnum)
end)
end)
+
+ it('highlight groups', function()
+ command('runtime plugin/diagnostic.vim')
+ eq({
+ 'LspDiagnosticsDefaultError',
+ 'LspDiagnosticsDefaultHint',
+ 'LspDiagnosticsDefaultInformation',
+ 'LspDiagnosticsDefaultWarning',
+ 'LspDiagnosticsFloatingError',
+ 'LspDiagnosticsFloatingHint',
+ 'LspDiagnosticsFloatingInformation',
+ 'LspDiagnosticsFloatingWarning',
+ 'LspDiagnosticsSignError',
+ 'LspDiagnosticsSignHint',
+ 'LspDiagnosticsSignInformation',
+ 'LspDiagnosticsSignWarning',
+ 'LspDiagnosticsUnderlineError',
+ 'LspDiagnosticsUnderlineHint',
+ 'LspDiagnosticsUnderlineInformation',
+ 'LspDiagnosticsUnderlineWarning',
+ 'LspDiagnosticsVirtualTextError',
+ 'LspDiagnosticsVirtualTextHint',
+ 'LspDiagnosticsVirtualTextInformation',
+ 'LspDiagnosticsVirtualTextWarning',
+ }, exec_lua([[return vim.fn.getcompletion('Lsp', 'highlight')]]))
+ end)
+
end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 7df5eb049c..ef78c8db4d 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -1017,31 +1017,6 @@ describe('LSP', function()
}
end
- it('highlight groups', function()
- eq({
- 'LspDiagnosticsDefaultError',
- 'LspDiagnosticsDefaultHint',
- 'LspDiagnosticsDefaultInformation',
- 'LspDiagnosticsDefaultWarning',
- 'LspDiagnosticsFloatingError',
- 'LspDiagnosticsFloatingHint',
- 'LspDiagnosticsFloatingInformation',
- 'LspDiagnosticsFloatingWarning',
- 'LspDiagnosticsSignError',
- 'LspDiagnosticsSignHint',
- 'LspDiagnosticsSignInformation',
- 'LspDiagnosticsSignWarning',
- 'LspDiagnosticsUnderlineError',
- 'LspDiagnosticsUnderlineHint',
- 'LspDiagnosticsUnderlineInformation',
- 'LspDiagnosticsUnderlineWarning',
- 'LspDiagnosticsVirtualTextError',
- 'LspDiagnosticsVirtualTextHint',
- 'LspDiagnosticsVirtualTextInformation',
- 'LspDiagnosticsVirtualTextWarning',
- }, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]]))
- end)
-
describe('apply_text_edits', function()
before_each(function()
insert(dedent([[