or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/coc.nvim@0.0.x

docs

advanced

commands.mdcursor-navigation.mdmulti-cursor.mdrpc-lsp.mdsnippets.md
index.md
tile.json

tessl/npm-coc-nvim

tessl install tessl/npm-coc-nvim@0.0.0

LSP based intellisense engine for neovim & vim8.

multi-cursor.mddocs/advanced/

Multi-Cursor Editing [Intermediate]

Complexity: Intermediate | Category: Advanced | Keywords: multi-cursor, cursors, selection, editing

Common Tasks: Select word occurrences | Add cursors manually | Edit multiple locations | Range selection

Multiple cursor support for simultaneous editing at multiple locations.

Common Tasks

Select word and add more occurrences

nmap <silent> <C-d> <Plug>(coc-cursors-word)

Press repeatedly to add more matches.

Add cursor at current position

nmap <silent> <C-c> <Plug>(coc-cursors-position)

Manually place cursors where needed.

Select range in visual mode

xmap <silent> <C-c> <Plug>(coc-cursors-range)

Add multiple cursor ranges from selection.

Check if multi-cursor is active

if get(b:, 'coc_cursors_activated', 0)
  echo "Multi-cursor mode active"
endif

Query buffer state for multi-cursor mode.

Key Mappings

<Plug>(coc-cursors-operator)

<Plug>(coc-cursors-operator)

Add cursors via operator motion.

Example:

nmap <silent> <C-c> <Plug>(coc-cursors-operator)

" Then use with motions:
" <C-c>iw - add cursor to word
" <C-c>ap - add cursor to paragraph

<Plug>(coc-cursors-word)

<Plug>(coc-cursors-word)

Add cursor to word under cursor. Press multiple times to add more occurrences.

Example:

nmap <expr> <silent> <C-d> <SID>select_current_word()

function! s:select_current_word()
  if !get(b:, 'coc_cursors_activated', 0)
    return "\<Plug>(coc-cursors-word)"
  endif
  return "\<Plug>(coc-cursors-word)"
endfunction

<Plug>(coc-cursors-position)

<Plug>(coc-cursors-position)

Add cursor at current position.

Example:

nmap <silent> <C-c>p <Plug>(coc-cursors-position)

<Plug>(coc-cursors-range)

<Plug>(coc-cursors-range)

Add cursor range in visual mode.

Example:

xmap <silent> <C-c> <Plug>(coc-cursors-range)

CocAction API

cursorsSelect

CocAction('cursorsSelect', bufnr, mode, type)

Select cursor ranges programmatically.

Parameters:

  • bufnr: Buffer number
  • mode: Selection mode ('n', 'v', 'V', etc.)
  • type: Selection type ('word', 'position', 'range')

Example:

call CocAction('cursorsSelect', bufnr('%'), 'n', 'word')

addRanges

CocAction('addRanges', ranges)

Add multiple cursor ranges.

Parameters:

  • ranges: List of range objects with start and end LSP positions

Example:

let ranges = [
  \ {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 5}},
  \ {'start': {'line': 2, 'character': 0}, 'end': {'line': 2, 'character': 5}}
  \ ]
call CocAction('addRanges', ranges)

Buffer Variables

b:coc_cursors_activated

b:coc_cursors_activated

Indicates if multi-cursor mode is active in buffer.

Type: Number (read-only) Values: 1 when active, 0 otherwise

Example:

if get(b:, 'coc_cursors_activated', 0)
  echo "Multi-cursor mode is active"
endif

Global Variables

g:coc_selected_text

g:coc_selected_text

Last selected text in multi-cursor mode.

Type: String (read-only)

Example:

if exists('g:coc_selected_text')
  echo 'Selected: ' . g:coc_selected_text
endif

Configuration

Multi-cursor settings in coc-settings.json:

{
  "cursors.cancelKey": "<esc>",
  "cursors.nextKey": "<C-n>",
  "cursors.previousKey": "<C-p>"
}

Highlight Groups

CocCursorRange

CocCursorRange

Highlight for cursor ranges.

Example:

