mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lsp): support linked editing ranges #34388
ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_linkedEditingRange
This commit is contained in:
		| @@ -2285,6 +2285,38 @@ is_enabled({bufnr})                      *vim.lsp.document_color.is_enabled()* | ||||
|         (`boolean`) | ||||
|  | ||||
|  | ||||
| ============================================================================== | ||||
| Lua module: vim.lsp.linked_editing_range            *lsp-linked_editing_range* | ||||
|  | ||||
| The `vim.lsp.linked_editing_range` module enables "linked editing" via a | ||||
| language server's `textDocument/linkedEditingRange` request. Linked editing | ||||
| ranges are synchronized text regions, meaning changes in one range are | ||||
| mirrored in all the others. This is helpful in HTML files for example, where | ||||
| the language server can update the text of a closing tag if its opening tag | ||||
| was changed. | ||||
|  | ||||
| LSP spec: | ||||
| https://microsoft.github.io/language-server-protocol/specification/#textDocument_linkedEditingRange | ||||
|  | ||||
|  | ||||
| enable({enable}, {filter})             *vim.lsp.linked_editing_range.enable()* | ||||
|     Enable or disable a linked editing session globally or for a specific | ||||
|     client. The following is a practical usage example: >lua | ||||
|         vim.lsp.start({ | ||||
|           name = 'html', | ||||
|           cmd = '…', | ||||
|           on_attach = function(client) | ||||
|             vim.lsp.linked_editing_range.enable(true, { client_id = client.id }) | ||||
|           end, | ||||
|         }) | ||||
| < | ||||
|  | ||||
|     Parameters: ~ | ||||
|       • {enable}  (`boolean?`) `true` or `nil` to enable, `false` to disable. | ||||
|       • {filter}  (`table?`) Optional filters |kwargs|: | ||||
|                   • {client_id} (`integer?`) Client ID, or `nil` for all. | ||||
|  | ||||
|  | ||||
| ============================================================================== | ||||
| Lua module: vim.lsp.util                                            *lsp-util* | ||||
|  | ||||
|   | ||||
| @@ -212,6 +212,8 @@ LSP | ||||
| • When inside the float created by |vim.diagnostic.open_float()| and the | ||||
|   cursor is on a line with `DiagnosticRelatedInformation`, |gf| can be used to | ||||
|   jump to the problematic location. | ||||
| • Support for `textDocument/linkedEditingRange`: |lsp-linked_editing_range| | ||||
|   https://microsoft.github.io/language-server-protocol/specification/#textDocument_linkedEditingRange | ||||
|  | ||||
| LUA | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ local lsp = vim._defer_require('vim.lsp', { | ||||
|   document_color = ..., --- @module 'vim.lsp.document_color' | ||||
|   handlers = ..., --- @module 'vim.lsp.handlers' | ||||
|   inlay_hint = ..., --- @module 'vim.lsp.inlay_hint' | ||||
|   linked_editing_range = ..., --- @module 'vim.lsp.linked_editing_range' | ||||
|   log = ..., --- @module 'vim.lsp.log' | ||||
|   protocol = ..., --- @module 'vim.lsp.protocol' | ||||
|   rpc = ..., --- @module 'vim.lsp.rpc' | ||||
|   | ||||
| @@ -205,6 +205,8 @@ local validate = vim.validate | ||||
| --- See [vim.lsp.ClientConfig]. | ||||
| --- @field workspace_folders lsp.WorkspaceFolder[]? | ||||
| --- | ||||
| --- Whether linked editing ranges are enabled for this client. | ||||
| --- @field _linked_editing_enabled boolean? | ||||
| --- | ||||
| --- Track this so that we can escalate automatically if we've already tried a | ||||
| --- graceful shutdown | ||||
|   | ||||
							
								
								
									
										352
									
								
								runtime/lua/vim/lsp/linked_editing_range.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								runtime/lua/vim/lsp/linked_editing_range.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,352 @@ | ||||
| --- @brief | ||||
| --- The `vim.lsp.linked_editing_range` module enables "linked editing" via a language server's | ||||
| --- `textDocument/linkedEditingRange` request. Linked editing ranges are synchronized text regions, | ||||
| --- meaning changes in one range are mirrored in all the others. This is helpful in HTML files for | ||||
| --- example, where the language server can update the text of a closing tag if its opening tag was | ||||
| --- changed. | ||||
| --- | ||||
| --- LSP spec: https://microsoft.github.io/language-server-protocol/specification/#textDocument_linkedEditingRange | ||||
|  | ||||
| local util = require('vim.lsp.util') | ||||
| local log = require('vim.lsp.log') | ||||
| local lsp = vim.lsp | ||||
| local method = require('vim.lsp.protocol').Methods.textDocument_linkedEditingRange | ||||
| local Range = require('vim.treesitter._range') | ||||
| local api = vim.api | ||||
| local M = {} | ||||
|  | ||||
| ---@class (private) vim.lsp.linked_editing_range.state Global state for linked editing ranges | ||||
| ---An optional word pattern (regular expression) that describes valid contents for the given ranges. | ||||
| ---@field word_pattern string | ||||
| ---@field range_index? integer The index of the range that the cursor is on. | ||||
| ---@field namespace integer namespace for range extmarks | ||||
|  | ||||
| ---@class (private) vim.lsp.linked_editing_range.LinkedEditor | ||||
| ---@field active table<integer, vim.lsp.linked_editing_range.LinkedEditor> | ||||
| ---@field bufnr integer | ||||
| ---@field augroup integer augroup for buffer events | ||||
| ---@field client_states table<integer, vim.lsp.linked_editing_range.state> | ||||
| local LinkedEditor = { active = {} } | ||||
|  | ||||
| ---@package | ||||
| ---@param client_id integer | ||||
| function LinkedEditor:attach(client_id) | ||||
|   if self.client_states[client_id] then | ||||
|     return | ||||
|   end | ||||
|   self.client_states[client_id] = { | ||||
|     namespace = api.nvim_create_namespace('nvim.lsp.linked_editing_range:' .. client_id), | ||||
|     word_pattern = '^[%w%-_]*$', | ||||
|   } | ||||
| end | ||||
|  | ||||
| ---@package | ||||
| ---@param bufnr integer | ||||
| ---@param client_state vim.lsp.linked_editing_range.state | ||||
| local function clear_ranges(bufnr, client_state) | ||||
|   api.nvim_buf_clear_namespace(bufnr, client_state.namespace, 0, -1) | ||||
|   client_state.range_index = nil | ||||
| end | ||||
|  | ||||
| ---@package | ||||
| ---@param client_id integer | ||||
| function LinkedEditor:detach(client_id) | ||||
|   local client_state = self.client_states[client_id] | ||||
|   if not client_state then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   --TODO: delete namespace if/when that becomes possible | ||||
|   clear_ranges(self.bufnr, client_state) | ||||
|   self.client_states[client_id] = nil | ||||
|  | ||||
|   -- Destroy the LinkedEditor instance if we are detaching the last client | ||||
|   if vim.tbl_isempty(self.client_states) then | ||||
|     api.nvim_del_augroup_by_id(self.augroup) | ||||
|     LinkedEditor.active[self.bufnr] = nil | ||||
|   end | ||||
| end | ||||
|  | ||||
| ---Syncs the text of each linked editing range after a range has been edited. | ||||
| --- | ||||
| ---@package | ||||
| ---@param bufnr integer | ||||
| ---@param client_state vim.lsp.linked_editing_range.state | ||||
| local function update_ranges(bufnr, client_state) | ||||
|   if not client_state.range_index then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local ns = client_state.namespace | ||||
|   local ranges = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) | ||||
|   if #ranges <= 1 then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local r = assert(ranges[client_state.range_index]) | ||||
|   local replacement = api.nvim_buf_get_text(bufnr, r[2], r[3], r[4].end_row, r[4].end_col, {}) | ||||
|  | ||||
|   if not string.match(table.concat(replacement, '\n'), client_state.word_pattern) then | ||||
|     clear_ranges(bufnr, client_state) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   -- Join text update changes into one undo chunk. If we came here from an undo, then return. | ||||
|   local success = pcall(vim.cmd.undojoin) | ||||
|   if not success then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   for i, range in ipairs(ranges) do | ||||
|     if i ~= client_state.range_index then | ||||
|       api.nvim_buf_set_text( | ||||
|         bufnr, | ||||
|         range[2], | ||||
|         range[3], | ||||
|         range[4].end_row, | ||||
|         range[4].end_col, | ||||
|         replacement | ||||
|       ) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| ---|lsp-handler| for the `textDocument/linkedEditingRange` request. Sets marks for the given ranges | ||||
| ---(if present) and tracks which range the cursor is currently inside. | ||||
| --- | ||||
| ---@package | ||||
| ---@param err lsp.ResponseError? | ||||
| ---@param result lsp.LinkedEditingRanges? | ||||
| ---@param ctx lsp.HandlerContext | ||||
| function LinkedEditor:handler(err, result, ctx) | ||||
|   if err then | ||||
|     log.error('linkededitingrange', err) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local client_id = ctx.client_id | ||||
|   local client_state = self.client_states[client_id] | ||||
|   if not client_state then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local bufnr = assert(ctx.bufnr) | ||||
|   if not api.nvim_buf_is_loaded(bufnr) or util.buf_versions[bufnr] ~= ctx.version then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   clear_ranges(bufnr, client_state) | ||||
|  | ||||
|   if not result then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local client = assert(lsp.get_client_by_id(client_id)) | ||||
|  | ||||
|   local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) | ||||
|   local curpos = api.nvim_win_get_cursor(0) | ||||
|   local cursor_range = { curpos[1] - 1, curpos[2], curpos[1] - 1, curpos[2] } | ||||
|   for i, range in ipairs(result.ranges) do | ||||
|     local start_line = range.start.line | ||||
|     local line = lines and lines[start_line + 1] or '' | ||||
|     local start_col = vim.str_byteindex(line, client.offset_encoding, range.start.character, false) | ||||
|     local end_line = range['end'].line | ||||
|     line = lines and lines[end_line + 1] or '' | ||||
|     local end_col = vim.str_byteindex(line, client.offset_encoding, range['end'].character, false) | ||||
|  | ||||
|     api.nvim_buf_set_extmark(bufnr, client_state.namespace, start_line, start_col, { | ||||
|       end_line = end_line, | ||||
|       end_col = end_col, | ||||
|       hl_group = 'LspReferenceTarget', | ||||
|       right_gravity = false, | ||||
|       end_right_gravity = true, | ||||
|     }) | ||||
|  | ||||
|     local range_tuple = { start_line, start_col, end_line, end_col } | ||||
|     if Range.contains(range_tuple, cursor_range) then | ||||
|       client_state.range_index = i | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   -- TODO: Apply the client's own word pattern, if it exists | ||||
| end | ||||
|  | ||||
| ---Refreshes the linked editing ranges by issuing a new request. | ||||
| ---@package | ||||
| function LinkedEditor:refresh() | ||||
|   local bufnr = self.bufnr | ||||
|  | ||||
|   util._cancel_requests({ | ||||
|     bufnr = bufnr, | ||||
|     method = method, | ||||
|     type = 'pending', | ||||
|   }) | ||||
|   lsp.buf_request(bufnr, method, function(client) | ||||
|     return util.make_position_params(0, client.offset_encoding) | ||||
|   end, function(...) | ||||
|     self:handler(...) | ||||
|   end) | ||||
| end | ||||
|  | ||||
| ---Construct a new LinkedEditor for the buffer. | ||||
| --- | ||||
| ---@private | ||||
| ---@param bufnr integer | ||||
| ---@return vim.lsp.linked_editing_range.LinkedEditor | ||||
| function LinkedEditor.new(bufnr) | ||||
|   local self = setmetatable({}, { __index = LinkedEditor }) | ||||
|  | ||||
|   self.bufnr = bufnr | ||||
|   local augroup = | ||||
|     api.nvim_create_augroup('nvim.lsp.linked_editing_range:' .. bufnr, { clear = true }) | ||||
|   self.augroup = augroup | ||||
|   self.client_states = {} | ||||
|  | ||||
|   api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { | ||||
|     buffer = bufnr, | ||||
|     group = augroup, | ||||
|     callback = function() | ||||
|       for _, client_state in pairs(self.client_states) do | ||||
|         update_ranges(bufnr, client_state) | ||||
|       end | ||||
|       self:refresh() | ||||
|     end, | ||||
|   }) | ||||
|   api.nvim_create_autocmd('CursorMoved', { | ||||
|     group = augroup, | ||||
|     buffer = bufnr, | ||||
|     callback = function() | ||||
|       self:refresh() | ||||
|     end, | ||||
|   }) | ||||
|   api.nvim_create_autocmd('LspDetach', { | ||||
|     group = augroup, | ||||
|     buffer = bufnr, | ||||
|     callback = function(args) | ||||
|       self:detach(args.data.client_id) | ||||
|     end, | ||||
|   }) | ||||
|  | ||||
|   LinkedEditor.active[bufnr] = self | ||||
|   return self | ||||
| end | ||||
|  | ||||
| ---@param bufnr integer | ||||
| ---@param client vim.lsp.Client | ||||
| local function attach_linked_editor(bufnr, client) | ||||
|   local client_id = client.id | ||||
|   if not lsp.buf_is_attached(bufnr, client_id) then | ||||
|     vim.notify( | ||||
|       '[LSP] Client with id ' .. client_id .. ' not attached to buffer ' .. bufnr, | ||||
|       vim.log.levels.WARN | ||||
|     ) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   if not vim.tbl_get(client.server_capabilities, 'linkedEditingRangeProvider') then | ||||
|     vim.notify('[LSP] Server does not support linked editing ranges', vim.log.levels.WARN) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   local linked_editor = LinkedEditor.active[bufnr] or LinkedEditor.new(bufnr) | ||||
|   linked_editor:attach(client_id) | ||||
|   linked_editor:refresh() | ||||
| end | ||||
|  | ||||
| ---@param bufnr integer | ||||
| ---@param client vim.lsp.Client | ||||
| local function detach_linked_editor(bufnr, client) | ||||
|   local linked_editor = LinkedEditor.active[bufnr] | ||||
|   if not linked_editor then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   linked_editor:detach(client.id) | ||||
| end | ||||
|  | ||||
| api.nvim_create_autocmd('LspAttach', { | ||||
|   desc = 'Enable linked editing ranges for all buffers this client attaches to, if enabled', | ||||
|   callback = function(ev) | ||||
|     local client = assert(lsp.get_client_by_id(ev.data.client_id)) | ||||
|     if not client._linked_editing_enabled or not client:supports_method(method, ev.buf) then | ||||
|       return | ||||
|     end | ||||
|  | ||||
|     attach_linked_editor(ev.buf, client) | ||||
|   end, | ||||
| }) | ||||
|  | ||||
| ---@param enable boolean | ||||
| ---@param client vim.lsp.Client | ||||
| local function toggle_linked_editing_for_client(enable, client) | ||||
|   local handler = enable and attach_linked_editor or detach_linked_editor | ||||
|  | ||||
|   -- Toggle for buffers already attached. | ||||
|   for bufnr, _ in pairs(client.attached_buffers) do | ||||
|     handler(bufnr, client) | ||||
|   end | ||||
|  | ||||
|   client._linked_editing_enabled = enable | ||||
| end | ||||
|  | ||||
| ---@param enable boolean | ||||
| local function toggle_linked_editing_globally(enable) | ||||
|   -- Toggle for clients that have already attached. | ||||
|   local clients = lsp.get_clients({ method = method }) | ||||
|   for _, client in ipairs(clients) do | ||||
|     toggle_linked_editing_for_client(enable, client) | ||||
|   end | ||||
|  | ||||
|   -- If disabling, only clear the attachment autocmd. If enabling, create it. | ||||
|   local group = api.nvim_create_augroup('nvim.lsp.linked_editing_range', { clear = true }) | ||||
|   if enable then | ||||
|     api.nvim_create_autocmd('LspAttach', { | ||||
|       group = group, | ||||
|       desc = 'Enable linked editing ranges for all clients', | ||||
|       callback = function(ev) | ||||
|         local client = assert(lsp.get_client_by_id(ev.data.client_id)) | ||||
|         if client:supports_method(method, ev.buf) then | ||||
|           attach_linked_editor(ev.buf, client) | ||||
|         end | ||||
|       end, | ||||
|     }) | ||||
|   end | ||||
| end | ||||
|  | ||||
| --- Optional filters |kwargs|: | ||||
| --- @inlinedoc | ||||
| --- @class vim.lsp.linked_editing_range.enable.Filter | ||||
| --- @field client_id integer? Client ID, or `nil` for all. | ||||
|  | ||||
| --- Enable or disable a linked editing session globally or for a specific client. The following is a | ||||
| --- practical usage example: | ||||
| --- | ||||
| --- ```lua | ||||
| --- vim.lsp.start({ | ||||
| ---   name = 'html', | ||||
| ---   cmd = '…', | ||||
| ---   on_attach = function(client) | ||||
| ---     vim.lsp.linked_editing_range.enable(true, { client_id = client.id }) | ||||
| ---   end, | ||||
| --- }) | ||||
| --- ``` | ||||
| --- | ||||
| ---@param enable boolean? `true` or `nil` to enable, `false` to disable. | ||||
| ---@param filter vim.lsp.linked_editing_range.enable.Filter? | ||||
| function M.enable(enable, filter) | ||||
|   vim.validate('enable', enable, 'boolean', true) | ||||
|   vim.validate('filter', filter, 'table', true) | ||||
|  | ||||
|   enable = enable ~= false | ||||
|   filter = filter or {} | ||||
|  | ||||
|   if filter.client_id then | ||||
|     local client = | ||||
|       assert(lsp.get_client_by_id(filter.client_id), 'Client not found for id ' .. filter.client_id) | ||||
|     toggle_linked_editing_for_client(enable, client) | ||||
|   else | ||||
|     toggle_linked_editing_globally(enable) | ||||
|   end | ||||
| end | ||||
|  | ||||
| return M | ||||
| @@ -556,6 +556,9 @@ function protocol.make_client_capabilities() | ||||
|       selectionRange = { | ||||
|         dynamicRegistration = false, | ||||
|       }, | ||||
|       linkedEditingRange = { | ||||
|         dynamicRegistration = false, | ||||
|       }, | ||||
|     }, | ||||
|     workspace = { | ||||
|       symbol = { | ||||
|   | ||||
| @@ -283,6 +283,7 @@ local config = { | ||||
|       'tagfunc.lua', | ||||
|       'semantic_tokens.lua', | ||||
|       'document_color.lua', | ||||
|       'linked_editing_range.lua', | ||||
|       'handlers.lua', | ||||
|       'util.lua', | ||||
|       'log.lua', | ||||
|   | ||||
							
								
								
									
										137
									
								
								test/functional/plugin/lsp/linked_editing_range_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								test/functional/plugin/lsp/linked_editing_range_spec.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| local t = require('test.testutil') | ||||
| local n = require('test.functional.testnvim')() | ||||
| local t_lsp = require('test.functional.plugin.lsp.testutil') | ||||
|  | ||||
| local eq = t.eq | ||||
| local exec_lua = n.exec_lua | ||||
| local insert = n.insert | ||||
| local feed = n.feed | ||||
|  | ||||
| local clear_notrace = t_lsp.clear_notrace | ||||
| local create_server_definition = t_lsp.create_server_definition | ||||
|  | ||||
| describe('vim.lsp.linked_editing_range', function() | ||||
|   before_each(function() | ||||
|     clear_notrace() | ||||
|  | ||||
|     insert([[ | ||||
|     hello | ||||
|     hello | ||||
|     hello]]) | ||||
|  | ||||
|     exec_lua(create_server_definition) | ||||
|     exec_lua(function() | ||||
|       vim.lsp.linked_editing_range.enable() | ||||
|  | ||||
|       _G.server = _G._create_server({ | ||||
|         capabilities = { | ||||
|           linkedEditingRangeProvider = true, | ||||
|         }, | ||||
|         handlers = { | ||||
|           ['textDocument/linkedEditingRange'] = function(_, _, callback) | ||||
|             callback(nil, { | ||||
|               ranges = { | ||||
|                 { start = { line = 0, character = 0 }, ['end'] = { line = 0, character = 5 } }, | ||||
|                 { start = { line = 1, character = 0 }, ['end'] = { line = 1, character = 5 } }, | ||||
|                 { start = { line = 2, character = 0 }, ['end'] = { line = 2, character = 5 } }, | ||||
|               }, | ||||
|             }) | ||||
|           end, | ||||
|         }, | ||||
|       }) | ||||
|  | ||||
|       _G.server_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) | ||||
|     end) | ||||
|   end) | ||||
|  | ||||
|   it('initiates linked editing', function() | ||||
|     exec_lua(function() | ||||
|       local win = vim.api.nvim_get_current_win() | ||||
|       vim.api.nvim_win_set_cursor(win, { 1, 0 }) | ||||
|     end) | ||||
|     -- Deletion | ||||
|     feed('ldw') | ||||
|     eq( | ||||
|       { | ||||
|         'h', | ||||
|         'h', | ||||
|         'h', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     -- Insertion | ||||
|     feed('Apt<Esc>') | ||||
|     eq( | ||||
|       { | ||||
|         'hpt', | ||||
|         'hpt', | ||||
|         'hpt', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     -- Undo/redo | ||||
|     feed('0xx') | ||||
|     eq( | ||||
|       { | ||||
|         't', | ||||
|         't', | ||||
|         't', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     feed('u') | ||||
|     eq( | ||||
|       { | ||||
|         'pt', | ||||
|         'pt', | ||||
|         'pt', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     feed('u') | ||||
|     eq( | ||||
|       { | ||||
|         'hpt', | ||||
|         'hpt', | ||||
|         'hpt', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     feed('<C-r><C-r>') | ||||
|     eq( | ||||
|       { | ||||
|         't', | ||||
|         't', | ||||
|         't', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|     -- Disabling | ||||
|     exec_lua(function() | ||||
|       vim.lsp.linked_editing_range.enable(false, { client_id = _G.server_id }) | ||||
|     end) | ||||
|     feed('Ipp<Esc>') | ||||
|     eq( | ||||
|       { | ||||
|         'ppt', | ||||
|         't', | ||||
|         't', | ||||
|       }, | ||||
|       exec_lua(function() | ||||
|         return vim.api.nvim_buf_get_lines(0, 0, -1, false) | ||||
|       end) | ||||
|     ) | ||||
|   end) | ||||
| end) | ||||
		Reference in New Issue
	
	Block a user
	 Riley Bruins
					Riley Bruins