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.

cursor-navigation.mddocs/advanced/

Cursor Navigation [Advanced]

Complexity: Advanced | Category: Advanced | Keywords: cursor, position, movement, selection, LSP

Common Tasks: Get cursor position | Move to position | Handle selections | Convert coordinates

Utilities for working with cursor position, movement, and selection in Vim/Neovim. These functions provide character-aware cursor operations that handle multibyte characters correctly.

Common Tasks

Get cursor position in LSP format

let [line, char] = coc#cursor#position()

Returns 0-indexed line and character position.

Move cursor to LSP position

call coc#cursor#move_to(line, character)

Move to specific line and character (0-indexed).

Get visual selection range

let selection = coc#cursor#get_selection(0)
let [start_line, start_char, end_line, end_char] = selection

Returns selection boundaries in LSP format.

Get character offset from buffer start

let offset = coc#cursor#char_offset()

Total character count from beginning of file.

Capabilities

Screen Position

Get cursor position relative to the screen.

/**
 * Get cursor position relative to screen cell (absolute screen coordinates)
 * @returns [row, col] array with 0-indexed screen coordinates
 */
function! coc#cursor#screen_pos()

Usage Example:

let [row, col] = coc#cursor#screen_pos()
echo 'Cursor at screen position:' row ',' col

Column Movement

Move cursor horizontally by a number of columns.

/**
 * Move cursor by column offset (handles positive and negative deltas)
 * @param delta - Number of columns to move (positive=right, negative=left)
 */
function! coc#cursor#move_by_col(delta)

Usage Example:

" Move cursor 5 columns to the right
call coc#cursor#move_by_col(5)

" Move cursor 3 columns to the left
call coc#cursor#move_by_col(-3)

Cursor Position

Get current cursor position in LSP format (0-indexed line and character).

/**
 * Get cursor position in LSP format
 * @returns [line, character] array (0-indexed line, character count not bytes)
 */
function! coc#cursor#position()

Usage Example:

let [line, char] = coc#cursor#position()
echo 'Cursor at line' line 'character' char

" Use with CocAction
call CocAction('doHover')

Note: This function returns character positions, not byte positions, making it safe for use with multibyte characters (e.g., emoji, CJK characters).

Move to Position

Move cursor to a specific line and character position (LSP format).

/**
 * Move cursor to LSP position (0-indexed)
 * @param line - Line number (0-indexed)
 * @param character - Character position (0-indexed, character count not bytes)
 */
function! coc#cursor#move_to(line, character)

Usage Example:

" Move to line 10, character 5 (LSP format)
call coc#cursor#move_to(9, 5)

" Move to start of line 0
call coc#cursor#move_to(0, 0)

" Use with LSP response
let location = response.location
call coc#cursor#move_to(location.range.start.line, location.range.start.character)

Character Offset

Get the character offset of the cursor from the start of the buffer.

/**
 * Get character offset of cursor from buffer start
 * Counts characters, not bytes (multibyte-safe)
 * @returns Character offset (0-indexed from buffer start)
 */
function! coc#cursor#char_offset()

Usage Example:

let offset = coc#cursor#char_offset()
echo 'Cursor is at character offset' offset 'from start of buffer'

" Useful for text document identification
let params = {
  \ 'textDocument': {'uri': 'file://' . expand('%:p')},
  \ 'position': {'character': offset}
  \ }

Note: This function counts actual characters (including newlines), not byte positions. Essential for LSP operations with multibyte text.

Selection Range

Get the latest visual selection range in LSP format.

/**
 * Get latest selection range (from last visual mode)
 * @param char - 1 to force character-wise mode, 0 to use actual visual mode
 * @returns [start_line, start_char, end_line, end_char] or v:null if no selection
 *          All values are 0-indexed, character-based (not bytes)
 */
function! coc#cursor#get_selection(char)

Usage Examples:

" Get last visual selection
let selection = coc#cursor#get_selection(0)
if selection isnot v:null
  let [start_line, start_char, end_line, end_char] = selection
  echo 'Selection from' start_line ':' start_char 'to' end_line ':' end_char
endif

" Force character-wise selection
let char_selection = coc#cursor#get_selection(1)

" Use with LSP range operations
function! ApplyRangeAction() abort
  let selection = coc#cursor#get_selection(0)
  if selection is v:null
    echo 'No selection'
    return
  endif
  let [sl, sc, el, ec] = selection
  let range = {
    \ 'start': {'line': sl, 'character': sc},
    \ 'end': {'line': el, 'character': ec}
    \ }
  call CocAction('codeAction', '', range)
endfunction

Visual Modes:

  • v (character-wise): Returns character range within lines
  • V (line-wise): Returns full line range with end character = 0
  • \<C-v> (block-wise): Returns rectangular selection range

Note: Returns v:null if no visual selection exists. All positions use character counts, not byte offsets.

Common Patterns

LSP Position Conversion

Convert between Vim cursor positions and LSP positions:

" Vim (1-indexed line, 1-indexed byte column) to LSP (0-indexed line, 0-indexed character)
let lsp_pos = coc#cursor#position()

" LSP to Vim
call coc#cursor#move_to(lsp_line, lsp_character)

Safe Multibyte Operations

These functions handle multibyte characters correctly:

" Works correctly with emoji, CJK, etc.
call coc#cursor#move_to(0, 5)  " Moves to 5th character, not 5th byte

let offset = coc#cursor#char_offset()  " Character count, not byte count

Selection-Based Actions

" Visual mode mapping for code actions on selection
vnoremap <silent> <leader>a :call <SID>VisualCodeAction()<CR>

function! s:VisualCodeAction() abort
  let selection = coc#cursor#get_selection(0)
  if selection is v:null
    return
  endif
  let [sl, sc, el, ec] = selection
  call CocAction('codeAction', '', {
    \ 'start': {'line': sl, 'character': sc},
    \ 'end': {'line': el, 'character': ec}
    \ })
endfunction

Building LSP Parameters

" Create textDocument/hover params
function! s:hover_params() abort
  let [line, char] = coc#cursor#position()
  return {
    \ 'textDocument': {'uri': 'file://' . expand('%:p')},
    \ 'position': {'line': line, 'character': char}
    \ }
endfunction

" Create range params from selection
function! s:range_params() abort
  let selection = coc#cursor#get_selection(0)
  if selection is v:null
    return v:null
  endif
  let [sl, sc, el, ec] = selection
  return {
    \ 'textDocument': {'uri': 'file://' . expand('%:p')},
    \ 'range': {
      \ 'start': {'line': sl, 'character': sc},
      \ 'end': {'line': el, 'character': ec}
      \ }
    \ }
endfunction

Smart Navigation

" Jump to definition and save position
function! SmartDefinition() abort
  " Save current position
  let [line, char] = coc#cursor#position()
  let pos = {'file': expand('%:p'), 'line': line, 'character': char}

  " Jump to definition
  call CocAction('jumpDefinition')

  " Save to jump stack
  call add(g:jump_stack, pos)
endfunction

" Jump back
function! JumpBack() abort
  if empty(g:jump_stack)
    echo 'Jump stack empty'
    return
  endif

  let pos = remove(g:jump_stack, -1)
  execute 'edit' pos.file
  call coc#cursor#move_to(pos.line, pos.character)
endfunction

let g:jump_stack = []
nnoremap <silent> gd :call SmartDefinition()<CR>
nnoremap <silent> gb :call JumpBack()<CR>

Relative Movement

" Move cursor relative to current position
function! RelativeMove(lines, chars) abort
  let [line, char] = coc#cursor#position()
  let new_line = line + a:lines
  let new_char = char + a:chars

  " Clamp to buffer bounds
  if new_line < 0
    let new_line = 0
  endif
  if new_line >= line('$')
    let new_line = line('$') - 1
  endif

  call coc#cursor#move_to(new_line, max([0, new_char]))
