-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ALERename (tsserver & LSP), ALEOrganizeImports (tsserver) and auto import support (tsserver) #2709
Add ALERename (tsserver & LSP), ALEOrganizeImports (tsserver) and auto import support (tsserver) #2709
Changes from all commits
5eb8d39
4487725
2e6e77a
83b48a5
075418d
f022358
f3ba394
702618b
0b3de88
bb8048b
b5305f0
a7f776d
e01fead
15f0e0f
1eef4c5
a4327cc
a8d5139
070a749
2badf51
6d3516a
c96eb1b
200a29a
8143f35
c4f8a80
e213d75
f3a9fd8
5d47840
0871cca
ddd62df
9905b6a
ed93090
fd8e2d0
726d9b5
4b746ea
65fe1fb
c8dd766
193765a
74bb384
b3247df
d6321d7
9bb19b7
a7fb8c6
17c8ec1
2e1727a
dc35bcd
5e750e3
8fce6c4
8a192cd
6169226
36dcbee
75ee313
8845d6f
643682a
723809f
499b0be
d776758
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
" Author: Jerko Steiner <jerko.steiner@gmail.com> | ||
" Description: Code action support for LSP / tsserver | ||
|
||
function! ale#code_action#HandleCodeAction(code_action) abort | ||
let l:current_buffer = bufnr('') | ||
let l:changes = a:code_action.changes | ||
|
||
for l:file_code_edit in l:changes | ||
let l:buf = bufnr(l:file_code_edit.fileName) | ||
|
||
if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod') | ||
call ale#util#Execute('echom ''Aborting action, file is unsaved''') | ||
|
||
return | ||
endif | ||
endfor | ||
|
||
for l:file_code_edit in l:changes | ||
call ale#code_action#ApplyChanges( | ||
\ l:file_code_edit.fileName, l:file_code_edit.textChanges) | ||
endfor | ||
endfunction | ||
|
||
function! ale#code_action#ApplyChanges(filename, changes) abort | ||
let l:current_buffer = bufnr('') | ||
" The buffer is used to determine the fileformat, if available. | ||
let l:buffer = bufnr(a:filename) | ||
let l:is_current_buffer = l:buffer > 0 && l:buffer == l:current_buffer | ||
|
||
if l:buffer > 0 | ||
let l:lines = getbufline(l:buffer, 1, '$') | ||
else | ||
let l:lines = readfile(a:filename, 'b') | ||
endif | ||
|
||
if l:is_current_buffer | ||
let l:pos = getpos('.')[1:2] | ||
else | ||
let l:pos = [1, 1] | ||
endif | ||
|
||
" We have to keep track of how many lines we have added, and offset | ||
" changes accordingly. | ||
let l:line_offset = 0 | ||
let l:column_offset = 0 | ||
let l:last_end_line = 0 | ||
|
||
for l:code_edit in a:changes | ||
if l:code_edit.start.line isnot l:last_end_line | ||
let l:column_offset = 0 | ||
endif | ||
|
||
let l:line = l:code_edit.start.line + l:line_offset | ||
let l:column = l:code_edit.start.offset + l:column_offset | ||
let l:end_line = l:code_edit.end.line + l:line_offset | ||
let l:end_column = l:code_edit.end.offset + l:column_offset | ||
let l:text = l:code_edit.newText | ||
|
||
let l:cur_line = l:pos[0] | ||
let l:cur_column = l:pos[1] | ||
|
||
let l:last_end_line = l:end_line | ||
|
||
" Adjust the ends according to previous edits. | ||
if l:end_line > len(l:lines) | ||
let l:end_line_len = 0 | ||
else | ||
let l:end_line_len = len(l:lines[l:end_line - 1]) | ||
endif | ||
|
||
let l:insertions = split(l:text, '\n', 1) | ||
|
||
if l:line is 1 | ||
" Same logic as for column below. Vimscript's slice [:-1] will not | ||
" be an empty list. | ||
let l:start = [] | ||
else | ||
let l:start = l:lines[: l:line - 2] | ||
endif | ||
|
||
if l:column is 1 | ||
" We need to handle column 1 specially, because we can't slice an | ||
" empty string ending on index 0. | ||
let l:middle = [l:insertions[0]] | ||
else | ||
let l:middle = [l:lines[l:line - 1][: l:column - 2] . l:insertions[0]] | ||
endif | ||
|
||
call extend(l:middle, l:insertions[1:]) | ||
let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :] | ||
|
||
let l:lines_before_change = len(l:lines) | ||
let l:lines = l:start + l:middle + l:lines[l:end_line :] | ||
|
||
let l:current_line_offset = len(l:lines) - l:lines_before_change | ||
let l:line_offset += l:current_line_offset | ||
let l:column_offset = len(l:middle[-1]) - l:end_line_len | ||
|
||
let l:pos = s:UpdateCursor(l:pos, | ||
\ [l:line, l:column], | ||
\ [l:end_line, l:end_column], | ||
\ [l:current_line_offset, l:column_offset]) | ||
endfor | ||
|
||
if l:lines[-1] is# '' | ||
call remove(l:lines, -1) | ||
endif | ||
|
||
call ale#util#Writefile(l:buffer, l:lines, a:filename) | ||
|
||
if l:is_current_buffer | ||
call ale#util#Execute(':e!') | ||
call setpos('.', [0, l:pos[0], l:pos[1], 0]) | ||
endif | ||
endfunction | ||
|
||
function! s:UpdateCursor(cursor, start, end, offset) abort | ||
let l:cur_line = a:cursor[0] | ||
let l:cur_column = a:cursor[1] | ||
let l:line = a:start[0] | ||
let l:column = a:start[1] | ||
let l:end_line = a:end[0] | ||
let l:end_column = a:end[1] | ||
let l:line_offset = a:offset[0] | ||
let l:column_offset = a:offset[1] | ||
|
||
if l:end_line < l:cur_line | ||
" both start and end lines are before the cursor. only line offset | ||
" needs to be updated | ||
let l:cur_line += l:line_offset | ||
elseif l:end_line == l:cur_line | ||
" end line is at the same location as cursor, which means | ||
" l:line <= l:cur_line | ||
if l:line < l:cur_line || l:column <= l:cur_column | ||
" updates are happening either before or around the cursor | ||
if l:end_column < l:cur_column | ||
" updates are happening before the cursor, update the | ||
" column offset for cursor | ||
let l:cur_line += l:line_offset | ||
let l:cur_column += l:column_offset | ||
else | ||
" updates are happening around the cursor, move the cursor | ||
" to the end of the changes | ||
let l:cur_line += l:line_offset | ||
let l:cur_column = l:end_column + l:column_offset | ||
endif | ||
" else is not necessary, it means modifications are happening | ||
" after the cursor so no cursor updates need to be done | ||
endif | ||
else | ||
" end line is after the cursor | ||
if l:line < l:cur_line || l:line == l:cur_line && l:column <= l:cur_column | ||
" changes are happening around the cursor, move the cursor | ||
" to the end of the changes | ||
let l:cur_line = l:end_line + l:line_offset | ||
let l:cur_column = l:end_column + l:column_offset | ||
" else is not necesary, it means modifications are happening | ||
" after the cursor so no cursor updates need to be done | ||
endif | ||
endif | ||
|
||
return [l:cur_line, l:cur_column] | ||
endfunction |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ onoremap <silent> <Plug>(ale_show_completion_menu) <Nop> | |
let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100) | ||
let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', []) | ||
let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50) | ||
let g:ale_completion_tsserver_autoimport = get(g:, 'ale_completion_tsserver_autoimport', 0) | ||
|
||
let s:timer_id = -1 | ||
let s:last_done_pos = [] | ||
|
@@ -287,7 +288,10 @@ function! ale#completion#ParseTSServerCompletions(response) abort | |
let l:names = [] | ||
|
||
for l:suggestion in a:response.body | ||
call add(l:names, l:suggestion.name) | ||
call add(l:names, { | ||
\ 'word': l:suggestion.name, | ||
\ 'source': get(l:suggestion, 'source', ''), | ||
\}) | ||
endfor | ||
|
||
return l:names | ||
|
@@ -321,13 +325,22 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort | |
endif | ||
|
||
" See :help complete-items | ||
call add(l:results, { | ||
let l:result = { | ||
\ 'word': l:suggestion.name, | ||
\ 'kind': l:kind, | ||
\ 'icase': 1, | ||
\ 'menu': join(l:displayParts, ''), | ||
\ 'dup': g:ale_completion_tsserver_autoimport, | ||
\ 'info': join(l:documentationParts, ''), | ||
\}) | ||
\} | ||
|
||
if has_key(l:suggestion, 'codeActions') | ||
let l:result.user_data = json_encode({ | ||
\ 'codeActions': l:suggestion.codeActions, | ||
\ }) | ||
endif | ||
|
||
call add(l:results, l:result) | ||
endfor | ||
|
||
let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) | ||
|
@@ -336,12 +349,12 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort | |
let l:names_with_details = map(copy(l:results), 'v:val.word') | ||
let l:missing_names = filter( | ||
\ copy(l:names), | ||
\ 'index(l:names_with_details, v:val) < 0', | ||
\ 'index(l:names_with_details, v:val.word) < 0', | ||
\) | ||
|
||
for l:name in l:missing_names | ||
call add(l:results, { | ||
\ 'word': l:name, | ||
\ 'word': l:name.word, | ||
\ 'kind': 'v', | ||
\ 'icase': 1, | ||
\ 'menu': '', | ||
|
@@ -463,13 +476,22 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort | |
call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) | ||
|
||
if !empty(l:names) | ||
let l:identifiers = [] | ||
|
||
for l:name in l:names | ||
call add(l:identifiers, { | ||
\ 'name': l:name.word, | ||
\ 'source': get(l:name, 'source', ''), | ||
\}) | ||
endfor | ||
|
||
let b:ale_completion_info.request_id = ale#lsp#Send( | ||
\ b:ale_completion_info.conn_id, | ||
\ ale#lsp#tsserver_message#CompletionEntryDetails( | ||
\ l:buffer, | ||
\ b:ale_completion_info.line, | ||
\ b:ale_completion_info.column, | ||
\ l:names, | ||
\ l:identifiers, | ||
\ ), | ||
\) | ||
endif | ||
|
@@ -516,6 +538,7 @@ function! s:OnReady(linter, lsp_details) abort | |
\ b:ale_completion_info.line, | ||
\ b:ale_completion_info.column, | ||
\ b:ale_completion_info.prefix, | ||
\ g:ale_completion_tsserver_autoimport, | ||
\) | ||
else | ||
" Send a message saying the buffer has changed first, otherwise | ||
|
@@ -670,6 +693,26 @@ function! ale#completion#Queue() abort | |
let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler')) | ||
endfunction | ||
|
||
function! ale#completion#HandleUserData(completed_item) abort | ||
let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') | ||
|
||
if l:source isnot# 'ale-automatic' && l:source isnot# 'ale-manual' | ||
return | ||
endif | ||
|
||
let l:user_data_json = get(a:completed_item, 'user_data', '') | ||
|
||
if empty(l:user_data_json) | ||
return | ||
endif | ||
|
||
let l:user_data = json_decode(l:user_data_json) | ||
|
||
for l:code_action in get(l:user_data, 'codeActions', []) | ||
call ale#code_action#HandleCodeAction(l:code_action) | ||
endfor | ||
endfunction | ||
|
||
function! ale#completion#Done() abort | ||
silent! pclose | ||
|
||
|
@@ -678,6 +721,10 @@ function! ale#completion#Done() abort | |
let s:last_done_pos = getpos('.')[1:2] | ||
endfunction | ||
|
||
augroup ALECompletionActions | ||
autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put this logic into the existing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that function! s:Setup(enabled) abort
augroup ALECompletionGroup
autocmd!
if a:enabled
autocmd TextChangedI * call ale#completion#Queue()
autocmd CompleteDone * call ale#completion#Done(v:completed_item)
elseif g:ale_completion_tsserver_autoimport
autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
endif
augroup END
if !a:enabled && !g:ale_completion_tsserver_autoimport
augroup! ALECompletionGroup
endif
endfunction What do you think? |
||
augroup END | ||
|
||
function! s:Setup(enabled) abort | ||
augroup ALECompletionGroup | ||
autocmd! | ||
jeremija marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try removing
json_encode
here andjson_decode
below. I think you can storeDictionary
values too.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so too, but I get
E731: using Dictionary as a String
when I remove thejson_encode
/json_decode
functions :(