highlight CocCursorRange ctermbg=237 guibg=#3a3a3a

Usage Examples

Basic Multi-Cursor Setup

" Select word and add more occurrences
nmap <silent> <C-d> <Plug>(coc-cursors-word)

" Add cursor at position
nmap <silent> <C-c> <Plug>(coc-cursors-position)

" Visual range
xmap <silent> <C-c> <Plug>(coc-cursors-range)

" Operator mode
nmap <expr> <silent> <C-c> <SID>coc_cursors_operator()
function! s:coc_cursors_operator()
  return "\<Plug>(coc-cursors-operator)"
endfunction

VSCode-like Multi-Cursor

" Ctrl-D to select word and add next occurrence
nmap <expr> <silent> <C-d> <SID>select_current_word()
function! s:select_current_word()
  if !get(b:, 'coc_cursors_activated', 0)
    return "\<Plug>(coc-cursors-word)"
  endif
  return "\<Plug>(coc-cursors-word)*N"
endfunction

" Alt-Click to add cursor (if terminal supports it)
nmap <M-LeftMouse> <Plug>(coc-cursors-position)

Multi-Cursor Status

function! MultiCursorStatus() abort
  if get(b:, 'coc_cursors_activated', 0)
    return '[MULTI]'
  endif
  return ''
endfunction

set statusline+=%{MultiCursorStatus()}

Exit Multi-Cursor Mode

" Press Escape to exit multi-cursor mode
function! ExitCursorMode() abort
  if get(b:, 'coc_cursors_activated', 0)
    call coc#rpc#notify('cursorsCancel', [])
    return "\<Esc>"
  endif
  return "\<Esc>"
endfunction

nnoremap <silent><expr> <Esc> ExitCursorMode()

Select All Occurrences

function! SelectAllOccurrences() abort
  " Get word under cursor
  let word = expand('<cword>')

  " Get all occurrences
  let pattern = '\<' . word . '\>'
  let matches = []
  let lnum = 1

  while lnum <= line('$')
    let line = getline(lnum)
    let col = 0

    while 1
      let col = match(line, pattern, col)
      if col == -1
        break
      endif

      call add(matches, {
        \ 'start': {'line': lnum - 1, 'character': col},
        \ 'end': {'line': lnum - 1, 'character': col + len(word)}
        \ })

      let col += 1
    endwhile

    let lnum += 1
  endwhile

  if len(matches) > 0
    call CocAction('addRanges', matches)
    echo 'Selected ' . len(matches) . ' occurrences'
  else
    echo 'No matches found'
  endif
endfunction

nnoremap <silent> <leader>ma :call SelectAllOccurrences()<CR>

Add Cursor Above/Below

function! AddCursorAbove() abort
  let line = line('.') - 1
  if line > 0
    call cursor(line, col('.'))
    call CocAction('cursorsSelect', bufnr('%'), 'n', 'position')
  endif
endfunction

function! AddCursorBelow() abort
  let line = line('.') + 1
  if line <= line('$')
    call cursor(line, col('.'))
    call CocAction('cursorsSelect', bufnr('%'), 'n', 'position')
  endif
endfunction

nnoremap <silent> <C-S-Up> :call AddCursorAbove()<CR>
nnoremap <silent> <C-S-Down> :call AddCursorBelow()<CR>

Select in Range

function! SelectWordInRange(start, end) abort
  let word = expand('<cword>')
  let pattern = '\<' . word . '\>'
  let matches = []

  for lnum in range(a:start, a:end)
    let line = getline(lnum)
    let col = 0

    while 1
      let col = match(line, pattern, col)
      if col == -1
        break
      endif

      call add(matches, {
        \ 'start': {'line': lnum - 1, 'character': col},
        \ 'end': {'line': lnum - 1, 'character': col + len(word)}
        \ })

      let col += 1
    endwhile
  endfor

  call CocAction('addRanges', matches)
  echo 'Added ' . len(matches) . ' cursors'
endfunction

command! -range SelectInRange call SelectWordInRange(<line1>, <line2>)

Multi-Cursor with Count