endfunction

" Move 5 lines down, 2 chars right
call RelativeMove(5, 2)

Selection Validation

" Validate and normalize selection
function! ValidateSelection() abort
  let sel = coc#cursor#get_selection(0)
  if sel is v:null
    echohl ErrorMsg
    echo 'No active selection'
    echohl None
    return v:null
  endif

  let [sl, sc, el, ec] = sel

  " Ensure start is before end
  if sl > el || (sl == el && sc > ec)
    let [sl, sc, el, ec] = [el, ec, sl, sc]
  endif

  return [sl, sc, el, ec]
endfunction

Screen-Relative Operations

" Check if cursor is visible on screen
function! IsCursorVisible() abort
  let [screen_row, screen_col] = coc#cursor#screen_pos()
  let screen_lines = &lines - &cmdheight - 1

  return screen_row >= 0 && screen_row < screen_lines
endfunction

" Center cursor on screen
function! CenterCursor() abort
  normal! zz
  let [row, col] = coc#cursor#screen_pos()
  echo 'Cursor centered at screen position' row ',' col
endfunction

Character-Aware Word Boundary

" Get word under cursor in LSP coordinates
function! GetWordRange() abort
  let [line, char] = coc#cursor#position()
  let line_text = getline('.')

  " Find word boundaries (simplified)
  let start_char = char
  while start_char > 0 && line_text[start_char - 1] =~# '\w'
    let start_char -= 1
  endwhile

  let end_char = char
  let line_len = strchars(line_text)
  while end_char < line_len && line_text[end_char] =~# '\w'
    let end_char += 1
  endwhile

  return {
    \ 'start': {'line': line, 'character': start_char},
    \ 'end': {'line': line, 'character': end_char}
    \ }
endfunction

Usage Examples

Highlight Current Word

function! HighlightCurrentWord() abort
  let [line, char] = coc#cursor#position()
  let range = GetWordRange()

  " Send highlight request to LSP
  call CocAction('highlight', range)
endfunction

autocmd CursorHold * call HighlightCurrentWord()

Column-Aware Navigation

" Navigate to same column on different line
function! GotoLineKeepColumn(target_line) abort
  let [_, char] = coc#cursor#position()
  call coc#cursor#move_to(a:target_line, char)
endfunction

nnoremap <leader>j :call GotoLineKeepColumn(line('.') + 10)<CR>
nnoremap <leader>k :call GotoLineKeepColumn(line('.') - 10)<CR>

Range-Based Refactoring

function! RefactorSelection() abort
  let selection = coc#cursor#get_selection(0)
  if selection is v:null
    echo 'Select text first'
    return
  endif

  let [sl, sc, el, ec] = selection
  let range = {
    \ 'start': {'line': sl, 'character': sc},
    \ 'end': {'line': el, 'character': ec}
    \ }

  call CocAction('refactor', range)
endfunction

vnoremap <silent> <leader>r :<C-u>call RefactorSelection()<CR>

Smart Line Selection

function! SelectCurrentLine() abort
  let [line, _] = coc#cursor#position()
  let line_text = getline('.')
  let line_len = strchars(line_text)

  return [line, 0, line, line_len]
endfunction

function! ExpandSelectionToLine() abort
  let selection = coc#cursor#get_selection(0)
  if selection is v:null
    let selection = SelectCurrentLine()
  else
    let [sl, _, el, _] = selection
    let start_len = strchars(getline(sl + 1))
    let end_len = strchars(getline(el + 1))
    let selection = [sl, 0, el, end_len]
  endif

  call CocAction('addRanges', [selection])
endfunction

Error Handling

Null Selection Handling

Issue: coc#cursor#get_selection() returns v:null when no selection exists.

Solution:

