diff options
-rwxr-xr-x | ci/before_script.sh | 6 | ||||
-rw-r--r-- | ci/common/build.sh | 9 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 43 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 97 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 17 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 211 | ||||
-rwxr-xr-x | scripts/vim-patch.sh | 6 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 4 | ||||
-rw-r--r-- | src/nvim/ascii.h | 2 | ||||
-rw-r--r-- | src/nvim/change.c | 4 | ||||
-rw-r--r-- | src/nvim/event/stream.c | 5 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 4 | ||||
-rw-r--r-- | src/nvim/extmark.c | 9 | ||||
-rw-r--r-- | src/nvim/fileio.c | 5 | ||||
-rw-r--r-- | src/nvim/fold.c | 219 | ||||
-rw-r--r-- | src/nvim/lua/treesitter.c | 27 | ||||
-rw-r--r-- | src/nvim/memline.c | 54 | ||||
-rw-r--r-- | src/nvim/option.c | 393 | ||||
-rw-r--r-- | src/nvim/os/signal.c | 33 | ||||
-rw-r--r-- | src/nvim/syntax.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.c | 5 | ||||
-rw-r--r-- | src/nvim/testdir/test_messages.vim | 3 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 170 | ||||
-rw-r--r-- | src/nvim/version.c | 6 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 74 | ||||
-rw-r--r-- | test/functional/terminal/mouse_spec.lua | 34 |
28 files changed, 832 insertions, 634 deletions
diff --git a/ci/before_script.sh b/ci/before_script.sh index a0e87adb9e..1759dbe942 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -10,6 +10,12 @@ fi CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" +# Enable ipv6 on Travis. ref: a39c8b7ce30d +if ! test "${TRAVIS_OS_NAME}" = osx ; then + echo "before_script.sh: enable ipv6" + sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 +fi + # Test some of the configuration variables. if [[ -n "${GCOV}" ]] && [[ ! $(type -P "${GCOV}") ]]; then echo "\$GCOV: '${GCOV}' is not executable." diff --git a/ci/common/build.sh b/ci/common/build.sh index 02e1110a15..0024f2cbd5 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -86,12 +86,3 @@ build_nvim() { cd "${TRAVIS_BUILD_DIR}" } - -macos_rvm_dance() { - # neovim-ruby gem requires a ruby newer than the macOS default. - source ~/.rvm/scripts/rvm - rvm get stable --auto-dotfiles - rvm reload - rvm use 2.2.5 - rvm use -} diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 2f5427f6fc..2d0bba0ffb 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -64,7 +64,7 @@ FAQ *lsp-faq* - Q: How to force-reload LSP? A: Stop all clients, then reload the buffer. > - :lua vim.lsp.stop_all_clients() + :lua vim.lsp.stop_client(vim.lsp.get_active_clients()) :edit - Q: Why isn't completion working? @@ -752,9 +752,6 @@ npcall({fn}, {...}) *vim.lsp.buf.npcall()* ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()* TODO: Documentation -peek_definition() *vim.lsp.buf.peek_definition()* - TODO: Documentation - *vim.lsp.buf.range_formatting()* range_formatting({options}, {start_pos}, {end_pos}) TODO: Documentation @@ -915,6 +912,21 @@ apply_text_edits({text_edits}, {bufnr}) apply_workspace_edit({workspace_edit}) TODO: Documentation + *vim.lsp.util.diagnostics_by_buf* +diagnostics_by_buf + A table containing diagnostics grouped by buf. + + {<bufnr>: {diagnostics}} + + {diagnostics} is an array of diagnostics. + + By default this is populated by the + `textDocument/publishDiagnostics` callback via + |vim.lsp.util.buf_diagnostics_save_positions|. + + It contains entries for active buffers. Once a buffer is + detached the entries for it are discarded. + buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()* TODO: Documentation @@ -945,9 +957,14 @@ buf_diagnostics_count({kind}) buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* TODO: Documentation - *vim.lsp.util.buf_diagnostics_save_positions()* + *vim.lsp.util.buf_diagnostics_save()* buf_diagnostics_save_positions({bufnr}, {diagnostics}) - TODO: Documentation + Stores the diagnostics into |vim.lsp.util.diagnostics_by_buf| + + Parameters: ~ + {bufr} bufnr for which the diagnostics are for. + {diagnostics} Diagnostics[] received from the + langauge server. *vim.lsp.util.buf_diagnostics_underline()* buf_diagnostics_underline({bufnr}, {diagnostics}) @@ -960,12 +977,12 @@ buf_diagnostics_virtual_text({bufnr}, {diagnostics}) *vim.lsp.util.buf_diagnostics_signs()* buf_diagnostics_signs({bufnr}, {diagnostics}) Place signs for each diagnostic in the sign column. - Sign characters can be customized with the following options: + Sign characters can be customized with the following commands: > -let g:LspDiagnosticsErrorSign = 'E' -let g:LspDiagnosticsWarningSign = 'W' -let g:LspDiagnosticsInformationSign = 'I' -let g:LspDiagnosticsHintSign = 'H' +sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= +sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= +sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= +sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= < @@ -1033,10 +1050,6 @@ npcall({fn}, {...}) *vim.lsp.util.npcall()* ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()* TODO: Documentation - *vim.lsp.util.open_floating_peek_preview()* -open_floating_peek_preview({bufnr}, {start}, {finish}, {opts}) - TODO: Documentation - *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {filetype}, {opts}) TODO: Documentation diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index afff4d9900..c7de2df25f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -198,6 +198,7 @@ local function text_document_did_open_handler(bufnr, client) } } client.notify('textDocument/didOpen', params) + util.buf_versions[bufnr] = params.textDocument.version end --- LSP client object. @@ -722,6 +723,7 @@ function lsp.buf_attach_client(bufnr, client_id) client.notify('textDocument/didClose', params) end end) + util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil end; -- TODO if we know all of the potential clients ahead of time, then we @@ -1015,5 +1017,21 @@ function lsp.get_log_path() return log.get_filename() end +local function define_default_sign(name, properties) + if not vim.fn.sign_getdefined(name) then + vim.fn.sign_define(name, properties) + end +end + +-- Define the LspDiagnostics signs if they're not defined already. +local function define_default_lsp_diagnostics_signs() + define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsError', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarning', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformation', linehl='', numhl=''}) + define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHint', linehl='', numhl=''}) +end + +define_default_lsp_diagnostics_signs() + return lsp -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fc9e10cb73..587d1f52e9 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -32,12 +32,6 @@ function M.hover() request('textDocument/hover', params) end -function M.peek_definition() - local params = util.make_position_params() - request('textDocument/peekDefinition', params) -end - - function M.declaration() local params = util.make_position_params() request('textDocument/declaration', params) diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index c403a3fd51..bd2cbf1ea7 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -50,6 +50,8 @@ end M['textDocument/references'] = function(_, _, result) if not result then return end util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") end M['textDocument/documentSymbol'] = function(_, _, result, _, bufnr) @@ -112,11 +114,20 @@ local function location_callback(_, method, result) local _ = log.info() and log.info(method, 'No location found') return nil end - util.jump_to_location(result[1]) - if #result > 1 then - util.set_qflist(util.locations_to_items(result)) - api.nvim_command("copen") - api.nvim_command("wincmd p") + + -- textDocument/definition can return Location or Location[] + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition + + if vim.tbl_islist(result) then + util.jump_to_location(result[1]) + + if #result > 1 then + util.set_qflist(util.locations_to_items(result)) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end + else + util.jump_to_location(result) end end @@ -125,72 +136,13 @@ M['textDocument/definition'] = location_callback M['textDocument/typeDefinition'] = location_callback M['textDocument/implementation'] = location_callback ---- Convert SignatureHelp response to preview contents. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp -local function signature_help_to_preview_contents(input) - if not input.signatures then - return - end - --The active signature. If omitted or the value lies outside the range of - --`signatures` the value defaults to zero or is ignored if `signatures.length - --=== 0`. Whenever possible implementors should make an active decision about - --the active signature and shouldn't rely on a default value. - local contents = {} - local active_signature = input.activeSignature or 0 - -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #input.signatures then - active_signature = 0 - end - local signature = input.signatures[active_signature + 1] - if not signature then - return - end - vim.list_extend(contents, vim.split(signature.label, '\n', true)) - if signature.documentation then - util.convert_input_to_markdown_lines(signature.documentation, contents) - end - if input.parameters then - local active_parameter = input.activeParameter or 0 - -- If the activeParameter is not inside the valid range, then clip it. - if active_parameter >= #input.parameters then - active_parameter = 0 - end - local parameter = signature.parameters and signature.parameters[active_parameter] - if parameter then - --[=[ - --Represents a parameter of a callable-signature. A parameter can - --have a label and a doc-comment. - interface ParameterInformation { - --The label of this parameter information. - -- - --Either a string or an inclusive start and exclusive end offsets within its containing - --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 - --string representation as `Position` and `Range` does. - -- - --*Note*: a label of type string should be a substring of its containing signature label. - --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. - label: string | [number, number]; - --The human-readable doc-comment of this parameter. Will be shown - --in the UI but can be omitted. - documentation?: string | MarkupContent; - } - --]=] - -- TODO highlight parameter - if parameter.documentation then - util.convert_input_to_markdown_lines(parameter.documentation, contents) - end - end - end - return contents -end - M['textDocument/signatureHelp'] = function(_, method, result) util.focusable_preview(method, function() if not (result and result.signatures and result.signatures[1]) then return { 'No signature available' } end -- TODO show popup when signatures is empty? - local lines = signature_help_to_preview_contents(result) + local lines = util.convert_signature_help_to_markdown_lines(result) lines = util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then return { 'No signature available' } @@ -199,21 +151,6 @@ M['textDocument/signatureHelp'] = function(_, method, result) end) end -M['textDocument/peekDefinition'] = function(_, _, result, _) - if not (result and result[1]) then return end - local loc = result[1] - local bufnr = vim.uri_to_bufnr(loc.uri) or error("not found: "..tostring(loc.uri)) - local start = loc.range.start - local finish = loc.range["end"] - util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) - local headbuf = util.open_floating_preview({"Peek:"}, nil, { - offset_y = -(finish.line - start.line); - width = finish.character - start.character + 2; - }) - -- TODO(ashkan) change highlight group? - api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) -end - M['textDocument/documentHighlight'] = function(_, _, result, _) if not result then return end local bufnr = api.nvim_get_current_buf() diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 74d73da31f..dad1dc11f1 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -140,14 +140,23 @@ local function format_rpc_error(err) validate { err = { err, 't' }; } - local code_name = assert(protocol.ErrorCodes[err.code], "err.code is invalid") - local message_parts = {"RPC", code_name} + + -- There is ErrorCodes in the LSP specification, + -- but in ResponseError.code it is not used and the actual type is number. + local code + if protocol.ErrorCodes[err.code] then + code = string.format("code_name = %s,", protocol.ErrorCodes[err.code]) + else + code = string.format("code_name = unknown, code = %s,", err.code) + end + + local message_parts = {"RPC[Error]", code} if err.message then - table.insert(message_parts, "message = ") + table.insert(message_parts, "message =") table.insert(message_parts, string.format("%q", err.message)) end if err.data then - table.insert(message_parts, "data = ") + table.insert(message_parts, "data =") table.insert(message_parts, vim.inspect(err.data)) end return table.concat(message_parts, ' ') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 763e9719a7..53e2240ff5 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -6,6 +6,31 @@ local list_extend = vim.list_extend local M = {} +--- Diagnostics received from the server via `textDocument/publishDiagnostics` +-- by buffer. +-- +-- {<bufnr>: {diagnostics}} +-- +-- This contains only entries for active buffers. Entries for detached buffers +-- are discarded. +-- +-- If you override the `textDocument/publishDiagnostic` callback, +-- this will be empty unless you call `buf_diagnostics_save_positions`. +-- +-- +-- Diagnostic is: +-- +-- { +-- range: Range +-- message: string +-- severity?: DiagnosticSeverity +-- code?: number | string +-- source?: string +-- tags?: DiagnosticTag[] +-- relatedInformation?: DiagnosticRelatedInformation[] +-- } +M.diagnostics_by_buf = {} + local split = vim.split local function split_lines(value) return split(value, '\n', true) @@ -134,8 +159,8 @@ end function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) - -- TODO(ashkan) check this is correct. - if (M.buf_versions[bufnr] or 0) > text_document.version then + -- `VersionedTextDocumentIdentifier`s version may be nil https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier + if text_document.version ~= nil and M.buf_versions[bufnr] > text_document.version then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -157,11 +182,23 @@ local function sort_completion_items(items) end end +-- Returns text that should be inserted when selecting completion item. The precedence is as follows: +-- textEdit.newText > insertText > label +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +local function get_completion_word(item) + if item.textEdit ~= nil and item.textEdit.newText ~= nil then + return item.textEdit.newText + elseif item.insertText ~= nil then + return item.insertText + end + return item.label +end + -- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. -- So we exclude completion candidates whose prefix does not match. local function remove_unmatch_completion_items(items, prefix) return vim.tbl_filter(function(item) - local word = item.insertText or item.label + local word = get_completion_word(item) return vim.startswith(word, prefix) end, items) end @@ -193,7 +230,7 @@ function M.text_document_completion_list_to_complete_items(result, prefix) end end - local word = completion_item.insertText or completion_item.label + local word = get_completion_word(completion_item) table.insert(matches, { word = word, abbr = completion_item.label, @@ -275,6 +312,65 @@ function M.convert_input_to_markdown_lines(input, contents) return contents end +--- Convert SignatureHelp response to markdown lines. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +function M.convert_signature_help_to_markdown_lines(signature_help) + if not signature_help.signatures then + return + end + --The active signature. If omitted or the value lies outside the range of + --`signatures` the value defaults to zero or is ignored if `signatures.length + --=== 0`. Whenever possible implementors should make an active decision about + --the active signature and shouldn't rely on a default value. + local contents = {} + local active_signature = signature_help.activeSignature or 0 + -- If the activeSignature is not inside the valid range, then clip it. + if active_signature >= #signature_help.signatures then + active_signature = 0 + end + local signature = signature_help.signatures[active_signature + 1] + if not signature then + return + end + vim.list_extend(contents, vim.split(signature.label, '\n', true)) + if signature.documentation then + M.convert_input_to_markdown_lines(signature.documentation, contents) + end + if signature_help.parameters then + local active_parameter = signature_help.activeParameter or 0 + -- If the activeParameter is not inside the valid range, then clip it. + if active_parameter >= #signature_help.parameters then + active_parameter = 0 + end + local parameter = signature.parameters and signature.parameters[active_parameter] + if parameter then + --[=[ + --Represents a parameter of a callable-signature. A parameter can + --have a label and a doc-comment. + interface ParameterInformation { + --The label of this parameter information. + -- + --Either a string or an inclusive start and exclusive end offsets within its containing + --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + --string representation as `Position` and `Range` does. + -- + --*Note*: a label of type string should be a substring of its containing signature label. + --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + label: string | [number, number]; + --The human-readable doc-comment of this parameter. Will be shown + --in the UI but can be omitted. + documentation?: string | MarkupContent; + } + --]=] + -- TODO highlight parameter + if parameter.documentation then + M.convert_input_help_to_markdown_lines(parameter.documentation, contents) + end + end + end + return contents +end + function M.make_floating_popup_options(width, height, opts) validate { opts = { opts, 't', true }; @@ -526,31 +622,6 @@ function M.open_floating_preview(contents, filetype, opts) return floating_bufnr, floating_winnr end -local function validate_lsp_position(pos) - validate { pos = {pos, 't'} } - validate { - line = {pos.line, 'n'}; - character = {pos.character, 'n'}; - } - return true -end - -function M.open_floating_peek_preview(bufnr, start, finish, opts) - validate { - bufnr = {bufnr, 'n'}; - start = {start, validate_lsp_position, 'valid start Position'}; - finish = {finish, validate_lsp_position, 'valid finish Position'}; - opts = { opts, 't', true }; - } - local width = math.max(finish.character - start.character + 1, 1) - local height = math.max(finish.line - start.line + 1, 1) - local floating_winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts)) - api.nvim_win_set_cursor(floating_winnr, {start.line+1, start.character}) - api.nvim_command("autocmd CursorMoved * ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") - return floating_winnr -end - - local function highlight_range(bufnr, ns, hiname, start, finish) if start[1] == finish[1] then -- TODO care about encoding here since this is in byte index? @@ -565,8 +636,6 @@ local function highlight_range(bufnr, ns, hiname, start, finish) end do - local all_buffer_diagnostics = {} - local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") local reference_ns = api.nvim_create_namespace("vim_lsp_references") local sign_ns = 'vim_lsp_signs' @@ -622,13 +691,12 @@ do -- if #marks == 0 then -- return -- end - -- local buffer_diagnostics = all_buffer_diagnostics[bufnr] local lines = {"Diagnostics:"} local highlights = {{0, "Bold"}} - local buffer_diagnostics = all_buffer_diagnostics[bufnr] + local buffer_diagnostics = M.diagnostics_by_buf[bufnr] if not buffer_diagnostics then return end - local line_diagnostics = buffer_diagnostics[line] + local line_diagnostics = M.diagnostics_group_by_line(buffer_diagnostics)[line] if not line_diagnostics then return end for i, diagnostic in ipairs(line_diagnostics) do @@ -639,6 +707,7 @@ do -- TODO(ashkan) make format configurable? local prefix = string.format("%d. ", i) local hiname = severity_highlights[diagnostic.severity] + assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) local message_lines = split_lines(diagnostic.message) table.insert(lines, prefix..message_lines[1]) table.insert(highlights, {#prefix + 1, hiname}) @@ -656,6 +725,8 @@ do return popup_bufnr, winnr end + --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf + -- function M.buf_diagnostics_save_positions(bufnr, diagnostics) validate { bufnr = {bufnr, 'n', true}; @@ -664,28 +735,15 @@ do if not diagnostics then return end bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr - if not all_buffer_diagnostics[bufnr] then + if not M.diagnostics_by_buf[bufnr] then -- Clean up our data when the buffer unloads. api.nvim_buf_attach(bufnr, false, { on_detach = function(b) - all_buffer_diagnostics[b] = nil + M.diagnostics_by_buf[b] = nil end }) end - all_buffer_diagnostics[bufnr] = {} - local buffer_diagnostics = all_buffer_diagnostics[bufnr] - - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range.start - -- local mark_id = api.nvim_buf_set_extmark(bufnr, diagnostic_ns, 0, start.line, 0, {}) - -- buffer_diagnostics[mark_id] = diagnostic - local line_diagnostics = buffer_diagnostics[start.line] - if not line_diagnostics then - line_diagnostics = {} - buffer_diagnostics[start.line] = line_diagnostics - end - table.insert(line_diagnostics, diagnostic) - end + M.diagnostics_by_buf[bufnr] = diagnostics end function M.buf_diagnostics_underline(bufnr, diagnostics) @@ -729,15 +787,26 @@ do end end - function M.buf_diagnostics_virtual_text(bufnr, diagnostics) - local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] - if not buffer_line_diagnostics then - M.buf_diagnostics_save_positions(bufnr, diagnostics) + function M.diagnostics_group_by_line(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) end - buffer_line_diagnostics = all_buffer_diagnostics[bufnr] - if not buffer_line_diagnostics then + return diagnostics_by_line + end + + function M.buf_diagnostics_virtual_text(bufnr, diagnostics) + if not diagnostics then return end + local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics) for line, line_diags in pairs(buffer_line_diagnostics) do local virt_texts = {} for i = 1, #line_diags - 1 do @@ -749,31 +818,29 @@ do api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) end end + function M.buf_diagnostics_count(kind) local bufnr = vim.api.nvim_get_current_buf() - local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] - if not buffer_line_diagnostics then return end + local diagnostics = M.diagnostics_by_buf[bufnr] + if not diagnostics then return end local count = 0 - for _, line_diags in pairs(buffer_line_diagnostics) do - for _, diag in ipairs(line_diags) do - if protocol.DiagnosticSeverity[kind] == diag.severity then count = count + 1 end + for _, diagnostic in pairs(diagnostics) do + if protocol.DiagnosticSeverity[kind] == diagnostic.severity then + count = count + 1 end end return count end - function M.buf_diagnostics_signs(bufnr, diagnostics) - vim.fn.sign_define('LspDiagnosticsErrorSign', {text=vim.g['LspDiagnosticsErrorSign'] or 'E', texthl='LspDiagnosticsError', linehl='', numhl=''}) - vim.fn.sign_define('LspDiagnosticsWarningSign', {text=vim.g['LspDiagnosticsWarningSign'] or 'W', texthl='LspDiagnosticsWarning', linehl='', numhl=''}) - vim.fn.sign_define('LspDiagnosticsInformationSign', {text=vim.g['LspDiagnosticsInformationSign'] or 'I', texthl='LspDiagnosticsInformation', linehl='', numhl=''}) - vim.fn.sign_define('LspDiagnosticsHintSign', {text=vim.g['LspDiagnosticsHintSign'] or 'H', texthl='LspDiagnosticsHint', linehl='', numhl=''}) + local diagnostic_severity_map = { + [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; + [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; + [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; + [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; + } + + function M.buf_diagnostics_signs(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do - local diagnostic_severity_map = { - [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; - [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; - [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; - [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; - } vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) end end diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index e50eb307e5..dc62c9e744 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -5,6 +5,12 @@ set -u # Use privileged mode, which e.g. skips using CDPATH. set -p +# Ensure that the user has a bash that supports -A +if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then + echo "This script requires bash version 3 or later (you have ${BASH_VERSION})." >&2 + exit 1 +fi + readonly NVIM_SOURCE_DIR="${NVIM_SOURCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" readonly VIM_SOURCE_DIR_DEFAULT="${NVIM_SOURCE_DIR}/.vim-src" readonly VIM_SOURCE_DIR="${VIM_SOURCE_DIR:-${VIM_SOURCE_DIR_DEFAULT}}" diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 048b937136..df3a263dcf 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -63,8 +63,8 @@ #define FIXED_TEMP_ARRAY(name, fixsize) \ Array name = ARRAY_DICT_INIT; \ Object name##__items[fixsize]; \ - args.size = fixsize; \ - args.items = name##__items; \ + name.size = fixsize; \ + name.items = name##__items; \ #define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index 2397af27cc..31423e79af 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -31,7 +31,9 @@ #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" #define DCS 0x90 // Device Control String +#define DCS_STR "\033P" #define STERM 0x9c // String Terminator +#define STERM_STR "\033\\" #define POUND 0xA3 diff --git a/src/nvim/change.c b/src/nvim/change.c index a341b8fce1..51afb40b40 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -361,8 +361,8 @@ void changed_bytes(linenr_T lnum, colnr_T col) /// insert/delete bytes at column /// -/// Like changed_bytes() but also adjust extmark for "added" bytes. -/// When "added" is negative text was deleted. +/// Like changed_bytes() but also adjust extmark for "new" bytes. +/// When "new" is negative text was deleted. static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new) { if (curbuf_splice_pending == 0) { diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index d1a53fa4b6..0e87f7c6c1 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -16,6 +16,11 @@ # include "event/stream.c.generated.h" #endif +// For compatbility with libuv < 1.19.0 (tested on 1.18.0) +#if UV_VERSION_MINOR < 19 +#define uv_stream_get_write_queue_size(stream) stream->write_queue_size +#endif + /// Sets the stream associated with `fd` to "blocking" mode. /// /// @return `0` on success, or libuv error code on failure. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index f18395e6cc..8a0f2e634a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -882,7 +882,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { - foldMoveRange(&win->w_folds, line1, line2, dest); + foldMoveRange(win, &win->w_folds, line1, line2, dest); } } curbuf->b_op_start.lnum = dest - num_lines + 1; @@ -891,7 +891,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { - foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); + foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2); } } curbuf->b_op_start.lnum = dest + 1; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 38d111f2aa..1457a1172d 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -578,6 +578,15 @@ void extmark_splice(buf_T *buf, } } +void extmark_splice_cols(buf_T *buf, + int start_row, colnr_T start_col, + colnr_T old_col, colnr_T new_col, + ExtmarkOp undo) +{ + extmark_splice(buf, start_row, start_col, + 0, old_col, + 0, new_col, undo); +} void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 88e9390c65..8da1c94219 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -62,6 +62,11 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ +// For compatibility with libuv < 1.20.0 (tested on 1.18.0) +#ifndef UV_FS_COPYFILE_FICLONE +#define UV_FS_COPYFILE_FICLONE 0 +#endif + // // The autocommands are stored in a list for each event. // Autocommands for the same pattern, that are consecutive, are joined diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 405f15d751..61a85171e8 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -642,15 +642,17 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end) /* We want the new fold to be closed. If it would remain open because * of using 'foldlevel', need to adjust fd_flags of containing folds. */ - if (use_level && !closed && level < curwin->w_p_fdl) + if (use_level && !closed && level < wp->w_p_fdl) { closeFold(start, 1L); - if (!use_level) - curwin->w_fold_manual = true; + } + if (!use_level) { + wp->w_fold_manual = true; + } fp->fd_flags = FD_CLOSED; fp->fd_small = kNone; - /* redraw */ - changed_window_setting(); + // redraw + changed_window_setting_win(wp); } } @@ -711,7 +713,7 @@ void deleteFold( lnum = found_fp->fd_top + found_fp->fd_len + found_off; if (foldmethodIsManual(wp)) { - deleteFoldEntry(found_ga, + deleteFoldEntry(wp, found_ga, (int)(found_fp - (fold_T *)found_ga->ga_data), recursive); } else { @@ -724,22 +726,24 @@ void deleteFold( if (!did_one) { parseMarker(wp); } - deleteFoldMarkers(found_fp, recursive, found_off); + deleteFoldMarkers(wp, found_fp, recursive, found_off); } did_one = true; - /* redraw window */ - changed_window_setting(); + // redraw window + changed_window_setting_win(wp); } } if (!did_one) { EMSG(_(e_nofold)); - /* Force a redraw to remove the Visual highlighting. */ - if (had_visual) - redraw_curbuf_later(INVERTED); - } else - /* Deleting markers may make cursor column invalid. */ - check_cursor_col(); + // Force a redraw to remove the Visual highlighting. + if (had_visual) { + redraw_buf_later(wp->w_buffer, INVERTED); + } + } else { + // Deleting markers may make cursor column invalid + check_cursor_col_win(wp); + } if (last_lnum > 0) { // TODO(teto): pass the buffer @@ -751,7 +755,7 @@ void deleteFold( // the modification of the *first* line of the fold, but we send through a // notification that includes every line that was part of the fold int64_t num_changed = last_lnum - first_lnum; - buf_updates_send_changes(curbuf, first_lnum, num_changed, + buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed, num_changed, true); } } @@ -1303,7 +1307,7 @@ static void foldOpenNested(fold_T *fpr) // Delete fold "idx" from growarray "gap". // When "recursive" is true also delete all the folds contained in it. // When "recursive" is false contained folds are moved one level up. -static void deleteFoldEntry(garray_T *const gap, const int idx, +static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, const bool recursive) { fold_T *fp = (fold_T *)gap->ga_data + idx; @@ -1369,13 +1373,17 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long line2 = line1 - amount_after - 1; /* If appending a line in Insert mode, it should be included in the fold * just above the line. */ - if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) - --line1; - foldMarkAdjustRecurse(&wp->w_folds, line1, line2, amount, amount_after); + if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) { + line1--; + } + foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after); } -/* foldMarkAdjustRecurse() {{{2 */ -static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, long amount, long amount_after) +// foldMarkAdjustRecurse() {{{2 +static void foldMarkAdjustRecurse( + win_T *wp, garray_T *gap, + linenr_T line1, linenr_T line2, long amount, long amount_after +) { fold_T *fp; linenr_T last; @@ -1423,7 +1431,7 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, // 4. fold completely contained in range if (amount == MAXLNUM) { // Deleting lines: delete the fold completely - deleteFoldEntry(gap, i, true); + deleteFoldEntry(wp, gap, i, true); i--; // adjust index for deletion fp--; } else { @@ -1431,9 +1439,9 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, } } else { if (fp->fd_top < top) { - /* 2 or 3: need to correct nested folds too */ - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, amount_after); + // 2 or 3: need to correct nested folds too + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, + line2 - fp->fd_top, amount, amount_after); if (last <= line2) { /* 2. fold contains line1, line2 is below fold */ if (amount == MAXLNUM) @@ -1448,13 +1456,13 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, /* 5. fold is below line1 and contains line2; need to * correct nested folds too */ if (amount == MAXLNUM) { - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, amount, amount_after + (fp->fd_top - top)); fp->fd_len -= line2 - fp->fd_top + 1; fp->fd_top = line1; } else { - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, amount, amount_after - amount); fp->fd_len += amount_after - amount; @@ -1497,16 +1505,18 @@ static int getDeepestNestingRecurse(garray_T *gap) /// Check if a fold is closed and update the info needed to check nested folds. /// /// @param[in,out] use_levelp true: outer fold had FD_LEVEL +/// @param[in,out] fp fold to check /// @param level folding depth -/// @param[out] maybe_smallp TRUE: outer this had fd_small == kNone +/// @param[out] maybe_smallp true: outer this had fd_small == kNone /// @param lnum_off line number offset for fp->fd_top +/// @return true if fold is closed static bool check_closed( win_T *const wp, fold_T *const fp, - bool *const use_levelp, // true: outer fold had FD_LEVEL - const int level, // folding depth - bool *const maybe_smallp, // true: outer this had fd_small == kNone - const linenr_T lnum_off // line number offset for fp->fd_top + bool *const use_levelp, + const int level, + bool *const maybe_smallp, + const linenr_T lnum_off ) { bool closed = false; @@ -1552,13 +1562,13 @@ checkSmall( // Mark any nested folds to maybe-small setSmallMaybe(&fp->fd_nested); - if (fp->fd_len > curwin->w_p_fml) { + if (fp->fd_len > wp->w_p_fml) { fp->fd_small = kFalse; } else { int count = 0; for (int n = 0; n < fp->fd_len; n++) { count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); - if (count > curwin->w_p_fml) { + if (count > wp->w_p_fml) { fp->fd_small = kFalse; return; } @@ -1604,7 +1614,7 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end) // u_save() is unable to save the buffer line, but we send the // nvim_buf_lines_event anyway since it won't do any harm. int64_t num_changed = 1 + end - start; - buf_updates_send_changes(curbuf, start, num_changed, num_changed, true); + buf_updates_send_changes(buf, start, num_changed, num_changed, true); } /* foldAddMarker() {{{2 */ @@ -1621,7 +1631,7 @@ static void foldAddMarker( bool line_is_comment = false; // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end - line = ml_get(lnum); + line = ml_get_buf(buf, lnum, false); size_t line_len = STRLEN(line); size_t added = 0; @@ -1640,11 +1650,10 @@ static void foldAddMarker( STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); added = markerlen + STRLEN(cms)-2; } - ml_replace(lnum, newline, false); + ml_replace_buf(buf, lnum, newline, false); if (added) { - extmark_splice(buf, (int)lnum-1, (int)line_len, - 0, 0, - 0, (int)added, kExtmarkUndo); + extmark_splice_cols(buf, (int)lnum-1, (int)line_len, + 0, (int)added, kExtmarkUndo); } } } @@ -1655,20 +1664,22 @@ static void foldAddMarker( */ static void deleteFoldMarkers( + win_T *wp, fold_T *fp, int recursive, linenr_T lnum_off // offset for fp->fd_top ) { if (recursive) { - for (int i = 0; i < fp->fd_nested.ga_len; ++i) { - deleteFoldMarkers((fold_T *)fp->fd_nested.ga_data + i, TRUE, + for (int i = 0; i < fp->fd_nested.ga_len; i++) { + deleteFoldMarkers(wp, (fold_T *)fp->fd_nested.ga_data + i, true, lnum_off + fp->fd_top); } } - foldDelMarker(fp->fd_top + lnum_off, curwin->w_p_fmr, foldstartmarkerlen); - foldDelMarker(fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker, - foldendmarkerlen); + foldDelMarker(wp->w_buffer, fp->fd_top+lnum_off, wp->w_p_fmr, + foldstartmarkerlen); + foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1, + foldendmarker, foldendmarkerlen); } // foldDelMarker() {{{2 @@ -1677,18 +1688,20 @@ deleteFoldMarkers( // Delete 'commentstring' if it matches. // If the marker is not found, there is no error message. Could be a missing // close-marker. -static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen) +static void foldDelMarker( + buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen +) { char_u *newline; - char_u *cms = curbuf->b_p_cms; + char_u *cms = buf->b_p_cms; char_u *cms2; // end marker may be missing and fold extends below the last line - if (lnum > curbuf->b_ml.ml_line_count) { + if (lnum > buf->b_ml.ml_line_count) { return; } - char_u *line = ml_get(lnum); - for (char_u *p = line; *p != NUL; ++p) { + char_u *line = ml_get_buf(buf, lnum, false); + for (char_u *p = line; *p != NUL; p++) { if (STRNCMP(p, marker, markerlen) != 0) { continue; } @@ -1712,10 +1725,10 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen) assert(p >= line); memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); - ml_replace(lnum, newline, false); - extmark_splice(curbuf, (int)lnum-1, (int)(p - line), - 0, (int)len, - 0, 0, kExtmarkUndo); + ml_replace_buf(buf, lnum, newline, false); + extmark_splice_cols(buf, (int)lnum-1, (int)(p - line), + (int)len, + 0, kExtmarkUndo); } break; } @@ -1903,13 +1916,12 @@ void foldtext_cleanup(char_u *str) /* foldUpdateIEMS() {{{2 */ /* * Update the folding for window "wp", at least from lines "top" to "bot". - * Return TRUE if any folds did change. * IEMS = "Indent Expr Marker Syntax" */ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) { fline_T fline; - void (*getlevel)(fline_T *); + LevelGetter getlevel = NULL; fold_T *fp; /* Avoid problems when being called recursively. */ @@ -2091,8 +2103,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) } } - /* There can't be any folds from start until end now. */ - foldRemove(&wp->w_folds, start, end); + // There can't be any folds from start until end now. + foldRemove(wp, &wp->w_folds, start, end); /* If some fold changed, need to redraw and position cursor. */ if (fold_changed && wp->w_p_fen) @@ -2272,12 +2284,12 @@ static linenr_T foldUpdateIEMSRecurse( if (fp->fd_top > firstlnum) { // We will move the start of this fold up, hence we move all // nested folds (with relative line numbers) down. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)0, (linenr_T)MAXLNUM, (long)(fp->fd_top - firstlnum), 0L); } else { // Will move fold down, move nested folds relatively up. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)0, (long)(firstlnum - fp->fd_top - 1), (linenr_T)MAXLNUM, @@ -2307,10 +2319,10 @@ static linenr_T foldUpdateIEMSRecurse( breakstart = flp->lnum; breakend = flp->lnum; } - foldRemove(&fp->fd_nested, breakstart - fp->fd_top, + foldRemove(flp->wp, &fp->fd_nested, breakstart - fp->fd_top, breakend - fp->fd_top); i = (int)(fp - (fold_T *)gap->ga_data); - foldSplit(gap, i, breakstart, breakend - 1); + foldSplit(flp->wp->w_buffer, gap, i, breakstart, breakend - 1); fp = (fold_T *)gap->ga_data + i + 1; /* If using the "marker" or "syntax" method, we * need to continue until the end of the fold is @@ -2326,7 +2338,7 @@ static linenr_T foldUpdateIEMSRecurse( if (i != 0) { fp2 = fp - 1; if (fp2->fd_top + fp2->fd_len == fp->fd_top) { - foldMerge(fp2, gap, fp); + foldMerge(flp->wp, fp2, gap, fp); fp = fp2; } } @@ -2337,12 +2349,13 @@ static linenr_T foldUpdateIEMSRecurse( // A fold that starts at or after startlnum and stops // before the new fold must be deleted. Continue // looking for the next one. - deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true); + deleteFoldEntry(flp->wp, gap, + (int)(fp - (fold_T *)gap->ga_data), true); } else { /* A fold has some lines above startlnum, truncate it * to stop just above startlnum. */ fp->fd_len = startlnum - fp->fd_top; - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse(flp->wp, &fp->fd_nested, (linenr_T)fp->fd_len, (linenr_T)MAXLNUM, (linenr_T)MAXLNUM, 0L); fold_changed = true; @@ -2471,8 +2484,8 @@ static linenr_T foldUpdateIEMSRecurse( // Delete contained folds from the end of the last one found until where // we stopped looking. - foldRemove(&fp->fd_nested, startlnum2 - fp->fd_top, - flp->lnum - 1 - fp->fd_top); + foldRemove(flp->wp, &fp->fd_nested, startlnum2 - fp->fd_top, + flp->lnum - 1 - fp->fd_top); if (lvl < level) { // End of fold found, update the length when it got shorter. @@ -2490,7 +2503,7 @@ static linenr_T foldUpdateIEMSRecurse( // indent or expr method: split fold to create a new one // below bot i = (int)(fp - (fold_T *)gap->ga_data); - foldSplit(gap, i, flp->lnum, bot); + foldSplit(flp->wp->w_buffer, gap, i, flp->lnum, bot); fp = (fold_T *)gap->ga_data + i; } } else { @@ -2508,23 +2521,23 @@ static linenr_T foldUpdateIEMSRecurse( break; if (fp2->fd_top + fp2->fd_len > flp->lnum) { if (fp2->fd_top < flp->lnum) { - /* Make fold that includes lnum start at lnum. */ - foldMarkAdjustRecurse(&fp2->fd_nested, - (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1), - (linenr_T)MAXLNUM, (long)(fp2->fd_top - flp->lnum)); + // Make fold that includes lnum start at lnum. + foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested, + (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1), + (linenr_T)MAXLNUM, (long)(fp2->fd_top-flp->lnum)); fp2->fd_len -= flp->lnum - fp2->fd_top; fp2->fd_top = flp->lnum; fold_changed = true; } if (lvl >= level) { - /* merge new fold with existing fold that follows */ - foldMerge(fp, gap, fp2); + // merge new fold with existing fold that follows + foldMerge(flp->wp, fp, gap, fp2); } break; } fold_changed = true; - deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true); + deleteFoldEntry(flp->wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true); } /* Need to redraw the lines we inspected, which might be further down than @@ -2560,8 +2573,10 @@ static void foldInsert(garray_T *gap, int i) * The caller must first have taken care of any nested folds from "top" to * "bot"! */ -static void foldSplit(garray_T *const gap, const int i, const linenr_T top, - const linenr_T bot) +static void foldSplit(buf_T *buf, garray_T *const gap, + const int i, const linenr_T top, + const linenr_T bot + ) { fold_T *fp2; @@ -2616,7 +2631,9 @@ static void foldSplit(garray_T *const gap, const int i, const linenr_T top, * 5: made to start below "bot". * 6: not changed */ -static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) +static void foldRemove( + win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot +) { fold_T *fp = NULL; @@ -2628,10 +2645,11 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) // Find fold that includes top or a following one. if (foldFind(gap, top, &fp) && fp->fd_top < top) { // 2: or 3: need to delete nested folds - foldRemove(&fp->fd_nested, top - fp->fd_top, bot - fp->fd_top); + foldRemove(wp, &fp->fd_nested, top - fp->fd_top, bot - fp->fd_top); if (fp->fd_top + fp->fd_len - 1 > bot) { // 3: need to split it. - foldSplit(gap, (int)(fp - (fold_T *)gap->ga_data), top, bot); + foldSplit(wp->w_buffer, gap, + (int)(fp - (fold_T *)gap->ga_data), top, bot); } else { // 2: truncate fold at "top". fp->fd_len = top - fp->fd_top; @@ -2649,7 +2667,8 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) fold_changed = true; if (fp->fd_top + fp->fd_len - 1 > bot) { // 5: Make fold that includes bot start below bot. - foldMarkAdjustRecurse(&fp->fd_nested, + foldMarkAdjustRecurse( + wp, &fp->fd_nested, (linenr_T)0, (long)(bot - fp->fd_top), (linenr_T)MAXLNUM, (long)(fp->fd_top - bot - 1)); fp->fd_len -= bot - fp->fd_top + 1; @@ -2658,7 +2677,7 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot) } // 4: Delete completely contained fold. - deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true); + deleteFoldEntry(wp, gap, (int)(fp - (fold_T *)gap->ga_data), true); } } } @@ -2710,19 +2729,22 @@ static void foldReverseOrder( // 8. truncated below dest and shifted up. // 9. shifted up // 10. not changed -static void truncate_fold(fold_T *fp, linenr_T end) +static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) { // I want to stop *at here*, foldRemove() stops *above* top end += 1; - foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM); + foldRemove(wp, &fp->fd_nested, end - fp->fd_top, MAXLNUM); fp->fd_len = end - fp->fd_top; } #define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1) #define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) #define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data))) -void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, - const linenr_T dest) +void foldMoveRange( + win_T *const wp, garray_T *gap, + const linenr_T line1, const linenr_T line2, + const linenr_T dest +) { fold_T *fp; const linenr_T range_len = line2 - line1 + 1; @@ -2733,20 +2755,20 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, if (FOLD_END(fp) > dest) { // Case 4 -- don't have to change this fold, but have to move nested // folds. - foldMoveRange(&fp->fd_nested, line1 - fp->fd_top, line2 - + foldMoveRange(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, dest - fp->fd_top); return; } else if (FOLD_END(fp) > line2) { // Case 3 -- Remove nested folds between line1 and line2 & reduce the // length of fold by "range_len". // Folds after this one must be dealt with. - foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, line2 - fp->fd_top, MAXLNUM, -range_len); fp->fd_len -= range_len; } else { // Case 2 -- truncate fold *above* line1. // Folds after this one must be dealt with. - truncate_fold(fp, line1 - 1); + truncate_fold(wp, fp, line1 - 1); } // Look at the next fold, and treat that one as if it were the first after // "line1" (because now it is). @@ -2764,13 +2786,13 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, } if (VALID_FOLD(fp, gap) && fp->fd_top <= dest) { // Case 8. -- ensure truncated at dest, shift up - truncate_fold(fp, dest); + truncate_fold(wp, fp, dest); fp->fd_top -= range_len; } return; } else if (FOLD_END(fp) > dest) { // Case 7 -- remove nested folds and shrink - foldMarkAdjustRecurse(&fp->fd_nested, line2 + 1 - fp->fd_top, + foldMarkAdjustRecurse(wp, &fp->fd_nested, line2 + 1 - fp->fd_top, dest - fp->fd_top, MAXLNUM, -move_len); fp->fd_len -= move_len; fp->fd_top += move_len; @@ -2786,7 +2808,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, // 5, or 6 if (FOLD_END(fp) > line2) { // 6, truncate before moving - truncate_fold(fp, line2); + truncate_fold(wp, fp, line2); } fp->fd_top += move_len; continue; @@ -2798,7 +2820,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, } if (FOLD_END(fp) > dest) { - truncate_fold(fp, dest); + truncate_fold(wp, fp, dest); } fp->fd_top -= range_len; @@ -2830,7 +2852,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2, * The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". * Fold entry "fp2" in "gap" is deleted. */ -static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) +static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) { fold_T *fp3; fold_T *fp4; @@ -2840,8 +2862,9 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) /* If the last nested fold in fp1 touches the first nested fold in fp2, * merge them recursively. */ - if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) - foldMerge(fp3, gap2, fp4); + if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) { + foldMerge(wp, fp3, gap2, fp4); + } /* Move nested folds in fp2 to the end of fp1. */ if (!GA_EMPTY(gap2)) { @@ -2856,7 +2879,7 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2) } fp1->fd_len += fp2->fd_len; - deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true); + deleteFoldEntry(wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true); fold_changed = true; } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 4753df7b87..51d9549033 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -271,17 +271,22 @@ static const char *input_cb(void *payload, uint32_t byte_index, } char_u *line = ml_get_buf(bp, position.row+1, false); size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column, BUFSIZE); - - memcpy(buf, line+position.column, tocopy); - // Translate embedded \n to NUL - memchrsub(buf, '\n', '\0', tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < BUFSIZE) { - // now add the final \n. If it didn't fit, input_cb will be called again - // on the same line with advanced column. - buf[tocopy] = '\n'; - (*bytes_read)++; + + if (position.column > len) { + *bytes_read = 0; + } else { + size_t tocopy = MIN(len-position.column, BUFSIZE); + + memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. + buf[tocopy] = '\n'; + (*bytes_read)++; + } } return buf; #undef BUFSIZE diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b695d0e139..6e074b3249 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2390,21 +2390,32 @@ static int ml_append_int( void ml_add_deleted_len(char_u *ptr, ssize_t len) { + ml_add_deleted_len_buf(curbuf, ptr, len); +} + +void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) +{ if (inhibit_delete_count) { return; } if (len == -1) { len = STRLEN(ptr); } - curbuf->deleted_bytes += len+1; - if (curbuf->update_need_codepoints) { - mb_utflen(ptr, len, &curbuf->deleted_codepoints, - &curbuf->deleted_codeunits); - curbuf->deleted_codepoints++; // NL char - curbuf->deleted_codeunits++; + buf->deleted_bytes += len+1; + if (buf->update_need_codepoints) { + mb_utflen(ptr, len, &buf->deleted_codepoints, + &buf->deleted_codeunits); + buf->deleted_codepoints++; // NL char + buf->deleted_codeunits++; } } + +int ml_replace(linenr_T lnum, char_u *line, bool copy) +{ + return ml_replace_buf(curbuf, lnum, line, copy); +} + /* * Replace line lnum, with buffering, in current buffer. * @@ -2416,36 +2427,37 @@ void ml_add_deleted_len(char_u *ptr, ssize_t len) * * return FAIL for failure, OK otherwise */ -int ml_replace(linenr_T lnum, char_u *line, bool copy) +int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) { if (line == NULL) /* just checking... */ return FAIL; - /* When starting up, we might still need to create the memfile */ - if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) + // When starting up, we might still need to create the memfile + if (buf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { return FAIL; + } bool readlen = true; if (copy) { line = vim_strsave(line); } - if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered - ml_flush_line(curbuf); // flush it - } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated - ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); + if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered + ml_flush_line(buf); // flush it + } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated + ml_add_deleted_len(buf->b_ml.ml_line_ptr, -1); readlen = false; // already added the length - xfree(curbuf->b_ml.ml_line_ptr); // free it + xfree(buf->b_ml.ml_line_ptr); // free it } - if (readlen && kv_size(curbuf->update_callbacks)) { - ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); + if (readlen && kv_size(buf->update_callbacks)) { + ml_add_deleted_len(ml_get_buf(buf, lnum, false), -1); } - curbuf->b_ml.ml_line_ptr = line; - curbuf->b_ml.ml_line_lnum = lnum; - curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; + buf->b_ml.ml_line_ptr = line; + buf->b_ml.ml_line_lnum = lnum; + buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; return OK; } @@ -3989,8 +4001,8 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS); int extra = 0; - /* take care of cached line first */ - ml_flush_line(curbuf); + // take care of cached line first + ml_flush_line(buf); if (buf->b_ml.ml_usedchunks == -1 || buf->b_ml.ml_chunksize == NULL diff --git a/src/nvim/option.c b/src/nvim/option.c index 4eb1107cd4..eae52ff260 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -619,11 +619,9 @@ static void set_runtimepath_default(void) #undef NVIM_SIZE -/* - * Initialize the options, first part. - * - * Called only once from main(), just after creating the first buffer. - */ +/// Initialize the options, first part. +/// +/// Called only once from main(), just after creating the first buffer. void set_init_1(void) { int opt_idx; @@ -868,10 +866,8 @@ void set_init_1(void) set_helplang_default(get_mess_lang()); } -/* - * Set an option to its default value. - * This does not take care of side effects! - */ +/// Set an option to its default value. +/// This does not take care of side effects! static void set_option_default( int opt_idx, @@ -942,9 +938,7 @@ set_option_default( set_option_sctx_idx(opt_idx, opt_flags, current_sctx); } -/* - * Set all options (except terminal options) to their default value. - */ +/// Set all options (except terminal options) to their default value. static void set_options_default( int opt_flags // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL @@ -987,10 +981,8 @@ static void set_string_default(const char *name, char *val, bool allocated) } } -/* - * Set the Vi-default value of a number option. - * Used for 'lines' and 'columns'. - */ +/// Set the Vi-default value of a number option. +/// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) { int opt_idx; @@ -1117,10 +1109,8 @@ void set_init_3(void) set_title_defaults(); // 'title', 'icon' } -/* - * When 'helplang' is still at its default value, set it to "lang". - * Only the first two characters of "lang" are used. - */ +/// When 'helplang' is still at its default value, set it to "lang". +/// Only the first two characters of "lang" are used. void set_helplang_default(const char *lang) { if (lang == NULL) { @@ -1152,13 +1142,11 @@ void set_helplang_default(const char *lang) } -/* - * 'title' and 'icon' only default to true if they have not been set or reset - * in .vimrc and we can read the old value. - * When 'title' and 'icon' have been reset in .vimrc, we won't even check if - * they can be reset. This reduces startup time when using X on a remote - * machine. - */ +/// 'title' and 'icon' only default to true if they have not been set or reset +/// in .vimrc and we can read the old value. +/// When 'title' and 'icon' have been reset in .vimrc, we won't even check if +/// they can be reset. This reduces startup time when using X on a remote +/// machine. void set_title_defaults(void) { int idx1; @@ -2023,10 +2011,8 @@ static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c) return errbuf; } -/* - * Convert a key name or string into a key value. - * Used for 'wildchar' and 'cedit' options. - */ +/// Convert a key name or string into a key value. +/// Used for 'wildchar' and 'cedit' options. static int string_to_key(char_u *arg) { if (*arg == '<') { @@ -2038,10 +2024,8 @@ static int string_to_key(char_u *arg) return *arg; } -/* - * Check value of 'cedit' and set cedit_key. - * Returns NULL if value is OK, error message otherwise. - */ +/// Check value of 'cedit' and set cedit_key. +/// Returns NULL if value is OK, error message otherwise. static char_u *check_cedit(void) { int n; @@ -2125,13 +2109,11 @@ void set_options_bin( } } -/* - * Find the parameter represented by the given character (eg ', :, ", or /), - * and return its associated value in the 'shada' string. - * Only works for number parameters, not for 'r' or 'n'. - * If the parameter is not specified in the string or there is no following - * number, return -1. - */ +/// Find the parameter represented by the given character (eg ', :, ", or /), +/// and return its associated value in the 'shada' string. +/// Only works for number parameters, not for 'r' or 'n'. +/// If the parameter is not specified in the string or there is no following +/// number, return -1. int get_shada_parameter(int type) { char_u *p; @@ -2143,11 +2125,9 @@ int get_shada_parameter(int type) return -1; } -/* - * Find the parameter represented by the given character (eg ''', ':', '"', or - * '/') in the 'shada' option and return a pointer to the string after it. - * Return NULL if the parameter is not specified in the string. - */ +/// Find the parameter represented by the given character (eg ''', ':', '"', or +/// '/') in the 'shada' option and return a pointer to the string after it. +/// Return NULL if the parameter is not specified in the string. char_u *find_shada_parameter(int type) { char_u *p; @@ -2167,12 +2147,10 @@ char_u *find_shada_parameter(int type) return NULL; } -/* - * Expand environment variables for some string options. - * These string options cannot be indirect! - * If "val" is NULL expand the current value of the option. - * Return pointer to NameBuff, or NULL when not expanded. - */ +/// Expand environment variables for some string options. +/// These string options cannot be indirect! +/// If "val" is NULL expand the current value of the option. +/// Return pointer to NameBuff, or NULL when not expanded. static char_u *option_expand(int opt_idx, char_u *val) { // if option doesn't need expansion nothing to do @@ -2256,9 +2234,7 @@ static void didset_options2(void) check_opt_wim(); } -/* - * Check for string options that are NULL (normally only termcap options). - */ +/// Check for string options that are NULL (normally only termcap options). void check_options(void) { int opt_idx; @@ -2270,9 +2246,7 @@ void check_options(void) } } -/* - * Check string options in a buffer for NULL value. - */ +/// Check string options in a buffer for NULL value. void check_buf_options(buf_T *buf) { check_string_option(&buf->b_p_bh); @@ -2325,13 +2299,11 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_menc); } -/* - * Free the string allocated for an option. - * Checks for the string being empty_option. This may happen if we're out of - * memory, vim_strsave() returned NULL, which was replaced by empty_option by - * check_options(). - * Does NOT check for P_ALLOCED flag! - */ +/// Free the string allocated for an option. +/// Checks for the string being empty_option. This may happen if we're out of +/// memory, vim_strsave() returned NULL, which was replaced by empty_option by +/// check_options(). +/// Does NOT check for P_ALLOCED flag! void free_string_option(char_u *p) { if (p != empty_option) { @@ -2369,10 +2341,8 @@ int was_set_insecurely(char_u *opt, int opt_flags) return -1; } -/* - * Get a pointer to the flags used for the P_INSECURE flag of option - * "opt_idx". For some local options a local flags field is used. - */ +/// Get a pointer to the flags used for the P_INSECURE flag of option +/// "opt_idx". For some local options a local flags field is used. static uint32_t *insecure_flag(int opt_idx, int opt_flags) { if (opt_flags & OPT_LOCAL) @@ -2390,9 +2360,7 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags) } -/* - * Redraw the window title and/or tab page text later. - */ +/// Redraw the window title and/or tab page text later. static void redraw_titles(void) { need_maketitle = true; @@ -2473,9 +2441,7 @@ set_string_option_direct( } } -/* - * Set global value for string option when it's a local option. - */ +/// Set global value for string option when it's a local option. static void set_string_option_global( int opt_idx, // option index @@ -3742,10 +3708,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) return NULL; // no error } -/* - * Check validity of options with the 'statusline' format. - * Return error message or NULL. - */ +/// Check validity of options with the 'statusline' format. +/// Return error message or NULL. char_u *check_stl_option(char_u *s) { int itemcnt = 0; @@ -3838,10 +3802,8 @@ static char_u *did_set_spell_option(bool is_spellfile) return errmsg; } -/* - * Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. - * Return error message when failed, NULL when OK. - */ +/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. +/// Return error message when failed, NULL when OK. static char_u *compile_cap_prog(synblock_T *synblock) FUNC_ATTR_NONNULL_ALL { @@ -4683,9 +4645,7 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, } } -/* - * Called after an option changed: check if something needs to be redrawn. - */ +/// Called after an option changed: check if something needs to be redrawn. static void check_redraw(uint32_t flags) { // Careful: P_RCLR and P_RALL are a combination of other P_ flags @@ -5140,10 +5100,8 @@ static int find_key_option(const char_u *arg, bool has_lt) return find_key_option_len(arg, STRLEN(arg), has_lt); } -/* - * if 'all' == 0: show changed options - * if 'all' == 1: show all normal options - */ +/// if 'all' == 0: show changed options +/// if 'all' == 1: show all normal options static void showoptions( int all, @@ -5292,10 +5250,8 @@ void ui_refresh_options(void) } } -/* - * showoneopt: show the value of one option - * must not be called with a hidden option! - */ +/// showoneopt: show the value of one option +/// must not be called with a hidden option! static void showoneopt( vimoption_T *p, @@ -5331,28 +5287,26 @@ showoneopt( info_message = false; } -/* - * Write modified options as ":set" commands to a file. - * - * There are three values for "opt_flags": - * OPT_GLOBAL: Write global option values and fresh values of - * buffer-local options (used for start of a session - * file). - * OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for - * curwin (used for a vimrc file). - * OPT_LOCAL: Write buffer-local option values for curbuf, fresh - * and local values for window-local options of - * curwin. Local values are also written when at the - * default value, because a modeline or autocommand - * may have set them when doing ":edit file" and the - * user has set them back at the default or fresh - * value. - * When "local_only" is true, don't write fresh - * values, only local values (for ":mkview"). - * (fresh value = value used for a new buffer or window for a local option). - * - * Return FAIL on error, OK otherwise. - */ +/// Write modified options as ":set" commands to a file. +/// +/// There are three values for "opt_flags": +/// OPT_GLOBAL: Write global option values and fresh values of +/// buffer-local options (used for start of a session +/// file). +/// OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for +/// curwin (used for a vimrc file). +/// OPT_LOCAL: Write buffer-local option values for curbuf, fresh +/// and local values for window-local options of +/// curwin. Local values are also written when at the +/// default value, because a modeline or autocommand +/// may have set them when doing ":edit file" and the +/// user has set them back at the default or fresh +/// value. +/// When "local_only" is true, don't write fresh +/// values, only local values (for ":mkview"). +/// (fresh value = value used for a new buffer or window for a local option). +/// +/// Return FAIL on error, OK otherwise. int makeset(FILE *fd, int opt_flags, int local_only) { vimoption_T *p; @@ -5463,10 +5417,8 @@ int makeset(FILE *fd, int opt_flags, int local_only) return OK; } -/* - * Generate set commands for the local fold options only. Used when - * 'sessionoptions' or 'viewoptions' contains "folds" but not "options". - */ +/// Generate set commands for the local fold options only. Used when +/// 'sessionoptions' or 'viewoptions' contains "folds" but not "options". int makefoldset(FILE *fd) { if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, false) == FAIL @@ -5559,12 +5511,10 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) return OK; } -/* - * Compute columns for ruler and shown command. 'sc_col' is also used to - * decide what the maximum length of a message on the status line can be. - * If there is a status line for the last window, 'sc_col' is independent - * of 'ru_col'. - */ +/// Compute columns for ruler and shown command. 'sc_col' is also used to +/// decide what the maximum length of a message on the status line can be. +/// If there is a status line for the last window, 'sc_col' is independent +/// of 'ru_col'. #define COL_RULER 17 // columns needed by standard ruler @@ -5698,9 +5648,7 @@ void unset_global_local_option(char *name, void *from) } } -/* - * Get pointer to option variable, depending on local or global scope. - */ +/// Get pointer to option variable, depending on local or global scope. static char_u *get_varp_scope(vimoption_T *p, int opt_flags) { if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { @@ -5741,9 +5689,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) return get_varp(p); } -/* - * Get pointer to option variable. - */ +/// Get pointer to option variable. static char_u *get_varp(vimoption_T *p) { // hidden option, always return NULL @@ -5905,9 +5851,7 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_wm); } -/* - * Get the value of 'equalprg', either the buffer-local one or the global one. - */ +/// Get the value of 'equalprg', either the buffer-local one or the global one. char_u *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { @@ -5916,22 +5860,18 @@ char_u *get_equalprg(void) return curbuf->b_p_ep; } -/* - * Copy options from one window to another. - * Used when splitting a window. - */ +/// Copy options from one window to another. +/// Used when splitting a window. void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); } -/* - * Copy the options from one winopt_T to another. - * Doesn't free the old option values in "to", use clear_winopt() for that. - * The 'scroll' option is not copied, because it depends on the window height. - * The 'previewwindow' option is reset, there can be only one preview window. - */ +/// Copy the options from one winopt_T to another. +/// Doesn't free the old option values in "to", use clear_winopt() for that. +/// The 'scroll' option is not copied, because it depends on the window height. +/// The 'previewwindow' option is reset, there can be only one preview window. void copy_winopt(winopt_T *from, winopt_T *to) { to->wo_arab = from->wo_arab; @@ -5983,18 +5923,14 @@ void copy_winopt(winopt_T *from, winopt_T *to) check_winopt(to); // don't want NULL pointers } -/* - * Check string options in a window for a NULL value. - */ +/// Check string options in a window for a NULL value. void check_win_options(win_T *win) { check_winopt(&win->w_onebuf_opt); check_winopt(&win->w_allbuf_opt); } -/* - * Check for NULL pointers in a winopt_T and replace them with empty_option. - */ +/// Check for NULL pointers in a winopt_T and replace them with empty_option. static void check_winopt(winopt_T *wop) { check_string_option(&wop->wo_fdc); @@ -6016,9 +5952,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_lcs); } -/* - * Free the allocated memory inside a winopt_T. - */ +/// Free the allocated memory inside a winopt_T. void clear_winopt(winopt_T *wop) { clear_string_option(&wop->wo_fdc); @@ -6051,15 +5985,13 @@ void didset_window_options(win_T *wp) } -/* - * Copy global option values to local options for one buffer. - * Used when creating a new buffer and sometimes when entering a buffer. - * flags: - * BCO_ENTER We will enter the buf buffer. - * BCO_ALWAYS Always copy the options, but only set b_p_initialized when - * appropriate. - * BCO_NOHELP Don't copy the values to a help buffer. - */ +/// Copy global option values to local options for one buffer. +/// Used when creating a new buffer and sometimes when entering a buffer. +/// flags: +/// BCO_ENTER We will enter the buf buffer. +/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when +/// appropriate. +/// BCO_NOHELP Don't copy the values to a help buffer. void buf_copy_options(buf_T *buf, int flags) { int should_copy = true; @@ -6254,9 +6186,7 @@ void buf_copy_options(buf_T *buf, int flags) } } -/* - * Reset the 'modifiable' option and its default value. - */ +/// Reset the 'modifiable' option and its default value. void reset_modifiable(void) { int opt_idx; @@ -6269,17 +6199,13 @@ void reset_modifiable(void) } } -/* - * Set the global value for 'iminsert' to the local value. - */ +/// Set the global value for 'iminsert' to the local value. void set_iminsert_global(void) { p_iminsert = curbuf->b_p_iminsert; } -/* - * Set the global value for 'imsearch' to the local value. - */ +/// Set the global value for 'imsearch' to the local value. void set_imsearch_global(void) { p_imsearch = curbuf->b_p_imsearch; @@ -6581,10 +6507,8 @@ void ExpandOldSetting(int *num_file, char_u ***file) *num_file = 1; } -/* - * Get the value for the numeric or string option *opp in a nice format into - * NameBuff[]. Must not be called with a hidden option! - */ +/// Get the value for the numeric or string option///opp in a nice format into +/// NameBuff[]. Must not be called with a hidden option! static void option_value2string( vimoption_T *opp, @@ -6637,21 +6561,18 @@ static int wc_use_keyname(char_u *varp, long *wcp) return false; } -/* - * Any character has an equivalent 'langmap' character. This is used for - * keyboards that have a special language mode that sends characters above - * 128 (although other characters can be translated too). The "to" field is a - * Vim command character. This avoids having to switch the keyboard back to - * ASCII mode when leaving Insert mode. - * - * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim - * commands. - * langmap_mapga.ga_data is a sorted table of langmap_entry_T. - * This does the same as langmap_mapchar[] for characters >= 256. - */ -/* - * With multi-byte support use growarray for 'langmap' chars >= 256 - */ +/// Any character has an equivalent 'langmap' character. This is used for +/// keyboards that have a special language mode that sends characters above +/// 128 (although other characters can be translated too). The "to" field is a +/// Vim command character. This avoids having to switch the keyboard back to +/// ASCII mode when leaving Insert mode. +/// +/// langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim +/// commands. +/// langmap_mapga.ga_data is a sorted table of langmap_entry_T. +/// This does the same as langmap_mapchar[] for characters >= 256. +/// +/// With multi-byte support use growarray for 'langmap' chars >= 256 typedef struct { int from; int to; @@ -6659,10 +6580,8 @@ typedef struct { static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE; -/* - * Search for an entry in "langmap_mapga" for "from". If found set the "to" - * field. If not found insert a new entry at the appropriate location. - */ +/// Search for an entry in "langmap_mapga" for "from". If found set the "to" +/// field. If not found insert a new entry at the appropriate location. static void langmap_set_entry(int from, int to) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); @@ -6697,9 +6616,7 @@ static void langmap_set_entry(int from, int to) entries[0].to = to; } -/* - * Apply 'langmap' to multi-byte character "c" and return the result. - */ +/// Apply 'langmap' to multi-byte character "c" and return the result. int langmap_adjust_mb(int c) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); @@ -6730,10 +6647,8 @@ static void langmap_init(void) ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8); } -/* - * Called when langmap option is set; the language map can be - * changed at any time! - */ +/// Called when langmap option is set; the language map can be +/// changed at any time! static void langmap_set(void) { char_u *p; @@ -6836,9 +6751,7 @@ bool shortmess(int x) && vim_strchr((char_u *)SHM_ALL_ABBREVIATIONS, x) != NULL))); } -/* - * paste_option_changed() - Called after p_paste was set or reset. - */ +/// paste_option_changed() - Called after p_paste was set or reset. static void paste_option_changed(void) { static int old_p_paste = false; @@ -6985,9 +6898,7 @@ void reset_option_was_set(const char *name) } } -/* - * fill_breakat_flags() -- called when 'breakat' changes value. - */ +/// fill_breakat_flags() -- called when 'breakat' changes value. static void fill_breakat_flags(void) { char_u *p; @@ -7004,12 +6915,10 @@ static void fill_breakat_flags(void) } } -/* - * Check an option that can be a range of string values. - * - * Return OK for correct value, FAIL otherwise. - * Empty is always OK. - */ +/// Check an option that can be a range of string values. +/// +/// Return OK for correct value, FAIL otherwise. +/// Empty is always OK. static int check_opt_strings( char_u *val, char **values, @@ -7019,13 +6928,11 @@ static int check_opt_strings( return opt_strings_flags(val, values, NULL, list); } -/* - * Handle an option that can be a range of string values. - * Set a flag in "*flagp" for each string present. - * - * Return OK for correct value, FAIL otherwise. - * Empty is always OK. - */ +/// Handle an option that can be a range of string values. +/// Set a flag in "*flagp" for each string present. +/// +/// Return OK for correct value, FAIL otherwise. +/// Empty is always OK. static int opt_strings_flags( char_u *val, // new value char **values, // array of valid string values @@ -7058,9 +6965,7 @@ static int opt_strings_flags( return OK; } -/* - * Read the 'wildmode' option, fill wim_flags[]. - */ +/// Read the 'wildmode' option, fill wim_flags[]. static int check_opt_wim(void) { char_u new_wim_flags[4]; @@ -7113,11 +7018,9 @@ static int check_opt_wim(void) return OK; } -/* - * Check if backspacing over something is allowed. - * The parameter what is one of the following: whatBS_INDENT, BS_EOL - * or BS_START - */ +/// Check if backspacing over something is allowed. +/// The parameter what is one of the following: whatBS_INDENT, BS_EOL +/// or BS_START bool can_bs(int what) { if (what == BS_START && bt_prompt(curbuf)) { @@ -7131,10 +7034,8 @@ bool can_bs(int what) return vim_strchr(p_bs, what) != NULL; } -/* - * Save the current values of 'fileformat' and 'fileencoding', so that we know - * the file must be considered changed when the value is different. - */ +/// Save the current values of 'fileformat' and 'fileencoding', so that we know +/// the file must be considered changed when the value is different. void save_file_ff(buf_T *buf) { buf->b_start_ffc = *buf->b_p_ff; @@ -7184,18 +7085,14 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty) return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; } -/* - * return OK if "p" is a valid fileformat name, FAIL otherwise. - */ +/// return OK if "p" is a valid fileformat name, FAIL otherwise. int check_ff_value(char_u *p) { return check_opt_strings(p, p_ff_values, false); } -/* - * Return the effective shiftwidth value for current buffer, using the - * 'tabstop' value when 'shiftwidth' is zero. - */ +/// Return the effective shiftwidth value for current buffer, using the +/// 'tabstop' value when 'shiftwidth' is zero. int get_sw_value(buf_T *buf) { long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts; @@ -7203,8 +7100,8 @@ int get_sw_value(buf_T *buf) return (int)result; } -// Return the effective softtabstop value for the current buffer, -// using the effective shiftwidth value when 'softtabstop' is negative. +/// Return the effective softtabstop value for the current buffer, +/// using the effective shiftwidth value when 'softtabstop' is negative. int get_sts_value(void) { long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; @@ -7212,12 +7109,10 @@ int get_sts_value(void) return (int)result; } -/* - * Check matchpairs option for "*initc". - * If there is a match set "*initc" to the matching character and "*findc" to - * the opposite character. Set "*backwards" to the direction. - * When "switchit" is true swap the direction. - */ +/// Check matchpairs option for "*initc". +/// If there is a match set "*initc" to the matching character and "*findc" to +/// the opposite character. Set "*backwards" to the direction. +/// When "switchit" is true swap the direction. void find_mps_values(int *initc, int *findc, int *backwards, int switchit) { char_u *ptr = curbuf->b_p_mps; diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index ba6226ef9d..112de9fed8 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -50,22 +50,13 @@ void signal_init(void) signal_watcher_init(&main_loop, &shup, NULL); signal_watcher_init(&main_loop, &squit, NULL); signal_watcher_init(&main_loop, &sterm, NULL); -#ifdef SIGPIPE - signal_watcher_start(&spipe, on_signal, SIGPIPE); -#endif - signal_watcher_start(&shup, on_signal, SIGHUP); -#ifdef SIGQUIT - signal_watcher_start(&squit, on_signal, SIGQUIT); -#endif - signal_watcher_start(&sterm, on_signal, SIGTERM); #ifdef SIGPWR signal_watcher_init(&main_loop, &spwr, NULL); - signal_watcher_start(&spwr, on_signal, SIGPWR); #endif #ifdef SIGUSR1 signal_watcher_init(&main_loop, &susr1, NULL); - signal_watcher_start(&susr1, on_signal, SIGUSR1); #endif + signal_start(); } void signal_teardown(void) @@ -83,11 +74,33 @@ void signal_teardown(void) #endif } +void signal_start(void) +{ +#ifdef SIGPIPE + signal_watcher_start(&spipe, on_signal, SIGPIPE); +#endif + signal_watcher_start(&shup, on_signal, SIGHUP); +#ifdef SIGQUIT + signal_watcher_start(&squit, on_signal, SIGQUIT); +#endif + signal_watcher_start(&sterm, on_signal, SIGTERM); +#ifdef SIGPWR + signal_watcher_start(&spwr, on_signal, SIGPWR); +#endif +#ifdef SIGUSR1 + signal_watcher_start(&susr1, on_signal, SIGUSR1); +#endif +} + void signal_stop(void) { +#ifdef SIGPIPE signal_watcher_stop(&spipe); +#endif signal_watcher_stop(&shup); +#ifdef SIGQUIT signal_watcher_stop(&squit); +#endif signal_watcher_stop(&sterm); #ifdef SIGPWR signal_watcher_stop(&spwr); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ddb9188371..ef4dfb3caa 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6400,7 +6400,7 @@ static int color_numbers_88[28] = { 0, 4, 2, 6, 75, 11, 78, 15, -1 }; // for xterm with 256 colors... static int color_numbers_256[28] = { 0, 4, 2, 6, - 1, 5, 130, 130, + 1, 5, 130, 3, 248, 248, 7, 7, 242, 242, 12, 81, 10, 121, diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 6c164b27d2..560a345333 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -997,8 +997,9 @@ static void mouse_action(Terminal *term, int button, int row, int col, static bool send_mouse_event(Terminal *term, int c) { int row = mouse_row, col = mouse_col, grid = mouse_grid; + int offset; win_T *mouse_win = mouse_find_win(&grid, &row, &col); - if (mouse_win == NULL) { + if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) { goto end; } @@ -1020,7 +1021,7 @@ static bool send_mouse_event(Terminal *term, int c) default: return false; } - mouse_action(term, button, row, col, drag, 0); + mouse_action(term, button, row, col - offset, drag, 0); size_t len = vterm_output_read(term->vt, term->textbuf, sizeof(term->textbuf)); terminal_send(term, term->textbuf, (size_t)len); diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index aad21c002f..7fbf04311d 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -6,6 +6,9 @@ function Test_messages() set nomore " Avoid the "message maintainer" line. let $LANG = '' + let $LC_ALL = '' + let $LC_MESSAGES = '' + let $LC_COLLATE = '' let arr = map(range(10), '"hello" . v:val') for s in arr diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0c330149d0..228545a844 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -31,6 +31,7 @@ #include "nvim/event/signal.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/signal.h" #include "nvim/os/tty.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -48,10 +49,15 @@ #define OUTBUF_SIZE 0xffff #define TOO_MANY_EVENTS 1000000 -#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ - && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) -#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ - ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) +#define STARTS_WITH(str, prefix) \ + (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \ + sizeof(prefix) - 1)) +#define SCREEN_WRAP(is_screen, seq) ((is_screen) \ + ? DCS_STR seq STERM_STR : seq) +#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ + ((is_screen) \ + ? DCS_STR seq STERM_STR : (is_tmux) \ + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -1239,7 +1245,9 @@ static void suspend_event(void **argv) tui_terminal_stop(ui); data->cont_received = false; stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + signal_stop(); kill(0, SIGTSTP); + signal_start(); while (!data->cont_received) { // poll the event loop until SIGCONT is received loop_poll_events(data->loop, -1); @@ -1535,7 +1543,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool rxvt = terminfo_is_term_family(term, "rxvt"); bool teraterm = terminfo_is_term_family(term, "teraterm"); bool putty = terminfo_is_term_family(term, "putty"); - bool screen = terminfo_is_term_family(term, "screen"); + bool screen = terminfo_is_term_family(term, "screen") && !os_getenv("TMUX"); + bool screen_host_linuxvt = + terminfo_is_term_family(screen && term[6] == '.' + ? term + 7 : NULL, "linux"); bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX"); bool st = terminfo_is_term_family(term, "st"); bool gnome = terminfo_is_term_family(term, "gnome") @@ -1694,8 +1705,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + data->unibi_ext.get_bg = + (int)unibi_add_ext_str(ut, "ext.get_bg", + SCREEN_TMUX_WRAP(screen, tmux, "\x1b]11;?\x07")); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { @@ -1730,6 +1742,32 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with + // DCS because it is inherited from the host terminal. + if (screen) { + size_t len; + size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR); + if (-1 != data->unibi_ext.set_cursor_style) { + const char *orig_ss = + unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); + len = STRLEN(orig_ss) + dcs_st_len + 1; + char *ss = xmalloc(len); + snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR); + unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss); + xfree(ss); + } + if (-1 != data->unibi_ext.reset_cursor_style) { + const char *orig_se = + unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style); + len = strlen(orig_se) + dcs_st_len + 1; + char *se = xmalloc(len); + snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR); + unibi_set_ext_str(data->ut, + (size_t)data->unibi_ext.reset_cursor_style, se); + xfree(se); + } + } + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So // adding them to terminal types, that have such control sequences but lack // the correct terminfo entries, is a fixup, not an augmentation. @@ -1745,7 +1783,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (konsolev >= 180770) // #9364 || tmux // per tmux manual page // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen + || (screen + && (!screen_host_linuxvt + || (screen_host_linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) + // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped + // in DCS and output to the host terminal. || st // #7641 || rxvt // per command.C // per analysis of VT100Terminal.m @@ -1758,34 +1801,43 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = - (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); + (int)unibi_add_ext_str(ut, "Ss", SCREEN_WRAP(screen, "\x1b[%p1%d q")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - "\x1b[ q"); - } else if (linuxvt) { + SCREEN_WRAP(screen, "\x1b[ q")); + } else if (linuxvt || screen_host_linuxvt) { // Linux uses an idiosyncratic escape code to set the cursor shape and // does not support DECSCUSR. // See http://linuxgazette.net/137/anonymous.html for more info - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - "\x1b[?" - "%?" - // The parameter passed to Ss is the DECSCUSR parameter, so the - // terminal capability has to translate into the Linux idiosyncratic - // parameter. - // - // linuxvt only supports block and underline. It is also only - // possible to have a steady block (no steady underline) - "%p1%{2}%<" "%t%{8}" // blink block - "%e%p1%{2}%=" "%t%{112}" // steady block - "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) - "%e%p1%{4}%=" "%t%{4}" // steady underline - "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) - "%e%p1%{6}%=" "%t%{2}" // steady bar - "%e%{0}" // anything else - "%;" "%dc"); + // + // Since gnu Screen does not have Ss/Se, if the host terminal is a linux + // console that does not support xterm extensions, it will wraps the + // linux-specific sequence in DCS and outputs it. + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( + ut, "Ss", + SCREEN_WRAP(screen, + "\x1b[?" + "%?" + // The parameter passed to Ss is the DECSCUSR parameter, + // so the + // terminal capability has to translate into the Linux + // idiosyncratic parameter. + // + // linuxvt only supports block and underline. It is also + // only possible to have a steady block (no steady + // underline) + "%p1%{2}%<" "%t%{8}" // blink block + "%e%p1%{2}%=" "%t%{112}" // steady block + "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half + // block) + "%e%p1%{4}%=" "%t%{4}" // steady underline + "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) + "%e%p1%{6}%=" "%t%{2}" // steady bar + "%e%{0}" // anything else + "%;" "%dc")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); @@ -1795,21 +1847,25 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } else if (konsolev > 0 && konsolev < 180770) { // Konsole before version 18.07.70: set up a nonce profile. This has // side-effects on temporary font resizing. #6798 - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?" - "%p1%{3}%<" "%t%{0}" // block - "%e%p1%{5}%<" "%t%{2}" // underline - "%e%{1}" // everything else is bar - "%;%d;BlinkingCursorEnabled=%?" - "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, - "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. - "%;%d\x07")); + data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str( + ut, "Ss", + SCREEN_TMUX_WRAP(screen, tmux, + "\x1b]50;CursorShape=%?" + "%p1%{3}%<" "%t%{0}" // block + "%e%p1%{5}%<" "%t%{2}" // underline + "%e%{1}" // everything else is bar + "%;%d;BlinkingCursorEnabled=%?" + "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude + // zero as special, + "%e%p1%{1}%&" // in all other c2ses we can treat bit + // #0 as a flag. + "%;%d\x07")); if (-1 == data->unibi_ext.reset_cursor_style) { data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", ""); } unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, - "\x1b]50;\x07"); + SCREEN_TMUX_WRAP(screen, tmux, "\x1b]50;\x07")); } } } @@ -1838,6 +1894,8 @@ static void augment_terminfo(TUIData *data, const char *term, bool alacritty = terminfo_is_term_family(term, "alacritty"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; + bool screen_host_rxvt = + terminfo_is_term_family(screen && term[6] == '.' ? term + 7 : NULL, "rxvt"); const char *xterm_version = os_getenv("XTERM_VERSION"); bool true_xterm = xterm && !!xterm_version && !bsdvt; @@ -1907,7 +1965,7 @@ static void augment_terminfo(TUIData *data, const char *term, // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( - ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); + ut, NULL, SCREEN_TMUX_WRAP(screen, tmux, "\033]Pl%p1%06x\033\\")); } else if ((xterm || rxvt || tmux || alacritty) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. @@ -1927,21 +1985,27 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. + /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and + /// sent to the host terminal. data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str( ut, "ext.enable_lr_margin", "\x1b[?69h"); data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str( ut, "ext.disable_lr_margin", "\x1b[?69l"); data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.enable_bpaste", "\x1b[?2004h"); + ut, "ext.enable_bpaste", SCREEN_WRAP(screen, "\x1b[?2004h")); data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str( - ut, "ext.disable_bpaste", "\x1b[?2004l"); + ut, "ext.disable_bpaste", SCREEN_WRAP(screen, "\x1b[?2004l")); // For urxvt send BOTH xterm and old urxvt sequences. #8695 data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.enable_focus", - rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); + (rxvt || screen_host_rxvt) + ? SCREEN_WRAP(screen, "\x1b[?1004h\x1b]777;focus;on\x7") + : SCREEN_WRAP(screen, "\x1b[?1004h")); data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str( ut, "ext.disable_focus", - rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); + (rxvt || screen_host_rxvt) + ? SCREEN_WRAP(screen, "\x1b[?1004l\x1b]777;focus;off\x7") + : SCREEN_WRAP(screen, "\x1b[?1004l")); data->unibi_ext.enable_mouse = (int)unibi_add_ext_str( ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); data->unibi_ext.disable_mouse = (int)unibi_add_ext_str( @@ -1973,7 +2037,23 @@ static void flush_buf(UI *ui) uv_buf_t *bufp = &bufs[0]; TUIData *data = ui->data; - if (data->bufpos <= 0 && data->busy == data->is_invisible) { + // The content of the output for each condition is shown in the following + // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is + // no need to output it. + // + // | is_invisible | !is_invisible + // ------+-----------------+--------------+--------------- + // busy | want_invisible | N/A | invis + // | !want_invisible | N/A | invis + // ------+-----------------+--------------+--------------- + // !busy | want_invisible | N/A | invis + // | !want_invisible | norm | invis + norm + // ------+-----------------+--------------+--------------- + // + if (data->bufpos <= 0 + && ((data->is_invisible && data->busy) + || (data->is_invisible && !data->busy && data->want_invisible) + || (!data->is_invisible && !data->busy && !data->want_invisible))) { return; } @@ -2000,8 +2080,8 @@ static void flush_buf(UI *ui) bufp->base = data->norm; bufp->len = UV_BUF_LEN(data->normlen); bufp++; + data->is_invisible = false; } - data->is_invisible = false; } uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), diff --git a/src/nvim/version.c b/src/nvim/version.c index 9c14ced2ad..15a9713c7c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -398,7 +398,7 @@ static const int included_patches[] = { 1523, 1522, 1521, - // 1520, + 1520, 1519, 1518, 1517, @@ -530,7 +530,7 @@ static const int included_patches[] = { 1391, 1390, 1389, - // 1388, + 1388, 1387, 1386, 1385, @@ -789,7 +789,7 @@ static const int included_patches[] = { 1132, 1131, 1130, - // 1129, + 1129, 1128, 1127, 1126, diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 369b826adf..e53fb5b9e2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -812,4 +812,78 @@ describe('LSP', function() }, buf_lines(1)) end) end) + + describe('completion_list_to_complete_items', function() + -- Completion option precedence: + -- textEdit.newText > insertText > label + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion + it('should choose right completion option', function () + local prefix = 'foo' + local completion_list = { + -- resolves into label + { label='foobar' }, + { label='foobar', textEdit={} }, + -- resolves into insertText + { label='foocar', insertText='foobar' }, + { label='foocar', insertText='foobar', textEdit={} }, + -- resolves into textEdit.newText + { label='foocar', insertText='foodar', textEdit={newText='foobar'} }, + { label='foocar', textEdit={newText='foobar'} } + } + local completion_list_items = {items=completion_list} + local expected = { + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = '', menu = '', word = 'foobar'}, + } + + eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix)) + eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix)) + eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix)) + end) + end) + describe('buf_diagnostics_save_positions', function() + it('stores the diagnostics in diagnostics_by_buf', function () + local diagnostics = { + { range = {}; message = "diag1" }, + { range = {}; message = "diag2" }, + } + exec_lua([[ + vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics) + eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]]) + eq(diagnostics, exec_lua [[ + for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do + return diagnostics + end + ]]) + end) + end) + describe('lsp.util.show_line_diagnostics', function() + it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() + eq(3, exec_lua [[ + local buffer = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buffer, 0, -1, false, { + "testing"; + "123"; + }) + local diagnostics = { + { + range = { + start = { line = 0; character = 1; }; + ["end"] = { line = 0; character = 3; }; + }; + severity = vim.lsp.protocol.DiagnosticSeverity.Error; + message = "Syntax error"; + }, + } + vim.api.nvim_win_set_buf(0, buffer) + vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics) + local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics() + return popup_bufnr + ]]) + end) + end) end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index ee3db7ae97..0eb5901b3b 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -87,6 +87,36 @@ describe(':terminal mouse', function() {3:-- TERMINAL --} | ]]) end) + + it('will forward mouse clicks to the program with the correct even if set nu', function() + if helpers.pending_win32(pending) then return end + nvim('command', 'set number') + -- When the display area such as a number is clicked, it returns to the + -- normal mode. + feed('<LeftMouse><3,0>') + eq('n', eval('mode()')) + screen:expect([[ + {7: 11 }^line28 | + {7: 12 }line29 | + {7: 13 }line30 | + {7: 14 }mouse enabled | + {7: 15 }rows: 6, cols: 46 | + {7: 16 }{2: } | + | + ]]) + -- If click on the coordinate (0,1) of the region of the terminal + -- (i.e. the coordinate (4,1) of vim), 'CSI !"' is sent to the terminal. + feed('i<LeftMouse><4,1>') + screen:expect([[ + {7: 11 }line28 | + {7: 12 }line29 | + {7: 13 }line30 | + {7: 14 }mouse enabled | + {7: 15 }rows: 6, cols: 46 | + {7: 16 } !"{1: } | + {3:-- TERMINAL --} | + ]]) + end) end) describe('with a split window and other buffer', function() @@ -148,7 +178,7 @@ describe(':terminal mouse', function() end) it('wont lose focus if another window is scrolled', function() - feed('<ScrollWheelUp><0,0><ScrollWheelUp><0,0>') + feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>') screen:expect([[ {7: 21 }line │line30 | {7: 22 }line │rows: 5, cols: 25 | @@ -158,7 +188,7 @@ describe(':terminal mouse', function() ========== ========== | {3:-- TERMINAL --} | ]]) - feed('<S-ScrollWheelDown><0,0>') + feed('<S-ScrollWheelDown><4,0>') screen:expect([[ {7: 26 }line │line30 | {7: 27 }line │rows: 5, cols: 25 | |