function! AddMultipleCursors(count) abort
  for i in range(a:count)
    execute "normal \<Plug>(coc-cursors-word)"
  endfor
  echo 'Added ' . a:count . ' cursors'
endfunction

" Usage: 3<C-d> to add 3 occurrences
nnoremap <silent> <C-d> :<C-u>call AddMultipleCursors(v:count1)<CR>

Skip Current Match

" Skip current match and select next (like Ctrl-K in VSCode)
function! SkipCurrentMatch() abort
  if !get(b:, 'coc_cursors_activated', 0)
    return
  endif

  " Move to next match without selecting current
  call coc#rpc#notify('cursorsNext', [])
endfunction

nmap <silent> <C-k> :call SkipCurrentMatch()<CR>

Visual Multi-Cursor

" Create cursors from visual selection
function! CursorsFromVisual() abort
  let [line_start, column_start] = getpos("'<")[1:2]
  let [line_end, column_end] = getpos("'>")[1:2]

  if line_start == line_end
    " Single line selection
    call CocAction('cursorsSelect', bufnr('%'), 'v', 'range')
  else
    " Multi-line: create cursor at same column on each line
    let ranges = []
    for lnum in range(line_start, line_end)
      call add(ranges, {
        \ 'start': {'line': lnum - 1, 'character': column_start - 1},
        \ 'end': {'line': lnum - 1, 'character': column_start - 1}
        \ })
    endfor
    call CocAction('addRanges', ranges)
  endif
endfunction

xnoremap <silent> <C-n> :<C-u>call CursorsFromVisual()<CR>

Count Cursor Positions

function! CountCursors() abort
  if !get(b:, 'coc_cursors_activated', 0)
    echo "No multi-cursors active"
    return
  endif

  " This is a placeholder - actual count needs to be tracked
  echo "Multi-cursor mode active"
endfunction

command! CountCursors call CountCursors()

Regex Multi-Cursor

function! AddCursorsFromRegex(pattern) abort
  let matches = []
  let lnum = 1

  while lnum <= line('$')
    let line = getline(lnum)
    let col = 0

    while 1
      let match = matchstrpos(line, a:pattern, col)
      if match[1] == -1
        break
      endif

      call add(matches, {
        \ 'start': {'line': lnum - 1, 'character': match[1]},
        \ 'end': {'line': lnum - 1, 'character': match[2]}
        \ })

      let col = match[2]
    endwhile

    let lnum += 1
  endwhile

  if len(matches) > 0
    call CocAction('addRanges', matches)
    echo 'Added ' . len(matches) . ' cursors'
  else
    echo 'No matches for pattern: ' . a:pattern
  endif
endfunction

command! -nargs=1 CursorsRegex call AddCursorsFromRegex(<q-args>)

Column Selection

function! ColumnSelection(start_line, end_line, col) abort
  let ranges = []

  for lnum in range(a:start_line, a:end_line)
    let line_len = len(getline(lnum))
    if a:col <= line_len
      call add(ranges, {
        \ 'start': {'line': lnum - 1, 'character': a:col - 1},
        \ 'end': {'line': lnum - 1, 'character': a:col - 1}
        \ })
    endif
  endfor

  call CocAction('addRanges', ranges)
  echo 'Added ' . len(ranges) . ' cursors'
endfunction

command! -range -nargs=1 ColumnCursors call ColumnSelection(<line1>, <line2>, <args>)
" Usage: :10,20ColumnCursors 5

Smart Word Selection

function! SmartWordSelection() abort
  let word = expand('<cword>')
  let case_sensitive = word =~# '\u'  " Check if word has uppercase

  if case_sensitive
    " Case-sensitive matching
    let pattern = '\<' . word . '\>'
  else
    " Case-insensitive matching
    let pattern = '\c\<' . word . '\>'
  endif

  " Find and select all matches
  " Implementation similar to SelectAllOccurrences()
endfunction

nmap <silent> <leader>sw :call SmartWordSelection()<CR>

Error Handling

No Occurrences Found

Issue: Attempting to select word with no matches.

Solution:

