Skip to content

Commit 3121f0a

Browse files
machakannprabirshrestha
authored andcommitted
Multibyte-character support (Fix #425) (#490)
* Multibyte character support for lsp#get_position() * Multibyte character support for "textDocument/documentHighlight" * Fix `lsp#utils#byteindex()` would fails if a file is not yet loaded * Add tests * Rename lsp#utils#byteindex() and lsp#utils#charindex() lsp#utils#byteindex() -> lsp#utils#to_col() lsp#utils#charindex() -> lsp#utils#to_char() * Multibyte character support for textEdit * Remove unused line * Multibyte character support for "textDocument/rangeFormatting" * Multibyte character support for "textDocument/publishDiagnostics"
1 parent b1ed3dd commit 3121f0a

File tree

9 files changed

+97
-29
lines changed

9 files changed

+97
-29
lines changed

autoload/lsp.vim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,9 @@ function! lsp#get_text_document_identifier(...) abort
747747
endfunction
748748

749749
function! lsp#get_position(...) abort
750-
return { 'line': line('.') - 1, 'character': col('.') -1 }
750+
let l:line = line('.')
751+
let l:char = lsp#utils#to_char('%', l:line, col('.'))
752+
return { 'line': l:line - 1, 'character': l:char }
751753
endfunction
752754

753755
function! s:get_text_document_identifier(buf) abort

autoload/lsp/omni.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ function! s:apply_text_edits() abort
318318
" expand textEdit range, for omni complet inserted text.
319319
let l:text_edit = get(l:user_data, s:user_data_key, {})
320320
if !empty(l:text_edit)
321-
let l:expanded_text_edit = s:expand_range(l:text_edit, len(v:completed_item['word']))
321+
let l:expanded_text_edit = s:expand_range(l:text_edit, strchars(v:completed_item['word']))
322322
call add(l:all_text_edits, l:expanded_text_edit)
323323
endif
324324

autoload/lsp/ui/vim.vim

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,16 @@ function! s:document_format_range(sync) abort
252252
let l:server = l:servers[0]
253253

254254
let [l:start_lnum, l:start_col, l:end_lnum, l:end_col] = s:get_visual_selection_pos()
255+
let l:start_char = lsp#utils#to_char('%', l:start_lnum, l:start_col)
256+
let l:end_char = lsp#utils#to_char('%', l:end_lnum, l:end_col)
255257
redraw | echo 'Formatting document range ...'
256258
call lsp#send_request(l:server, {
257259
\ 'method': 'textDocument/rangeFormatting',
258260
\ 'params': {
259261
\ 'textDocument': lsp#get_text_document_identifier(),
260262
\ 'range': {
261-
\ 'start': { 'line': l:start_lnum - 1, 'character': l:start_col - 1 },
262-
\ 'end': { 'line': l:end_lnum - 1, 'character': l:end_col - 1 },
263+
\ 'start': { 'line': l:start_lnum - 1, 'character': l:start_char },
264+
\ 'end': { 'line': l:end_lnum - 1, 'character': l:end_char },
263265
\ },
264266
\ 'options': {
265267
\ 'tabSize': getbufvar(bufnr('%'), '&shiftwidth'),
@@ -347,9 +349,11 @@ function! s:get_visual_selection_range() abort
347349
if l:column_end - 1 > len(getline(l:line_end))
348350
let l:column_end = len(getline(l:line_end)) + 1
349351
endif
352+
let l:char_start = lsp#utils#to_char('%', l:line_start, l:column_start)
353+
let l:char_end = lsp#utils#to_char('%', l:line_end, l:column_end)
350354
return {
351-
\ 'start': { 'line': l:line_start - 1, 'character': l:column_start - 1 },
352-
\ 'end': { 'line': l:line_end - 1, 'character': l:column_end - 1 },
355+
\ 'start': { 'line': l:line_start - 1, 'character': l:char_start },
356+
\ 'end': { 'line': l:line_end - 1, 'character': l:char_end },
353357
\}
354358
endfunction
355359

@@ -506,14 +510,24 @@ function! s:handle_rename_prepare(server, last_req_id, type, data) abort
506510

507511
let l:range = a:data['response']['result']
508512
let l:lines = getline(1, '$')
509-
if l:range['start']['line'] ==# l:range['end']['line']
510-
let l:name = l:lines[l:range['start']['line']][l:range['start']['character'] : l:range['end']['character']-1]
513+
let l:start_line = l:range['start']['line'] + 1
514+
let l:start_char = l:range['start']['character']
515+
let l:start_col = lsp#utils#to_col('%', l:start_line, l:start_char)
516+
let l:end_line = l:range['end']['line'] + 1
517+
let l:end_char = l:range['end']['character']
518+
let l:end_col = lsp#utils#to_col('%', l:end_line, l:end_char)
519+
if l:start_line ==# l:end_line
520+
let l:name = l:lines[l:start_line - 1][l:start_col - 1 : l:end_col - 2]
511521
else
512-
let l:name = l:lines[l:range['start']['line']][l:range['start']['character'] :]
513-
for l:i in range(l:range['start']['line']+1, l:range['end']['line']-1)
522+
let l:name = l:lines[l:start_line - 1][l:start_col - 1 :]
523+
for l:i in range(l:start_line, l:end_line - 2)
514524
let l:name .= "\n" . l:lines[l:i]
515525
endfor
516-
let l:name .= l:lines[l:range['end']['line']][: l:range['end']['character']-1]
526+
if l:end_col - 2 < 0
527+
let l:name .= "\n"
528+
else
529+
let l:name .= l:lines[l:end_line - 1][: l:end_col - 2]
530+
endif
517531
endif
518532

519533
call timer_start(1, {x->s:rename(a:server, input('new name: ', l:name), l:range['start'])})

autoload/lsp/ui/vim/highlights.vim

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,17 @@ function! s:place_highlights(server_name, path, diagnostics) abort
9393
let l:ns = s:get_highlight_group(a:server_name)
9494
let l:bufnr = bufnr(a:path)
9595

96-
if !empty(a:diagnostics) && bufnr(a:path) >= 0
96+
if !empty(a:diagnostics) && l:bufnr >= 0
9797
for l:item in a:diagnostics
98-
let l:line = l:item['range']['start']['line']
99-
let l:start = l:item['range']['start']['character']
100-
let l:end = l:item['range']['end']['character']
98+
let l:line = l:item['range']['start']['line'] + 1
99+
let l:start_char = l:item['range']['start']['character']
100+
let l:start_col = lsp#utils#to_col(l:bufnr, l:line, l:start_char)
101+
let l:end_char = l:item['range']['end']['character']
102+
let l:end_col = lsp#utils#to_col(l:bufnr, l:line, l:end_char)
101103

102104
let l:name = get(s:severity_sign_names_mapping, l:item['severity'], 'LspError')
103105
let l:hl_name = l:name . 'Highlight'
104-
call nvim_buf_add_highlight(l:bufnr, l:ns, l:hl_name, l:line, l:start, l:end)
106+
call nvim_buf_add_highlight(l:bufnr, l:ns, l:hl_name, l:line, l:start_col, l:end_col)
105107
endfor
106108
endif
107109
endfunction

autoload/lsp/ui/vim/references.vim

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,41 @@ endif
1111
" If the range spans over multiple lines, break it down to multiple
1212
" positions, one for each line.
1313
" Return a list of positions.
14-
function! s:range_to_position(range) abort
14+
function! s:range_to_position(bufnr, range) abort
1515
let l:start = a:range['start']
1616
let l:end = a:range['end']
1717
let l:position = []
1818

19-
if l:end['line'] == l:start['line']
19+
let l:start_line = l:start['line'] + 1
20+
let l:start_char = l:start['character']
21+
let l:start_col = lsp#utils#to_col(a:bufnr, l:start_line, l:start_char)
22+
let l:end_line = l:end['line'] + 1
23+
let l:end_char = l:end['character']
24+
let l:end_col = lsp#utils#to_col(a:bufnr, l:end_line, l:end_char)
25+
if l:end_line == l:start_line
2026
let l:position = [[
21-
\ l:start['line'] + 1,
22-
\ l:start['character'] + 1,
23-
\ l:end['character'] - l:start['character']
27+
\ l:start_line,
28+
\ l:start_col,
29+
\ l:end_col - l:start_col
2430
\ ]]
2531
else
2632
" First line
2733
let l:position = [[
28-
\ l:start['line'] + 1,
29-
\ l:start['character'] + 1,
34+
\ l:start_line,
35+
\ l:start_col,
3036
\ 999
3137
\ ]]
3238

3339
" Last line
3440
call add(l:position, [
35-
\ l:end['line'] + 1,
41+
\ l:end_line,
3642
\ 1,
37-
\ l:end['character']
43+
\ l:end_col
3844
\ ])
3945

4046
" Lines in the middle
4147
let l:middle_lines = map(
42-
\ range(l:start['line'] + 2, end['line']),
48+
\ range(l:start_line + 1, l:end_line - 1),
4349
\ {_, l -> [l, 0, 999]}
4450
\ )
4551

@@ -105,7 +111,7 @@ function! s:handle_references(ctx, data) abort
105111
" Convert references to vim positions
106112
let l:position_list = []
107113
for l:reference in l:reference_list
108-
call extend(l:position_list, s:range_to_position(l:reference['range']))
114+
call extend(l:position_list, s:range_to_position(a:ctx['bufnr'], l:reference['range']))
109115
endfor
110116
call sort(l:position_list, function('s:compare_positions'))
111117

autoload/lsp/ui/vim/signs.vim

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ function! s:place_signs(server_name, path, diagnostics) abort
124124
if !empty(a:diagnostics) && bufnr(a:path) >= 0
125125
for l:item in a:diagnostics
126126
let l:line = l:item['range']['start']['line'] + 1
127-
let l:character = l:item['range']['start']['character'] + 1
128127

129128
if has_key(l:item, 'severity') && !empty(l:item['severity'])
130129
let l:sign_name = get(s:severity_sign_names_mapping, l:item['severity'], 'LspError')

autoload/lsp/utils.vim

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,20 @@ function! lsp#utils#to_col(expr, lnum, char) abort
202202
let l:linestr = l:lines[-1]
203203
return strlen(strcharpart(l:linestr, 0, a:char)) + 1
204204
endfunction
205+
206+
" Convert a byte-index (1-based) to a character-index (0-based)
207+
" This function requires a buffer specifier (expr, see :help bufname()),
208+
" a line number (lnum, 1-based), and a byte-index (char, 1-based).
209+
function! lsp#utils#to_char(expr, lnum, col) abort
210+
let l:lines = getbufline(a:expr, a:lnum)
211+
if l:lines == []
212+
if type(a:expr) != v:t_string || !filereadable(a:expr)
213+
" invalid a:expr
214+
return a:col - 1
215+
endif
216+
" a:expr is a file that is not yet loaded as a buffer
217+
let l:lines = readfile(a:expr, '', a:lnum)
218+
endif
219+
let l:linestr = l:lines[-1]
220+
return strchars(strpart(l:linestr, 0, a:col - 1))
221+
endfunction

autoload/lsp/utils/text_edit.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ function! s:generate_sub_cmd_insert(text_edit) abort
167167
let l:sub_cmd = s:preprocess_cmd(a:text_edit['range'])
168168
let l:sub_cmd .= s:generate_move_start_cmd(l:start_line, l:start_character)
169169

170-
if l:start_character >= len(getline(l:start_line))
170+
if l:start_character >= strchars(getline(l:start_line))
171171
let l:sub_cmd .= "\"=l:merged_text_edit['merged']['newText']\<CR>p"
172172
else
173173
let l:sub_cmd .= "\"=l:merged_text_edit['merged']['newText']\<CR>P"

test/lsp/utils.vimspec

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,32 @@ Describe lsp#utils
109109
Assert lsp#utils#to_col('./test/testfiles/multibyte.txt', 3, 0) == 1
110110
End
111111
End
112+
113+
Describe lsp#utils#to_char
114+
It should return the character-index from the given byte-index on a buffer
115+
call setline(1, ['a β c', 'δ', ''])
116+
Assert lsp#utils#to_char('%', 1, 1) == 0
117+
Assert lsp#utils#to_char('%', 1, 2) == 1
118+
Assert lsp#utils#to_char('%', 1, 3) == 2
119+
Assert lsp#utils#to_char('%', 1, 5) == 3
120+
Assert lsp#utils#to_char('%', 1, 6) == 4
121+
Assert lsp#utils#to_char('%', 1, 7) == 5
122+
Assert lsp#utils#to_char('%', 2, 1) == 0
123+
Assert lsp#utils#to_char('%', 2, 3) == 1
124+
Assert lsp#utils#to_char('%', 3, 1) == 0
125+
%delete
126+
End
127+
128+
It should return the character-index from the given byte-index in an unloaded file
129+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 1) == 0
130+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 2) == 1
131+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 3) == 2
132+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 5) == 3
133+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 6) == 4
134+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 1, 7) == 5
135+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 2, 1) == 0
136+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 2, 3) == 1
137+
Assert lsp#utils#to_char('./test/testfiles/multibyte.txt', 3, 1) == 0
138+
End
139+
End
112140
End

0 commit comments

Comments
 (0)