@@ -359,50 +359,128 @@ end
359
359
360
360
--- Renames all references to the symbol under the cursor.
361
361
---
362
- --- @param new_name (string ) If not provided , the user will be prompted for a new
363
- --- name using |vim.ui.input()|.
364
- function M .rename (new_name )
365
- local opts = {
366
- prompt = " New Name: "
367
- }
362
+ --- @param new_name string | nil If not provided , the user will be prompted for a new
363
+ --- name using |vim.ui.input()|.
364
+ --- @param options table | nil additional options
365
+ --- - filter (function|nil):
366
+ --- Predicate to filter clients used for rename.
367
+ --- Receives the attached clients as argument and must return a list of
368
+ --- clients.
369
+ --- - name (string|nil):
370
+ --- Restrict clients used for rename to ones where client.name matches
371
+ --- this field.
372
+ function M .rename (new_name , options )
373
+ options = options or {}
374
+ local bufnr = options .bufnr or vim .api .nvim_get_current_buf ()
375
+ local clients = vim .lsp .buf_get_clients (bufnr )
368
376
369
- --- @private
370
- local function on_confirm (input )
371
- if not (input and # input > 0 ) then return end
372
- local params = util .make_position_params ()
373
- params .newName = input
374
- request (' textDocument/rename' , params )
377
+ if options .filter then
378
+ clients = options .filter (clients )
379
+ elseif options .name then
380
+ clients = vim .tbl_filter (
381
+ function (client ) return client .name == options .name end ,
382
+ clients
383
+ )
384
+ end
385
+
386
+ if # clients == 0 then
387
+ vim .notify (" [LSP] Rename request failed, no matching language servers." )
375
388
end
376
389
390
+ local win = vim .api .nvim_get_current_win ()
391
+
392
+ -- Compute early to account for cursor movements after going async
393
+ local cword = vfn .expand (' <cword>' )
394
+
377
395
--- @private
378
- local function prepare_rename (err , result )
379
- if err == nil and result == nil then
380
- vim .notify (' nothing to rename' , vim .log .levels .INFO )
396
+ local function get_text_at_range (range )
397
+ return vim .api .nvim_buf_get_text (
398
+ bufnr ,
399
+ range .start .line ,
400
+ range .start .character ,
401
+ range [' end' ].line ,
402
+ range [' end' ].character ,
403
+ {}
404
+ )[1 ]
405
+ end
406
+
407
+ local try_use_client
408
+ try_use_client = function (idx , client )
409
+ if not client then
381
410
return
382
411
end
383
- if result and result .placeholder then
384
- opts .default = result .placeholder
385
- if not new_name then npcall (vim .ui .input , opts , on_confirm ) end
386
- elseif result and result .start and result [' end' ] and
387
- result .start .line == result [' end' ].line then
388
- local line = vfn .getline (result .start .line + 1 )
389
- local start_char = result .start .character + 1
390
- local end_char = result [' end' ].character
391
- opts .default = string.sub (line , start_char , end_char )
392
- if not new_name then npcall (vim .ui .input , opts , on_confirm ) end
412
+
413
+ --- @private
414
+ local function rename (name )
415
+ local params = util .make_position_params (win , client .offset_encoding )
416
+ params .newName = name
417
+ local handler = client .handlers [' textDocument/rename' ] or vim .lsp .handlers [' textDocument/rename' ]
418
+ client .request (' textDocument/rename' , params , function (...)
419
+ handler (... )
420
+ try_use_client (next (clients , idx ))
421
+ end , bufnr )
422
+ end
423
+
424
+ if client .supports_method (" textDocument/prepareRename" ) then
425
+ local params = util .make_position_params (win , client .offset_encoding )
426
+ client .request (' textDocument/prepareRename' , params , function (err , result )
427
+ if err or result == nil then
428
+ if next (clients , idx ) then
429
+ try_use_client (next (clients , idx ))
430
+ else
431
+ local msg = err and (' Error on prepareRename: ' .. (err .message or ' ' )) or ' Nothing to rename'
432
+ vim .notify (msg , vim .log .levels .INFO )
433
+ end
434
+ return
435
+ end
436
+
437
+ if new_name then
438
+ rename (new_name )
439
+ return
440
+ end
441
+
442
+ local prompt_opts = {
443
+ prompt = " New Name: "
444
+ }
445
+ -- result: Range | { range: Range, placeholder: string }
446
+ if result .placeholder then
447
+ prompt_opts .default = result .placeholder
448
+ elseif result .start then
449
+ prompt_opts .default = get_text_at_range (result )
450
+ elseif result .range then
451
+ prompt_opts .default = get_text_at_range (result .range )
452
+ else
453
+ prompt_opts .default = cword
454
+ end
455
+ vim .ui .input (prompt_opts , function (input )
456
+ if not input or # input == 0 then
457
+ return
458
+ end
459
+ rename (input )
460
+ end )
461
+ end , bufnr )
462
+ elseif client .supports_method (" textDocument/rename" ) then
463
+ if new_name then
464
+ rename (new_name )
465
+ return
466
+ end
467
+
468
+ local prompt_opts = {
469
+ prompt = " New Name: " ,
470
+ default = cword
471
+ }
472
+ vim .ui .input (prompt_opts , function (input )
473
+ if not input or # input == 0 then
474
+ return
475
+ end
476
+ rename (input )
477
+ end )
393
478
else
394
- -- fallback to guessing symbol using <cword>
395
- --
396
- -- this can happen if the language server does not support prepareRename,
397
- -- returns an unexpected response, or requests for "default behavior"
398
- --
399
- -- see https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename
400
- opts .default = vfn .expand (' <cword>' )
401
- if not new_name then npcall (vim .ui .input , opts , on_confirm ) end
479
+ vim .notify (' Client ' .. client .id .. ' /' .. client .name .. ' has no rename capability' )
402
480
end
403
- if new_name then on_confirm (new_name ) end
404
481
end
405
- request (' textDocument/prepareRename' , util .make_position_params (), prepare_rename )
482
+
483
+ try_use_client (next (clients ))
406
484
end
407
485
408
486
--- Lists all the references to the symbol under the cursor in the quickfix window.
0 commit comments