function! SafeSelectOccurrences() abort
  let word = expand('<cword>')

  if empty(word)
    echohl WarningMsg
    echo 'No word under cursor'
    echohl None
    return
  endif

  " Count matches first
  let count = 0
  execute 'silent %s/' . word . '//gn'
  " Check v:statusmsg for count

  if count == 0
    echo 'No occurrences of "' . word . '" found'
    return
  endif

  " Proceed with selection
  call SelectAllOccurrences()
endfunction

Invalid Range

Issue: Adding cursors to invalid line/character positions.

Solution:

function! ValidateRanges(ranges) abort
  let valid_ranges = []
  let max_line = line('$') - 1

  for range in a:ranges
    let line = range.start.line
    let char = range.start.character

    " Skip invalid lines
    if line < 0 || line > max_line
      continue
    endif

    " Clamp character to line length
    let line_text = getline(line + 1)
    let max_char = strchars(line_text)
    if char > max_char
      let char = max_char
    endif

    call add(valid_ranges, {
      \ 'start': {'line': line, 'character': char},
      \ 'end': range.end
      \ })
  endfor

  return valid_ranges
endfunction

Mode Conflicts

Issue: Multi-cursor conflicts with insert mode or other modes.

Solution:

function! SafeActivateCursors() abort
  " Ensure we're in normal mode
  if mode() != 'n'
    execute "normal! \<Esc>"
  endif

  " Check for conflicts
  if exists('b:coc_snippet_active') && b:coc_snippet_active
    echo 'Cannot activate multi-cursor while snippet is active'
    return
  endif

  " Proceed with activation
  call CocAction('cursorsSelect', bufnr('%'), 'n', 'word')
endfunction

Troubleshooting

Problem: Multi-cursor not activating

Symptoms: Key mappings don't enter multi-cursor mode.

Solutions:

  1. Check if coc.nvim is loaded: :echo exists('g:did_coc_loaded')
  2. Verify buffer is not readonly: :set modifiable?
  3. Check for mapping conflicts: :verbose nmap <C-d>
  4. Ensure coc service is running: :CocInfo

Problem: Cursors disappear after edit

Symptoms: Multi-cursor mode exits unexpectedly during editing.

Solutions:

  1. Check cursors.cancelKey setting in coc-settings.json
  2. Avoid using Escape key if it's configured as cancel key
  3. Use alternative exit mapping:
    let g:coc_cursors_cancel_key = '<C-c>'

Problem: Performance issues with many cursors

Symptoms: Lag when editing with many cursors (>100).

Solutions:

  1. Limit number of cursors in settings:
    {
      "cursors.maxCount": 100
    }
  2. Use range-based operations for bulk edits instead:
    :%s/pattern/replacement/g
  3. Disable syntax highlighting temporarily:
    :syntax off

Problem: Column selection misaligned

Symptoms: Cursors don't align vertically in column selection.

Solutions:

  1. Ensure consistent indentation (spaces vs tabs)
  2. Use visual block mode first:
    <C-v> (select column)
    <C-c> (convert to multi-cursor)
  3. Account for tab width in calculations:
    let effective_col = virtcol('.')

Problem: Regex pattern doesn't match

Symptoms: CursorsRegex finds no matches or wrong matches.

Solutions:

  1. Test regex with search first:
    /pattern
  2. Escape special characters:
    call AddCursorsFromRegex('\[.*\]')  " Match [...]
  3. Use very magic mode for complex patterns:
    call AddCursorsFromRegex('\v(pattern)')

Problem: Cursors added to wrong buffer

Symptoms: Multi-cursor activates in different buffer.

Solutions:

  1. Always specify buffer number:
    call CocAction('cursorsSelect', bufnr('%'), 'n', 'word')
  2. Check current buffer before activation:
    if bufnr('%') != expected_bufnr
      execute 'buffer' expected_bufnr
    endif

See Also

  • Cursor Navigation - Position APIs for multi-cursor placement
  • Commands - Execute commands with multi-cursor
  • Code Actions - Refactor with selections
  • Highlights - Customize cursor range highlighting
  • Variables - Configure multi-cursor behavior