function! SafeSelectionAction() abort
  let selection = coc#cursor#get_selection(0)

  if selection is v:null
    echohl WarningMsg
    echo 'No active selection. Make a visual selection first.'
    echohl None
    return
  endif

  " Process selection
  let [sl, sc, el, ec] = selection
  " ... rest of function
endfunction

Invalid Position Handling

Issue: Moving to invalid positions can cause errors.

Solution:

function! SafeMoveTo(line, char) abort
  " Clamp line to valid range
  let max_line = line('$') - 1
  let safe_line = max([0, min([a:line, max_line])])

  " Clamp character to line length
  let line_text = getline(safe_line + 1)
  let max_char = strchars(line_text)
  let safe_char = max([0, min([a:char, max_char])])

  call coc#cursor#move_to(safe_line, safe_char)
endfunction

Multibyte Character Errors

Issue: Byte vs character confusion with multibyte text.

Solution:

" WRONG - uses byte offsets
let col = col('.')  " Returns byte position
call cursor(line('.'), col + 5)  " Wrong with multibyte chars

" RIGHT - uses character positions
let [line, char] = coc#cursor#position()  " Character position
call coc#cursor#move_to(line, char + 5)  " Correct with multibyte

Screen Position Errors

Issue: Screen coordinates differ from buffer coordinates.

Solution:

function! SafeScreenOperation() abort
  let [screen_row, screen_col] = coc#cursor#screen_pos()

  " Validate screen bounds
  if screen_row < 0 || screen_row >= &lines
    echo 'Cursor off screen vertically'
    return
  endif

  if screen_col < 0 || screen_col >= &columns
    echo 'Cursor off screen horizontally'
    return
  endif

  " Safe to use screen coordinates
endfunction

Troubleshooting

Problem: Position conversions incorrect with multibyte characters

Symptoms: Cursor jumps to wrong position or selections are off by characters.

Solutions:

  1. Always use coc#cursor#position() instead of line('.') and col('.')
  2. Use coc#cursor#move_to() instead of cursor() for LSP coordinates
  3. Use strchars() instead of strlen() for character counting:
    " Wrong
    let len = strlen(text)  " Byte length
    
    " Right
    let len = strchars(text)  " Character count

Problem: Selection returns v:null unexpectedly

Symptoms: coc#cursor#get_selection() returns null even after visual selection.

Solutions:

  1. Ensure you're calling from correct context (after leaving visual mode)
  2. Use marks to check selection state:
    echo getpos("'<")  " Start of last visual
    echo getpos("'>")  " End of last visual
  3. Re-select if needed: normal! gv

Problem: Screen position incorrect in splits

Symptoms: coc#cursor#screen_pos() returns unexpected values in window splits.

Solutions:

  1. Screen coordinates are relative to top-left of screen, not window
  2. Use window-relative position instead:
    let win_row = winline()  " Row in current window
    let win_col = wincol()   " Column in current window

Problem: Movement functions lag with large files

Symptoms: Slow performance when moving cursor in large buffers.

Solutions:

  1. Disable expensive autocmds during batch operations:
    noautocmd call coc#cursor#move_to(line, char)
  2. Use batch operations when moving multiple times:
    let save_eventignore = &eventignore
    set eventignore=all
    " ... perform movements
    let &eventignore = save_eventignore

Problem: Character offset incorrect at buffer boundaries

Symptoms: coc#cursor#char_offset() returns unexpected values at start/end of file.

Solutions:

  1. Check for empty buffer:
    if line('$') == 1 && getline(1) == ''
      echo 'Empty buffer'
      let offset = 0
    else
      let offset = coc#cursor#char_offset()
    endif
  2. Validate offset range:
    let max_offset = strchars(join(getline(1, '$'), "\n"))
    let offset = min([coc#cursor#char_offset(), max_offset])

See Also

  • RPC and LSP Communication - Build LSP parameters with position data
  • Multi-Cursor - Multiple cursor editing with position APIs
  • Code Navigation - Jump to definitions using position
  • Float Windows - Show popups at cursor position
  • Code Actions - Apply actions to selections