diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/autoload/man.vim | 1 | ||||
-rw-r--r-- | runtime/doc/deprecated.txt | 44 | ||||
-rw-r--r-- | runtime/doc/diagnostic.txt | 483 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 498 | ||||
-rw-r--r-- | runtime/doc/if_perl.txt | 3 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 594 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 228 | ||||
-rw-r--r-- | runtime/doc/repeat.txt | 11 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 1 | ||||
-rw-r--r-- | runtime/lua/vim/diagnostic.lua | 1150 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 13 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 1477 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 14 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 51 | ||||
-rw-r--r-- | runtime/plugin/diagnostic.vim | 48 |
17 files changed, 2629 insertions, 2009 deletions
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 8bf95651b7..4f132b6121 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -58,6 +58,7 @@ function! man#open_page(count, mods, ...) abort else execute 'silent keepalt' a:mods 'stag' l:target endif + call s:set_options(v:false) finally call setbufvar(l:buf, '&tagfunc', l:save_tfu) endtry 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/eval.txt b/runtime/doc/eval.txt index f27d1e01a0..99e48e602b 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -14,8 +14,8 @@ Using expressions is introduced in chapter 41 of the user manual |usr_41.txt|. 1. Variables *variables* 1.1 Variable types ~ - *E712* -There are six types of variables: + *E712* *E896* *E897* *E899* +There are seven types of variables: *Number* *Integer* Number A 32 or 64 bit signed number. |expr-number| @@ -34,7 +34,7 @@ Funcref A reference to a function |Funcref|. like a Partial. Example: function("Callback", [arg], myDict) -List An ordered sequence of items |List|. +List An ordered sequence of items, see |List| for details. Example: [1, 2, ['a', 'b']] Dictionary An associative, unordered array: Each entry has a key and a @@ -43,6 +43,11 @@ Dictionary An associative, unordered array: Each entry has a key and a {'blue': "#0000ff", 'red': "#ff0000"} #{blue: "#0000ff", red: "#ff0000"} +Blob Binary Large Object. Stores any sequence of bytes. See |Blob| + for details. + Example: 0zFF00ED015DAF + 0z is an empty Blob. + The Number and String types are converted automatically, depending on how they are used. @@ -97,6 +102,7 @@ Note that " " and "0" are also non-empty strings, thus considered to be TRUE. A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE. *E745* *E728* *E703* *E729* *E730* *E731* + *E974* *E975* *E976* |List|, |Dictionary|, |Funcref|, and |Blob| types are not automatically converted. @@ -366,8 +372,8 @@ Changing the order of items in a list: > For loop ~ -The |:for| loop executes commands for each item in a list. A variable is set -to each item in the list in sequence. Example: > +The |:for| loop executes commands for each item in a |List| or |Blob|. +A variable is set to each item in the sequence. Example with a List: > :for item in mylist : call Doit(item) :endfor @@ -400,6 +406,8 @@ It is also possible to put remaining items in a List variable: > : endif :endfor +For a Blob one byte at a time is used. + List functions ~ *E714* @@ -594,7 +602,137 @@ Functions that can be used with a Dictionary: > :call map(dict, '">> " . v:val') " prepend ">> " to each item -1.5 More about variables ~ +1.5 Blobs ~ + *blob* *Blob* *Blobs* *E978* +A Blob is a binary object. It can be used to read an image from a file and +send it over a channel, for example. + +A Blob mostly behaves like a |List| of numbers, where each number has the +value of an 8-bit byte, from 0 to 255. + + +Blob creation ~ + +A Blob can be created with a |blob-literal|: > + :let b = 0zFF00ED015DAF +Dots can be inserted between bytes (pair of hex characters) for readability, +they don't change the value: > + :let b = 0zFF00.ED01.5DAF + +A blob can be read from a file with |readfile()| passing the {type} argument +set to "B", for example: > + :let b = readfile('image.png', 'B') + + +Blob index ~ + *blob-index* *E979* +A byte in the Blob can be accessed by putting the index in square brackets +after the Blob. Indexes are zero-based, thus the first byte has index zero. > + :let myblob = 0z00112233 + :let byte = myblob[0] " get the first byte: 0x00 + :let byte = myblob[2] " get the third byte: 0x22 + +A negative index is counted from the end. Index -1 refers to the last byte in +the Blob, -2 to the last but one byte, etc. > + :let last = myblob[-1] " get the last byte: 0x33 + +To avoid an error for an invalid index use the |get()| function. When an item +is not available it returns -1 or the default value you specify: > + :echo get(myblob, idx) + :echo get(myblob, idx, 999) + + +Blob iteration ~ + +The |:for| loop executes commands for each byte of a Blob. The loop variable is +set to each byte in the Blob. Example: > + :for byte in 0z112233 + : call Doit(byte) + :endfor +This calls Doit() with 0x11, 0x22 and 0x33. + + +Blob concatenation ~ + +Two blobs can be concatenated with the "+" operator: > + :let longblob = myblob + 0z4455 + :let myblob += 0z6677 + +To change a blob in-place see |blob-modification| below. + + +Part of a blob ~ + +A part of the Blob can be obtained by specifying the first and last index, +separated by a colon in square brackets: > + :let myblob = 0z00112233 + :let shortblob = myblob[1:2] " get 0z1122 + :let shortblob = myblob[2:-1] " get 0z2233 + +Omitting the first index is similar to zero. Omitting the last index is +similar to -1. > + :let endblob = myblob[2:] " from item 2 to the end: 0z2233 + :let shortblob = myblob[2:2] " Blob with one byte: 0z22 + :let otherblob = myblob[:] " make a copy of the Blob + +If the first index is beyond the last byte of the Blob or the second byte is +before the first byte, the result is an empty Blob. There is no error +message. + +If the second index is equal to or greater than the length of the Blob the +length minus one is used: > + :echo myblob[2:8] " result: 0z2233 + + +Blob modification ~ + *blob-modification* +To change a specific byte of a blob use |:let| this way: > + :let blob[4] = 0x44 + +When the index is just one beyond the end of the Blob, it is appended. Any +higher index is an error. + +To change a sequence of bytes the [:] notation can be used: > + let blob[1:3] = 0z445566 +The length of the replaced bytes must be exactly the same as the value +provided. *E972* + +To change part of a blob you can specify the first and last byte to be +modified. The value must at least have the number of bytes in the range: > + :let blob[3:5] = [3, 4, 5] + +You can also use the functions |add()|, |remove()| and |insert()|. + + +Blob identity ~ + +Blobs can be compared for equality: > + if blob == 0z001122 +And for equal identity: > + if blob is otherblob +< *blob-identity* *E977* +When variable "aa" is a Blob and you assign it to another variable "bb", both +variables refer to the same Blob. Then the "is" operator returns true. + +When making a copy using [:] or |copy()| the values are the same, but the +identity is different: > + :let blob = 0z112233 + :let blob2 = blob + :echo blob == blob2 +< 1 > + :echo blob is blob2 +< 1 > + :let blob3 = blob[:] + :echo blob == blob3 +< 1 > + :echo blob is blob3 +< 0 + +Making a copy of a Blob is done with the |copy()| function. Using [:] also +works, as explained above. + + +1.6 More about variables ~ *more-variables* If you need to know the type of a variable or expression, use the |type()| function. @@ -645,8 +783,9 @@ Expression syntax summary, from least to most significant: etc. As above, append ? for ignoring case, # for matching case - expr5 is expr5 same |List| instance - expr5 isnot expr5 different |List| instance + expr5 is expr5 same |List|, |Dictionary| or |Blob| instance + expr5 isnot expr5 different |List|, |Dictionary| or |Blob| + instance |expr5| expr6 expr6 + expr6 ... number addition, list or blob concatenation @@ -817,12 +956,12 @@ Dictionary and arguments, use |get()| to get the function name: > if get(Part1, 'name') == get(Part2, 'name') " Part1 and Part2 refer to the same function -When using "is" or "isnot" with a |List| or a |Dictionary| this checks if the -expressions are referring to the same |List| or |Dictionary| instance. A copy -of a |List| is different from the original |List|. When using "is" without -a |List| or a |Dictionary| it is equivalent to using "equal", using "isnot" -equivalent to using "not equal". Except that a different type means the -values are different: > +Using "is" or "isnot" with a |List|, |Dictionary| or |Blob| checks whether +the expressions are referring to the same |List|, |Dictionary| or |Blob| +instance. A copy of a |List| is different from the original |List|. When +using "is" without a |List|, |Dictionary| or |Blob|, it is equivalent to +using "equal", using "isnot" is equivalent to using "not equal". Except that +a different type means the values are different: > echo 4 == '4' 1 echo 4 is '4' @@ -1012,6 +1151,12 @@ just above. Also see |sublist| below. Examples: > :let l = mylist[4:4] " List with one item :let l = mylist[:] " shallow copy of a List +If expr8 is a |Blob| this results in a new |Blob| with the bytes in the +indexes expr1a and expr1b, inclusive. Examples: > + :let b = 0zDEADBEEF + :let bs = b[1:2] " 0zADBE + :let bs = b[] " copy of 0zDEADBEEF + Using expr8[expr1] or expr8[expr1a : expr1b] on a |Funcref| results in an error. @@ -1180,6 +1325,14 @@ encodings. Use "\u00ff" to store character 255 correctly as UTF-8. Note that "\000" and "\x00" force the end of the string. +blob-literal *blob-literal* *E973* +------------ + +Hexadecimal starting with 0z or 0Z, with an arbitrary number of bytes. +The sequence must be an even number of hex characters. Example: > + :let b = 0zFF00ED015DAF + + literal-string *literal-string* *E115* --------------- 'string' string constant *expr-'* @@ -2007,19 +2160,21 @@ v:swapcommand Normal mode command to be executed after a file has been For ":edit +cmd file" the value is ":cmd\r". *v:t_TYPE* *v:t_bool* *t_bool-variable* -v:t_bool Value of Boolean type. Read-only. See: |type()| +v:t_bool Value of |Boolean| type. Read-only. See: |type()| *v:t_dict* *t_dict-variable* -v:t_dict Value of Dictionary type. Read-only. See: |type()| +v:t_dict Value of |Dictionary| type. Read-only. See: |type()| *v:t_float* *t_float-variable* -v:t_float Value of Float type. Read-only. See: |type()| +v:t_float Value of |Float| type. Read-only. See: |type()| *v:t_func* *t_func-variable* -v:t_func Value of Funcref type. Read-only. See: |type()| +v:t_func Value of |Funcref| type. Read-only. See: |type()| *v:t_list* *t_list-variable* -v:t_list Value of List type. Read-only. See: |type()| +v:t_list Value of |List| type. Read-only. See: |type()| *v:t_number* *t_number-variable* -v:t_number Value of Number type. Read-only. See: |type()| +v:t_number Value of |Number| type. Read-only. See: |type()| *v:t_string* *t_string-variable* -v:t_string Value of String type. Read-only. See: |type()| +v:t_string Value of |String| type. Read-only. See: |type()| + *v:t_blob* *t_blob-variable* +v:t_blob Value of |Blob| type. Read-only. See: |type()| *v:termresponse* *termresponse-variable* v:termresponse The escape sequence returned by the terminal for the DA @@ -2103,7 +2258,7 @@ USAGE RESULT DESCRIPTION ~ abs({expr}) Float or Number absolute value of {expr} acos({expr}) Float arc cosine of {expr} -add({list}, {item}) List append {item} to |List| {list} +add({object}, {item}) List/Blob append {item} to {object} and({expr}, {expr}) Number bitwise AND api_info() Dict api metadata append({lnum}, {string}) Number append {string} below line {lnum} @@ -2315,8 +2470,8 @@ hlID({name}) Number syntax ID of highlight group {name} hostname() String name of the machine Vim is running on iconv({expr}, {from}, {to}) String convert encoding of {expr} indent({lnum}) Number indent of line {lnum} -index({list}, {expr} [, {start} [, {ic}]]) - Number index in {list} where {expr} appears +index({object}, {expr} [, {start} [, {ic}]]) + Number index in {object} where {expr} appears input({prompt} [, {text} [, {completion}]]) String get input from the user inputlist({textlist}) Number let the user pick from a choice list @@ -2324,8 +2479,8 @@ inputrestore() Number restore typeahead inputsave() Number save and clear typeahead inputsecret({prompt} [, {text}]) String like input() but hiding the text -insert({list}, {item} [, {idx}]) - List insert {item} in {list} [before {idx}] +insert({object}, {item} [, {idx}]) + List insert {item} in {object} [before {idx}] interrupt() none interrupt script execution invert({expr}) Number bitwise invert isdirectory({directory}) Number |TRUE| if {directory} is a directory @@ -2384,8 +2539,8 @@ min({expr}) Number minimum value of items in {expr} mkdir({name} [, {path} [, {prot}]]) Number create directory {name} mode([expr]) String current editing mode -msgpackdump({list}) List dump a list of objects to msgpack -msgpackparse({list}) List parse msgpack to a list of objects +msgpackdump({list} [, {type}]) List/Blob dump objects to msgpack +msgpackparse({data}) List parse msgpack to a list of objects nextnonblank({lnum}) Number line nr of non-blank line >= {lnum} nr2char({expr}[, {utf8}]) String single char with ASCII/UTF8 value {expr} nvim_...({args}...) any call nvim |api| functions @@ -2407,7 +2562,7 @@ pyxeval({expr}) any evaluate |python_x| expression range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} -readfile({fname} [, {binary} [, {max}]]) +readfile({fname} [, {type} [, {max}]]) List get list of lines from file {fname} reg_executing() String get the executing register name reg_recording() String get the recording register name @@ -2424,7 +2579,10 @@ remote_read({serverid} [, {timeout}]) remote_send({server}, {string} [, {idvar}]) String send key sequence remote_startserver({name}) none become server {name} -remove({list}, {idx} [, {end}]) any remove items {idx}-{end} from {list} +remove({list}, {idx} [, {end}]) any/List + remove items {idx}-{end} from {list} +remove({blob}, {idx} [, {end}]) Number/Blob + remove bytes {idx}-{end} from {blob} remove({dict}, {key}) any remove entry {key} from {dict} rename({from}, {to}) Number rename (move) file from {from} to {to} repeat({expr}, {count}) String repeat {expr} {count} times @@ -2612,8 +2770,8 @@ winrestview({dict}) none restore view of current window winsaveview() Dict save view of current window winwidth({nr}) Number width of window {nr} wordcount() Dict get byte/char/word statistics -writefile({list}, {fname} [, {flags}]) - Number write list of lines to file {fname} +writefile({object}, {fname} [, {flags}]) + Number write |Blob| or |List| of lines to file xor({expr}, {expr}) Number bitwise XOR @@ -2647,13 +2805,14 @@ acos({expr}) *acos()* Can also be used as a |method|: > Compute()->acos() -add({list}, {expr}) *add()* - Append the item {expr} to |List| {list}. Returns the - resulting |List|. Examples: > +add({object}, {expr}) *add()* + Append the item {expr} to |List| or |Blob| {object}. Returns + the resulting |List| or |Blob|. Examples: > :let alist = add([1, 2, 3], item) :call add(mylist, "woodstock") < Note that when {expr} is a |List| it is appended as a single item. Use |extend()| to concatenate |Lists|. + When {object} is a |Blob| then {expr} must be a number. Use |insert()| to add an item at another position. Can also be used as a |method|: > @@ -3066,8 +3225,8 @@ chansend({id}, {data}) *chansend()* written if the write succeeded, 0 otherwise. See |channel-bytes| for more information. - {data} may be a string, string convertible, or a list. If - {data} is a list, the items will be joined by newlines; any + {data} may be a string, string convertible, |Blob|, or a list. + If {data} is a list, the items will be joined by newlines; any newlines in an item will be sent as NUL. To send a final newline, include a final empty string. Example: > :call chansend(id, ["abc", "123\n456", ""]) @@ -3640,9 +3799,13 @@ diff_hlID({lnum}, {col}) *diff_hlID()* empty({expr}) *empty()* Return the Number 1 if {expr} is empty, zero otherwise. - A |List| or |Dictionary| is empty when it does not have any - items. A Number is empty when its value is zero. Special - variable is empty when it is |v:false| or |v:null|. + - A |List| or |Dictionary| is empty when it does not have any + items. + - A |String| is empty when its length is zero. + - A |Number| and |Float| are empty when their value is zero. + - |v:false| and |v:null| are empty, |v:true| is not. + - A |Blob| is empty when its length is zero. + Can also be used as a |method|: > mylist->empty() @@ -3665,8 +3828,8 @@ escape({string}, {chars}) *escape()* *eval()* eval({string}) Evaluate {string} and return the result. Especially useful to turn the result of |string()| back into the original value. - This works for Numbers, Floats, Strings and composites of - them. Also works for |Funcref|s that refer to existing + This works for Numbers, Floats, Strings, Blobs and composites + of them. Also works for |Funcref|s that refer to existing functions. Can also be used as a |method|: > @@ -4081,7 +4244,7 @@ filter({expr1}, {expr2}) *filter()* |Dictionary| to remain unmodified make a copy first: > :let l = filter(copy(mylist), 'v:val =~ "KEEP"') -< Returns {expr1}, the |List| , |Blob| or |Dictionary| that was +< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was filtered. When an error is encountered while evaluating {expr2} no further items in {expr1} are processed. When {expr2} is a Funcref errors inside a function are ignored, @@ -4362,6 +4525,10 @@ get({list}, {idx} [, {default}]) *get()* omitted. Can also be used as a |method|: > mylist->get(idx) +get({blob}, {idx} [, {default}]) + Get byte {idx} from |Blob| {blob}. When this byte is not + available return {default}. Return -1 when {default} is + omitted. get({dict}, {key} [, {default}]) Get item with key {key} from |Dictionary| {dict}. When this item is not available return {default}. Return zero when @@ -5548,17 +5715,21 @@ indent({lnum}) The result is a Number, which is indent of line {lnum} in the When {lnum} is invalid -1 is returned. -index({list}, {expr} [, {start} [, {ic}]]) *index()* - Return the lowest index in |List| {list} where the item has a - value equal to {expr}. There is no automatic conversion, so - the String "4" is different from the Number 4. And the number - 4 is different from the Float 4.0. The value of 'ignorecase' - is not used here, case always matters. +index({object}, {expr} [, {start} [, {ic}]]) *index()* + If {object} is a |List| return the lowest index where the item + has a value equal to {expr}. There is no automatic + conversion, so the String "4" is different from the Number 4. + And the Number 4 is different from the Float 4.0. The value + of 'ignorecase' is not used here, case always matters. + + If {object} is a |Blob| return the lowest index where the byte + value is equal to {expr}. + If {start} is given then start looking at the item with index {start} (may be negative for an item relative to the end). When {ic} is given and it is |TRUE|, ignore case. Otherwise case must match. - -1 is returned when {expr} is not found in {list}. + -1 is returned when {expr} is not found in {object}. Example: > :let idx = index(words, "the") :if index(numbers, 123) >= 0 @@ -5717,13 +5888,16 @@ inputsecret({prompt} [, {text}]) *inputsecret()* typed on the command-line in response to the issued prompt. NOTE: Command-line completion is not supported. -insert({list}, {item} [, {idx}]) *insert()* - Insert {item} at the start of |List| {list}. +insert({object}, {item} [, {idx}]) *insert()* + When {object} is a |List| or a |Blob| insert {item} at the start + of it. + If {idx} is specified insert {item} before the item with index {idx}. If {idx} is zero it goes before the first item, just like omitting {idx}. A negative {idx} is also possible, see |list-index|. -1 inserts just before the last item. - Returns the resulting |List|. Examples: > + + Returns the resulting |List| or |Blob|. Examples: > :let mylist = insert([2, 3, 5], 1) :call insert(mylist, 4, -1) :call insert(mylist, 6, len(mylist)) @@ -5787,16 +5961,16 @@ islocked({expr}) *islocked()* *E786* id({expr}) *id()* Returns a |String| which is a unique identifier of the - container type (|List|, |Dict| and |Partial|). It is + container type (|List|, |Dict|, |Blob| and |Partial|). It is guaranteed that for the mentioned types `id(v1) ==# id(v2)` returns true iff `type(v1) == type(v2) && v1 is v2`. - Note that |v:_null_string|, |v:_null_list|, and |v:_null_dict| - have the same `id()` with different types because they are - internally represented as a NULL pointers. `id()` returns a - hexadecimal representanion of the pointers to the containers - (i.e. like `0x994a40`), same as `printf("%p", {expr})`, - but it is advised against counting on the exact format of - return value. + Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and + |v:_null_blob| have the same `id()` with different types + because they are internally represented as NULL pointers. + `id()` returns a hexadecimal representanion of the pointers to + the containers (i.e. like `0x994a40`), same as `printf("%p", + {expr})`, but it is advised against counting on the exact + format of the return value. It is not guaranteed that `id(no_longer_existing_container)` will not be equal to some other `id()`: new containers may @@ -5806,8 +5980,13 @@ items({dict}) *items()* Return a |List| with all the key-value pairs of {dict}. Each |List| item is a list with two items: the key of a {dict} entry and the value of this entry. The |List| is in arbitrary - order. - Can also be used as a |method|: > + order. Also see |keys()| and |values()|. + Example: > + for [key, value] in items(mydict) + echo key . ': ' . value + endfor + +< Can also be used as a |method|: > mydict->items() isnan({expr}) *isnan()* @@ -5974,10 +6153,12 @@ json_encode({expr}) *json_encode()* surrogate pairs (such strings are not valid UTF-8 strings). Non-printable characters are converted into "\u1234" escapes or special escapes like "\t", other are dumped as-is. + |Blob|s are converted to arrays of the individual bytes. keys({dict}) *keys()* Return a |List| with all the keys of {dict}. The |List| is in - arbitrary order. + arbitrary order. Also see |items()| and |values()|. + Can also be used as a |method|: > mydict->keys() @@ -6141,12 +6322,14 @@ luaeval({expr}[, {expr}]) to Vim data structures. See |lua-eval| for more details. map({expr1}, {expr2}) *map()* - {expr1} must be a |List| or a |Dictionary|. + {expr1} must be a |List|, |Blob| or |Dictionary|. Replace each item in {expr1} with the result of evaluating - {expr2}. {expr2} must be a |string| or |Funcref|. + {expr2}. For a |Blob| each byte is replaced. + + {expr2} must be a |string| or |Funcref|. If {expr2} is a |string|, inside {expr2} |v:val| has the value - of the current item. For a |Dictionary| |v:key| has the key + of the current item. For a |Dictionary| |v:key| has the key of the current item and for a |List| |v:key| has the index of the current item. For a |Blob| |v:key| has the index of the current byte. @@ -6179,11 +6362,11 @@ map({expr1}, {expr2}) *map()* |Dictionary| to remain unmodified make a copy first: > :let tlist = map(copy(mylist), ' v:val . "\t"') -< Returns {expr1}, the |List| or |Dictionary| that was filtered. - When an error is encountered while evaluating {expr2} no - further items in {expr1} are processed. When {expr2} is a - Funcref errors inside a function are ignored, unless it was - defined with the "abort" flag. +< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was + filtered. When an error is encountered while evaluating + {expr2} no further items in {expr1} are processed. When + {expr2} is a Funcref errors inside a function are ignored, + unless it was defined with the "abort" flag. Can also be used as a |method|: > mylist->map(expr2) @@ -6651,11 +6834,15 @@ mode([expr]) Return a string that indicates the current mode. the leading character(s). Also see |visualmode()|. -msgpackdump({list}) *msgpackdump()* - Convert a list of VimL objects to msgpack. Returned value is - |readfile()|-style list. Example: > +msgpackdump({list} [, {type}]) *msgpackdump()* + Convert a list of VimL objects to msgpack. Returned value is a + |readfile()|-style list. When {type} contains "B", a |Blob| is + returned instead. Example: > call writefile(msgpackdump([{}]), 'fname.mpack', 'b') -< This will write the single 0x80 byte to `fname.mpack` file +< or, using a |Blob|: > + call writefile(msgpackdump([{}], 'B'), 'fname.mpack') +< + This will write the single 0x80 byte to a `fname.mpack` file (dictionary with zero items is represented by 0x80 byte in messagepack). @@ -6663,11 +6850,12 @@ msgpackdump({list}) *msgpackdump()* 1. |Funcref|s cannot be dumped. 2. Containers that reference themselves cannot be dumped. 3. Dictionary keys are always dumped as STR strings. - 4. Other strings are always dumped as BIN strings. + 4. Other strings and |Blob|s are always dumped as BIN strings. 5. Points 3. and 4. do not apply to |msgpack-special-dict|s. -msgpackparse({list}) *msgpackparse()* - Convert a |readfile()|-style list to a list of VimL objects. +msgpackparse({data}) *msgpackparse()* + Convert a |readfile()|-style list or a |Blob| to a list of + VimL objects. Example: > let fname = expand('~/.config/nvim/shada/main.shada') let mpack = readfile(fname, 'b') @@ -6677,7 +6865,7 @@ msgpackparse({list}) *msgpackparse()* Limitations: 1. Mapping ordering is not preserved unless messagepack - mapping is dumped using generic mapping + mapping is dumped using generic mapping (|msgpack-special-map|). 2. Since the parser aims to preserve all data untouched (except for 1.) some strings are parsed to @@ -6721,9 +6909,9 @@ msgpackparse({list}) *msgpackparse()* zero byte or if string is a mapping key and mapping is being represented as special dictionary for other reasons. - binary |readfile()|-style list of strings. This value will - appear in |msgpackparse()| output if binary string - contains zero byte. + binary |String|, or |Blob| if binary string contains zero + byte. This value cannot appear in |msgpackparse()| + output since blobs were introduced. array |List|. This value cannot appear in |msgpackparse()| output. *msgpack-special-map* @@ -7168,16 +7356,18 @@ readdir({directory} [, {expr}]) echo s:tree(".") < *readfile()* -readfile({fname} [, {binary} [, {max}]]) +readfile({fname} [, {type} [, {max}]]) Read file {fname} and return a |List|, each line of the file as an item. Lines are broken at NL characters. Macintosh files separated with CR will result in a single long line (unless a NL appears somewhere). All NUL characters are replaced with a NL character. - When {binary} contains "b" binary mode is used: + When {type} contains "b" binary mode is used: - When the last line ends in a NL an extra empty list item is added. - No CR characters are removed. + When {type} contains "B" a |Blob| is returned with the binary + data of the file unmodified. Otherwise: - CR characters that appear before a NL are removed. - Whether the last line ends in a NL or not does not matter. @@ -7357,6 +7547,17 @@ remove({list}, {idx} [, {end}]) *remove()* < Can also be used as a |method|: > mylist->remove(idx) +remove({blob}, {idx} [, {end}]) + Without {end}: Remove the byte at {idx} from |Blob| {blob} and + return the byte. + With {end}: Remove bytes from {idx} to {end} (inclusive) and + return a |Blob| with these bytes. When {idx} points to the same + byte as {end} a |Blob| with one byte is returned. When {end} + points to a byte before {idx} this is an error. + Example: > + :echo "last byte: " . remove(myblob, -1) + :call remove(mylist, 0, 9) + remove({dict}, {key}) Remove the entry from {dict} with key {key} and return it. Example: > @@ -7400,9 +7601,11 @@ resolve({filename}) *resolve()* *E655* path name) and also keeps a trailing path separator. *reverse()* -reverse({list}) Reverse the order of items in {list} in-place. Returns - {list}. - If you want a list to remain unmodified make a copy first: > +reverse({object}) + Reverse the order of items in {object} in-place. + {object} can be a |List| or a |Blob|. + Returns {object}. + If you want an object to remain unmodified make a copy first: > :let revlist = reverse(copy(mylist)) < Can also be used as a |method|: > mylist->reverse() @@ -8803,14 +9006,15 @@ stridx({haystack}, {needle} [, {start}]) *stridx()* *string()* string({expr}) Return {expr} converted to a String. If {expr} is a Number, - Float, String or a composition of them, then the result can be - parsed back with |eval()|. + Float, String, Blob or a composition of them, then the result + can be parsed back with |eval()|. {expr} type result ~ String 'string' Number 123 Float 123.123456 or 1.123456e8 or `str2float('inf')` Funcref `function('name')` + Blob 0z00112233.44556677.8899 List [item, item] Dictionary {key: value, key: value} Note that in String values the ' character is doubled. @@ -9139,11 +9343,23 @@ synstack({lnum}, {col}) *synstack()* valid positions. system({cmd} [, {input}]) *system()* *E677* - Get the output of {cmd} as a |string| (use |systemlist()| to - get a |List|). {cmd} is treated exactly as in |jobstart()|. - Not to be used for interactive commands. + Gets the output of {cmd} as a |string| (|systemlist()| returns + a |List|) and sets |v:shell_error| to the error code. + {cmd} is treated as in |jobstart()|: + If {cmd} is a List it runs directly (no 'shell'). + If {cmd} is a String it runs in the 'shell', like this: > + :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) - If {input} is a string it is written to a pipe and passed as +< Not to be used for interactive commands. + + Result is a String, filtered to avoid platform-specific quirks: + - <CR><NL> is replaced with <NL> + - NUL characters are replaced with SOH (0x01) + + Example: > + :echo system(['ls', expand('%:h')]) + +< If {input} is a string it is written to a pipe and passed as stdin to the command. The string is written as-is, line separators are not changed. If {input} is a |List| it is written to the pipe as @@ -9165,29 +9381,12 @@ system({cmd} [, {input}]) *system()* *E677* Note: Use |shellescape()| or |::S| with |expand()| or |fnamemodify()| to escape special characters in a command - argument. Newlines in {cmd} may cause the command to fail. - The characters in 'shellquote' and 'shellxquote' may also - cause trouble. - - Result is a String. Example: > - :let files = system("ls " . shellescape(expand('%:h'))) - :let files = system('ls ' . expand('%:h:S')) - -< To make the result more system-independent, the shell output - is filtered to replace <CR> with <NL> for Macintosh, and - <CR><NL> with <NL> for DOS-like systems. - To avoid the string being truncated at a NUL, all NUL - characters are replaced with SOH (0x01). + argument. 'shellquote' and 'shellxquote' must be properly + configured. Example: > + :echo system('ls '..shellescape(expand('%:h'))) + :echo system('ls '..expand('%:h:S')) - The command executed is constructed using several options when - {cmd} is a string: 'shell' 'shellcmdflag' {cmd} - - The resulting error code can be found in |v:shell_error|. - - Note that any wrong value in the options mentioned above may - make the function fail. It has also been reported to fail - when using a security agent application. - Unlike ":!cmd" there is no automatic check for changed files. +< Unlike ":!cmd" there is no automatic check for changed files. Use |:checktime| to force a check. Can also be used as a |method|: > @@ -9488,6 +9687,7 @@ type({expr}) *type()* Float: 5 (|v:t_float|) Boolean: 6 (|v:true| and |v:false|) Null: 7 (|v:null|) + Blob: 10 (|v:t_blob|) For backward compatibility, this method can be used: > :if type(myvar) == type(0) :if type(myvar) == type("") @@ -9572,7 +9772,7 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E882* values({dict}) *values()* Return a |List| with all the values of {dict}. The |List| is - in arbitrary order. + in arbitrary order. Also see |items()| and |keys()|. Can also be used as a |method|: > mydict->values() @@ -9932,14 +10132,17 @@ wordcount() *wordcount()* *writefile()* -writefile({list}, {fname} [, {flags}]) - Write |List| {list} to file {fname}. Each list item is - separated with a NL. Each list item must be a String or - Number. +writefile({object}, {fname} [, {flags}]) + When {object} is a |List| write it to file {fname}. Each list + item is separated with a NL. Each list item must be a String + or Number. When {flags} contains "b" then binary mode is used: There will not be a NL after the last list item. An empty item at the end does cause the last line in the file to end in a NL. + When {object} is a |Blob| write the bytes to file {fname} + unmodified. + When {flags} contains "a" then append mode is used, lines are appended to the file: > :call writefile(["foo"], "event.log", "a") @@ -10455,7 +10658,10 @@ This does NOT work: > This cannot be used to set a byte in a String. You can do that like this: > :let var = var[0:2] . 'X' . var[4:] -< +< When {var-name} is a |Blob| then {idx} can be the + length of the blob, in which case one byte is + appended. + *E711* *E719* :let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710* Set a sequence of items in a |List| to the result of @@ -10692,10 +10898,18 @@ text... :const x = 1 < is equivalent to: > :let x = 1 - :lockvar 1 x + :lockvar! x < This is useful if you want to make sure the variable - is not modified. - *E995* + is not modified. If the value is a List or Dictionary + literal then the items also cannot be changed: > + const ll = [1, 2, 3] + let ll[1] = 5 " Error! +< Nested references are not locked: > + let lvar = ['a'] + const lconst = [0, lvar] + let lconst[0] = 2 " Error! + let lconst[1][0] = 'b' " OK +< *E995* |:const| does not allow to for changing a variable. > :let x = 1 :const x = 2 " Error! @@ -10812,28 +11026,34 @@ text... NOTE: The ":append" and ":insert" commands don't work properly inside a ":while" and ":for" loop. -:for {var} in {list} *:for* *E690* *E732* +:for {var} in {object} *:for* *E690* *E732* :endfo[r] *:endfo* *:endfor* Repeat the commands between ":for" and ":endfor" for - each item in {list}. Variable {var} is set to the - value of each item. - When an error is detected for a command inside the - loop, execution continues after the "endfor". - Changing {list} inside the loop affects what items are - used. Make a copy if this is unwanted: > + each item in {object}. {object} can be a |List| or + a |Blob|. Variable {var} is set to the value of each + item. When an error is detected for a command inside + the loop, execution continues after the "endfor". + Changing {object} inside the loop affects what items + are used. Make a copy if this is unwanted: > :for item in copy(mylist) -< When not making a copy, Vim stores a reference to the - next item in the list, before executing the commands - with the current item. Thus the current item can be - removed without effect. Removing any later item means - it will not be found. Thus the following example - works (an inefficient way to make a list empty): > +< + When {object} is a |List| and not making a copy, Vim + stores a reference to the next item in the |List| + before executing the commands with the current item. + Thus the current item can be removed without effect. + Removing any later item means it will not be found. + Thus the following example works (an inefficient way + to make a |List| empty): > for item in mylist call remove(mylist, 0) endfor -< Note that reordering the list (e.g., with sort() or +< Note that reordering the |List| (e.g., with sort() or reverse()) may have unexpected effects. + When {object} is a |Blob|, Vim always makes a copy to + iterate over. Unlike with |List|, modifying the + |Blob| does not affect the iteration. + :for [{var1}, {var2}, ...] in {listlist} :endfo[r] Like ":for" above, but each item in {listlist} must be diff --git a/runtime/doc/if_perl.txt b/runtime/doc/if_perl.txt index ddcf220844..3787ca69ba 100644 --- a/runtime/doc/if_perl.txt +++ b/runtime/doc/if_perl.txt @@ -189,6 +189,9 @@ VIM::Eval({expr}) Evaluates {expr} and returns (success, value) in list A |List| is turned into a string by joining the items and inserting line breaks. + *perl-Blob* +VIM::Blob({expr}) Return Blob literal string 0zXXXX from scalar value. + ============================================================================== 3. VIM::Buffer objects *perl-buffer* 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/doc/lua.txt b/runtime/doc/lua.txt index 5731569947..53d68fa5e6 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1,15 +1,15 @@ *lua.txt* Nvim - NVIM REFERENCE MANUAL + NVIM REFERENCE MANUAL -Lua engine *lua* *Lua* +Lua engine *lua* *Lua* Type |gO| to see the table of contents. ============================================================================== -INTRODUCTION *lua-intro* +INTRODUCTION *lua-intro* The Lua 5.1 language is builtin and always available. Try this command to get an idea of what lurks beneath: > @@ -27,11 +27,12 @@ are on 'runtimepath': ~/.config/nvim/lua/foo.lua then `require('foo')` loads "~/.config/nvim/lua/foo.lua", and "runtime/lua/foo.lua" is not used. See |lua-require| to understand how Nvim -finds and loads Lua modules. The conventions are similar to VimL plugins, -with some extra features. See |lua-require-example| for a walkthrough. +finds and loads Lua modules. The conventions are similar to those of +Vimscript |plugin|s, with some extra features. See |lua-require-example| for +a walkthrough. ============================================================================== -IMPORTING LUA MODULES *lua-require* +IMPORTING LUA MODULES *lua-require* *lua-package-path* Nvim automatically adjusts `package.path` and `package.cpath` according to @@ -157,7 +158,7 @@ function without any parentheses. This is most often used to approximate ------------------------------------------------------------------------------ -LUA PLUGIN EXAMPLE *lua-require-example* +LUA PLUGIN EXAMPLE *lua-require-example* The following example plugin adds a command `:MakeCharBlob` which transforms current buffer into a long `unsigned char` array. Lua contains transformation @@ -234,7 +235,7 @@ lua/charblob.lua: > } ============================================================================== -COMMANDS *lua-commands* +COMMANDS *lua-commands* These commands execute a Lua chunk from either the command line (:lua, :luado) or a file (:luafile) on the given line [range]. As always in Lua, each chunk @@ -298,19 +299,20 @@ arguments separated by " " (space) instead of "\t" (tab). :luado if bp:match(line) then return "-->\t" .. line end < - *:luafile* + *:luafile* :[range]luafile {file} - Execute Lua script in {file}. - The whole argument is used as a single file name. + Execute Lua script in {file}. + The whole argument is used as the filename (like + |:edit|), spaces do not need to be escaped. + Alternatively you can |:source| Lua files. - Examples: - > + Examples: > :luafile script.lua :luafile % < ============================================================================== -luaeval() *lua-eval* *luaeval()* +luaeval() *lua-eval* *luaeval()* The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is "luaeval". "luaeval" takes an expression string and an optional argument used @@ -324,8 +326,8 @@ semantically equivalent in Lua to: end Lua nils, numbers, strings, tables and booleans are converted to their -respective VimL types. An error is thrown if conversion of any other Lua types -is attempted. +respective Vimscript types. If a Lua string contains a NUL byte, it will be +converted to a |Blob|. Conversion of other Lua types is an error. The magic global "_A" contains the second argument to luaeval(). @@ -348,21 +350,21 @@ cases there is the following agreement: 3. Table with string keys, at least one of which contains NUL byte, is also considered to be a dictionary, but this time it is converted to a |msgpack-special-map|. - *lua-special-tbl* + *lua-special-tbl* 4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point value: - - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to - a floating-point 1.0. Note that by default integral Lua numbers are - converted to |Number|s, non-integral are converted to |Float|s. This + - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to + a floating-point 1.0. Note that by default integral Lua numbers are + converted to |Number|s, non-integral are converted to |Float|s. This variant allows integral |Float|s. - - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty - dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is - converted to a dictionary `{'a': 42}`: non-string keys are ignored. - Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3. + - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty + dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is + converted to a dictionary `{'a': 42}`: non-string keys are ignored. + Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3. are errors. - - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well - as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not - form a 1-step sequence from 1 to N are ignored, as well as all + - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well + as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not + form a 1-step sequence from 1 to N are ignored, as well as all non-integral keys. Examples: > @@ -373,13 +375,13 @@ Examples: > : endfunction :echo Rand(1,10) -Note: second argument to `luaeval` undergoes VimL to Lua conversion -("marshalled"), so changes to Lua containers do not affect values in VimL. -Return value is also always converted. When converting, -|msgpack-special-dict|s are treated specially. +Note: second argument to `luaeval` is converted ("marshalled") from Vimscript +to Lua, so changes to Lua containers do not affect values in Vimscript. Return +value is also always converted. When converting, |msgpack-special-dict|s are +treated specially. ============================================================================== -Vimscript v:lua interface *v:lua-call* +Vimscript v:lua interface *v:lua-call* From Vimscript the special `v:lua` prefix can be used to call Lua functions which are global or accessible from global tables. The expression > @@ -419,7 +421,7 @@ Note: `v:lua` without a call is not allowed in a Vimscript expression: ============================================================================== -Lua standard modules *lua-stdlib* +Lua standard modules *lua-stdlib* The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes various functions and sub-modules. It is always loaded, thus require("vim") @@ -453,7 +455,7 @@ Note that underscore-prefixed functions (e.g. "_os_proc_children") are internal/private and must not be used by plugins. ------------------------------------------------------------------------------ -VIM.LOOP *lua-loop* *vim.loop* +VIM.LOOP *lua-loop* *vim.loop* `vim.loop` exposes all features of the Nvim event-loop. This is a low-level API that provides functionality for networking, filesystem, and process @@ -464,7 +466,7 @@ management. Try this command to see available functions: > Reference: https://github.com/luvit/luv/blob/master/docs.md Examples: https://github.com/luvit/luv/tree/master/examples - *E5560* *lua-loop-callbacks* + *E5560* *lua-loop-callbacks* It is an error to directly invoke `vim.api` functions (except |api-fast|) in `vim.loop` callbacks. For example, this is an error: > @@ -500,7 +502,7 @@ Example: repeating timer print('sleeping'); -Example: File-change detection *watch-file* +Example: File-change detection *watch-file* 1. Save this code to a file. 2. Execute it with ":luafile %". 3. Use ":Watch %" to watch any file. @@ -526,7 +528,7 @@ Example: File-change detection *watch-file* "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))") -Example: TCP echo-server *tcp-server* +Example: TCP echo-server *tcp-server* 1. Save this code to a file. 2. Execute it with ":luafile %". 3. Note the port number. @@ -556,7 +558,7 @@ Example: TCP echo-server *tcp-server* print('TCP echo-server listening on port: '..server:getsockname().port) ------------------------------------------------------------------------------ -VIM.HIGHLIGHT *lua-highlight* +VIM.HIGHLIGHT *lua-highlight* Nvim includes a function for highlighting a selection on yank (see for example https://github.com/machakann/vim-highlightedyank). To enable it, add @@ -591,21 +593,19 @@ vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclu range is inclusive (default false). ------------------------------------------------------------------------------ -VIM.REGEX *lua-regex* +VIM.REGEX *lua-regex* Vim regexes can be used directly from lua. Currently they only allow matching within a single line. -vim.regex({re}) *vim.regex()* +vim.regex({re}) *vim.regex()* + Parse the Vim regex {re} and return a regex object. Regexes are + "magic" and case-insensitive by default, regardless of 'magic' and + 'ignorecase'. The can be controlled with flags, see |/magic|. - Parse the regex {re} and return a regex object. 'magic' and - 'ignorecase' options are ignored, lua regexes always defaults to magic - and ignoring case. The behavior can be changed with flags in - the beginning of the string |/magic|. +Methods on the regex object: -Regex objects support the following methods: - -regex:match_str({str}) *regex:match_str()* +regex:match_str({str}) *regex:match_str()* Match the string against the regex. If the string should match the regex precisely, surround the regex with `^` and `$`. If the was a match, the byte indices for the beginning and end of @@ -613,7 +613,7 @@ regex:match_str({str}) *regex:match_str()* As any integer is truth-y, `regex:match()` can be directly used as a condition in an if-statement. -regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()* +regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()* Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end} are supplied, match only this byte index range. Otherwise see |regex:match_str()|. If {start} is used, then the returned byte @@ -692,67 +692,65 @@ VIM.MPACK *lua-mpack* The *vim.mpack* module provides packing and unpacking of lua objects to msgpack encoded strings. |vim.NIL| and |vim.empty_dict()| are supported. -vim.mpack.pack({obj}) *vim.mpack.pack* +vim.mpack.pack({obj}) *vim.mpack.pack* Packs a lua object {obj} and returns the msgpack representation as a string -vim.mpack.unpack({str}) *vim.mpack.unpack* +vim.mpack.unpack({str}) *vim.mpack.unpack* Unpacks the msgpack encoded {str} and returns a lua object ------------------------------------------------------------------------------ -VIM *lua-builtin* +VIM *lua-builtin* -vim.api.{func}({...}) *vim.api* +vim.api.{func}({...}) *vim.api* Invokes Nvim |API| function {func} with arguments {...}. Example: call the "nvim_get_current_line()" API function: > print(tostring(vim.api.nvim_get_current_line())) -vim.version() *vim.version* - Returns the version of the current neovim build. +vim.version() *vim.version* + Gets the version of the current Nvim build. -vim.in_fast_event() *vim.in_fast_event()* +vim.in_fast_event() *vim.in_fast_event()* Returns true if the code is executing as part of a "fast" event handler, where most of the API is disabled. These are low-level events (e.g. |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input. When this is `false` most API functions are callable (but may be subject to other restrictions such as |textlock|). -vim.NIL *vim.NIL* - Special value used to represent NIL in msgpack-rpc and |v:null| in - vimL interaction, and similar cases. Lua `nil` cannot be used as - part of a lua table representing a Dictionary or Array, as it - is equivalent to a missing value: `{"foo", nil}` is the same as - `{"foo"}` +vim.NIL *vim.NIL* + Special value representing NIL in |RPC| and |v:null| in Vimscript + conversion, and similar cases. Lua `nil` cannot be used as part of + a Lua table representing a Dictionary or Array, because it is + treated as missing: `{"foo", nil}` is the same as `{"foo"}`. -vim.empty_dict() *vim.empty_dict()* - Creates a special table which will be converted to an empty - dictionary when converting lua values to vimL or API types. The - table is empty, and this property is marked using a metatable. An - empty table `{}` without this metatable will default to convert to - an array/list. +vim.empty_dict() *vim.empty_dict()* + Creates a special empty table (marked with a metatable), which Nvim + converts to an empty dictionary when translating Lua values to + Vimscript or API types. Nvim by default converts an empty table `{}` + without this metatable to an list/array. - Note: if numeric keys are added to the table, the metatable will be - ignored and the dict converted to a list/array anyway. + Note: if numeric keys are present in the table, Nvim ignores the + metatable marker and converts the dict to a list/array anyway. -vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()* - Sends {event} to {channel} via |RPC| and returns immediately. - If {channel} is 0, the event is broadcast to all channels. +vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()* + Sends {event} to {channel} via |RPC| and returns immediately. If + {channel} is 0, the event is broadcast to all channels. - This function also works in a fast callback |lua-loop-callbacks|. + This function also works in a fast callback |lua-loop-callbacks|. -vim.rpcrequest({channel}, {method}[, {args}...]) *vim.rpcrequest()* - Sends a request to {channel} to invoke {method} via - |RPC| and blocks until a response is received. +vim.rpcrequest({channel}, {method}[, {args}...]) *vim.rpcrequest()* + Sends a request to {channel} to invoke {method} via |RPC| and blocks + until a response is received. - Note: NIL values as part of the return value is represented as - |vim.NIL| special value + Note: NIL values as part of the return value is represented as + |vim.NIL| special value -vim.stricmp({a}, {b}) *vim.stricmp()* +vim.stricmp({a}, {b}) *vim.stricmp()* Compares strings case-insensitively. Returns 0, 1 or -1 if strings are equal, {a} is greater than {b} or {a} is lesser than {b}, respectively. -vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* +vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not supplied, the length of the string is used. All indicies are zero-based. Returns two values: the UTF-32 and UTF-16 indicies respectively. @@ -840,40 +838,40 @@ vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()* end < -vim.type_idx *vim.type_idx* - Type index for use in |lua-special-tbl|. Specifying one of the - values from |vim.types| allows typing the empty table (it is - unclear whether empty Lua table represents empty list or empty array) - and forcing integral numbers to be |Float|. See |lua-special-tbl| for - more details. +vim.type_idx *vim.type_idx* + Type index for use in |lua-special-tbl|. Specifying one of the values + from |vim.types| allows typing the empty table (it is unclear whether + empty Lua table represents empty list or empty array) and forcing + integral numbers to be |Float|. See |lua-special-tbl| for more + details. -vim.val_idx *vim.val_idx* - Value index for tables representing |Float|s. A table representing - floating-point value 1.0 looks like this: > +vim.val_idx *vim.val_idx* + Value index for tables representing |Float|s. A table representing + floating-point value 1.0 looks like this: > { [vim.type_idx] = vim.types.float, [vim.val_idx] = 1.0, } -< See also |vim.type_idx| and |lua-special-tbl|. - -vim.types *vim.types* - Table with possible values for |vim.type_idx|. Contains two sets - of key-value pairs: first maps possible values for |vim.type_idx| - to human-readable strings, second maps human-readable type names to - values for |vim.type_idx|. Currently contains pairs for `float`, - `array` and `dictionary` types. - - Note: one must expect that values corresponding to `vim.types.float`, - `vim.types.array` and `vim.types.dictionary` fall under only two - following assumptions: - 1. Value may serve both as a key and as a value in a table. Given the - properties of Lua tables this basically means “value is not `nil`”. - 2. For each value in `vim.types` table `vim.types[vim.types[value]]` - is the same as `value`. - No other restrictions are put on types, and it is not guaranteed that - values corresponding to `vim.types.float`, `vim.types.array` and - `vim.types.dictionary` will not change or that `vim.types` table will - only contain values for these three types. +< See also |vim.type_idx| and |lua-special-tbl|. + +vim.types *vim.types* + Table with possible values for |vim.type_idx|. Contains two sets of + key-value pairs: first maps possible values for |vim.type_idx| to + human-readable strings, second maps human-readable type names to + values for |vim.type_idx|. Currently contains pairs for `float`, + `array` and `dictionary` types. + + Note: one must expect that values corresponding to `vim.types.float`, + `vim.types.array` and `vim.types.dictionary` fall under only two + following assumptions: + 1. Value may serve both as a key and as a value in a table. Given the + properties of Lua tables this basically means “value is not `nil`”. + 2. For each value in `vim.types` table `vim.types[vim.types[value]]` + is the same as `value`. + No other restrictions are put on types, and it is not guaranteed that + values corresponding to `vim.types.float`, `vim.types.array` and + `vim.types.dictionary` will not change or that `vim.types` table will + only contain values for these three types. ------------------------------------------------------------------------------ LUA-VIMSCRIPT BRIDGE *lua-vimscript* @@ -966,8 +964,8 @@ vim.env *vim.env* *lua-vim-optlocal* *lua-vim-setlocal* -In vimL, there is a succint and simple way to set options. For more -information, see |set-option|. In Lua, the corresponding method is `vim.opt`. +In Vimscript, there is an way to set options |set-option|. In Lua, the +corresponding method is `vim.opt`. `vim.opt` provides several conveniences for setting and controlling options from within Lua. @@ -975,18 +973,18 @@ from within Lua. Examples: ~ To set a boolean toggle: - In vimL: + In Vimscript: `set number` In Lua: `vim.opt.number = true` To set an array of values: - In vimL: + In Vimscript: `set wildignore=*.o,*.a,__pycache__` In Lua, there are two ways you can do this now. One is very similar to - the vimL way: + the Vimscript form: `vim.opt.wildignore = '*.o,*.a,__pycache__'` However, vim.opt also supports a more elegent way of setting @@ -1019,7 +1017,7 @@ from within Lua. vim.opt.wildignore:remove { "node_modules" } < To set a map of values: - In vimL: + In Vimscript: `set listchars=space:_,tab:>~` In Lua: diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 6851cd1511..5fdd5fc3c0 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -172,15 +172,16 @@ Using Vim scripts *using-scripts* For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:so* *:source* *load-vim-script* -:so[urce] {file} Runs |Ex| commands or Lua code (".lua" files) read - from {file}. +:[range]so[urce] [file] Runs |Ex| commands or Lua code (".lua" files) from + [file], or from the current buffer if no [file] is + given. Triggers the |SourcePre| autocommand. *:source!* -:so[urce]! {file} Runs |Normal-mode| commands read from {file}. When - used after |:global|, |:argdo|, |:windo|, |:bufdo|, in +:[range]so[urce]! {file} + Runs |Normal-mode| commands from {file}. When used + after |:global|, |:argdo|, |:windo|, |:bufdo|, in a loop or when another command follows the display won't be updated while executing the commands. - Cannot be used in the |sandbox|. *:ru* *:runtime* :ru[ntime][!] [where] {file} .. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 4dea053bc7..64824b2e3f 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -472,6 +472,7 @@ Compile-time features: X11 integration (see |x11-selection|) Eval: + Vim9script *js_encode()* *js_decode()* *v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead. diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua new file mode 100644 index 0000000000..33fa07ef4c --- /dev/null +++ b/runtime/lua/vim/diagnostic.lua @@ -0,0 +1,1150 @@ +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_move_pos(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 + vim.api.nvim_echo({"No more valid diagnostics to move to", "WarningMsg"}) + 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) + local prev = M.get_prev(opts) + if not prev then + return false + end + + return {prev.lnum, prev.col} +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( + 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) + local next = M.get_next(opts) + if not next then + return false + end + + return {next.lnum, next.col} +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( + 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..90c5872f11 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -674,7 +674,7 @@ function lsp.start_client(config) ---@param method (string) LSP method name ---@param params (table) The parameters for that method. function dispatch.notification(method, params) - local _ = log.debug() and log.debug('notification', method, params) + local _ = log.trace() and log.trace('notification', method, params) local handler = resolve_handler(method) if handler then -- Method name is provided here for convenience. @@ -688,13 +688,13 @@ function lsp.start_client(config) ---@param method (string) LSP method name ---@param params (table) The parameters for that method function dispatch.server_request(method, params) - local _ = log.debug() and log.debug('server_request', method, params) + local _ = log.trace() and log.trace('server_request', method, params) local handler = resolve_handler(method) if handler then - local _ = log.debug() and log.debug("server_request: found handler for", method) + local _ = log.trace() and log.trace("server_request: found handler for", method) return handler(nil, params, {method=method, client_id=client_id}) end - local _ = log.debug() and log.debug("server_request: no handler found for", method) + local _ = log.warn() and log.warn("server_request: no handler found for", method) return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end @@ -826,7 +826,7 @@ function lsp.start_client(config) -- TODO(ashkan) handle errors here. pcall(config.before_init, initialize_params, config) end - local _ = log.debug() and log.debug(log_prefix, "initialize_params", initialize_params) + local _ = log.trace() and log.trace(log_prefix, "initialize_params", initialize_params) rpc.request('initialize', initialize_params, function(init_err, result) assert(not init_err, tostring(init_err)) assert(result, "server sent empty result") @@ -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..01c675ba77 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> @@ -653,36 +462,13 @@ end --- - priority: Set the priority of the signs. --- - 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 - } - ) +function M.set_signs(diagnostics, bufnr, client_id, _, opts) + 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> @@ -706,35 +494,18 @@ end ---@param opts table: Configuration table: --- - 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) - ) +function M.set_underline(diagnostics, bufnr, client_id, _, opts) + 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> @@ -753,436 +524,75 @@ end --- - 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. -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 +function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts) + 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 ---@param 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()|. -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) +function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts) + 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..918666ab27 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 @@ -428,7 +434,7 @@ M['window/logMessage'] = function(_, result, ctx, _) log.error(message) elseif message_type == protocol.MessageType.Warning then log.warn(message) - elseif message_type == protocol.MessageType.Info then + elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then log.info(message) else log.debug(message) @@ -458,7 +464,7 @@ end -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do M[k] = function(err, result, ctx, config) - local _ = log.debug() and log.debug('default_handler', ctx.method, { + local _ = log.trace() and log.trace('default_handler', ctx.method, { err = err, result = result, ctx=vim.inspect(ctx), config = config }) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 5d2e396cc5..4597f1919a 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -14,7 +14,8 @@ log.levels = vim.deepcopy(vim.log.levels) -- Default log level is warn. local current_log_level = log.levels.WARN -local log_date_format = "%FT%H:%M:%S%z" +local log_date_format = "%F %H:%M:%S" +local format_func = function(arg) return vim.inspect(arg, {newline=''}) end do local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/" @@ -44,7 +45,7 @@ do end -- Start message for logging - logfile:write(string.format("[ START ] %s ] LSP logging initiated\n", os.date(log_date_format))) + logfile:write(string.format("[START][%s] LSP logging initiated\n", os.date(log_date_format))) for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. log[level] = levelnr @@ -67,14 +68,14 @@ do if levelnr < current_log_level then return false end if argc == 0 then return true end local info = debug.getinfo(2, "Sl") - local fileinfo = string.format("%s:%s", info.short_src, info.currentline) - local parts = { table.concat({"[", level, "]", os.date(log_date_format), "]", fileinfo, "]"}, " ") } + local header = string.format("[%s][%s] ...%s:%s", level, os.date(log_date_format), string.sub(info.short_src, #info.short_src - 15), info.currentline) + local parts = { header } for i = 1, argc do local arg = select(i, ...) if arg == nil then table.insert(parts, "nil") else - table.insert(parts, vim.inspect(arg, {newline=''})) + table.insert(parts, format_func(arg)) end end logfile:write(table.concat(parts, '\t'), "\n") @@ -104,6 +105,13 @@ function log.get_level() return current_log_level end +--- Sets formatting function used to format logs +---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting +function log.set_format_func(handle) + assert(handle == vim.inspect or type(handle) == 'function', "handle must be a function") + format_func = handle +end + --- Checks whether the level is sufficient for logging. ---@param level number log level ---@returns (bool) true if would log, false if not diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index eedb708118..7f31bbdf75 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -392,7 +392,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) ---@param payload (table) Converted into a JSON string, see |json_encode()| ---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. local function encode_and_send(payload) - local _ = log.debug() and log.debug("rpc.send.payload", payload) + local _ = log.debug() and log.debug("rpc.send", payload) if handle == nil or handle:is_closing() then return false end -- TODO(ashkan) remove this once we have a Lua json_encode schedule(function() @@ -493,7 +493,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) -- on_error(client_errors.INVALID_SERVER_JSON, err) return end - local _ = log.debug() and log.debug("decoded", decoded) + local _ = log.debug() and log.debug("rpc.receive", decoded) if type(decoded.method) == 'string' and decoded.id then -- Server Request 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 |