Dispatch textDocument/didChange after rename (2) (#4049)

* Dispatch textDocument/didChange after rename

Previously whenever we renamed a symbol that was referenced from other
files we'd just edit those files in the background, and the LSP wouldn't
know about these changes. If we tried to rename the same symbol again,
the renaming would fail. In some scenarios, the operation would just be
wrong. Here is an attempt to fix this issue.

I also noticed another bug when using Go with `gopls` LSP and the `gofmt`
fixer. Whenever the file was saved, the `gofmt` would run and reformat
the file. But it seems there was some kind of a race condition so I
disabled saving for now, and all of the modified files will be unsaved,
so the user should call `:wa` to save them. I personally like this even
better because I can inspect exactly what changes happened, and I
instantly see them in the other opened buffers, which was previously not
the case.

Fixes #3343, #3642, #3781.

* Address PR comments

* Remove mode tests in corner case tests

* Address PR comments

* Save after ALERename and ALEOrganizeImports

Also provide options to disable automatic saving, as well as instructions to
enable `set hidden` before doing that.

* Fix broken test

* Save only when !&hidden

* Update doc

* Update doc

* Add silent
This commit is contained in:
Jerko Steiner 2022-02-08 12:07:39 +01:00 committed by GitHub
parent 8b1ea33cc0
commit 4a4516e3bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 326 additions and 178 deletions

View File

@ -16,13 +16,12 @@ endfunction
function! ale#code_action#HandleCodeAction(code_action, options) abort function! ale#code_action#HandleCodeAction(code_action, options) abort
let l:current_buffer = bufnr('') let l:current_buffer = bufnr('')
let l:changes = a:code_action.changes let l:changes = a:code_action.changes
let l:should_save = get(a:options, 'should_save')
for l:file_code_edit in l:changes for l:file_code_edit in l:changes
call ale#code_action#ApplyChanges( call ale#code_action#ApplyChanges(
\ l:file_code_edit.fileName, \ l:file_code_edit.fileName,
\ l:file_code_edit.textChanges, \ l:file_code_edit.textChanges,
\ l:should_save, \ a:options,
\) \)
endfor endfor
endfunction endfunction
@ -63,29 +62,29 @@ function! s:ChangeCmp(left, right) abort
return 0 return 0
endfunction endfunction
function! ale#code_action#ApplyChanges(filename, changes, should_save) abort function! ale#code_action#ApplyChanges(filename, changes, options) abort
let l:current_buffer = bufnr('') let l:should_save = get(a:options, 'should_save')
let l:conn_id = get(a:options, 'conn_id')
let l:orig_buffer = bufnr('')
" The buffer is used to determine the fileformat, if available. " The buffer is used to determine the fileformat, if available.
let l:buffer = bufnr(a:filename) let l:buffer = bufnr(a:filename)
let l:is_current_buffer = l:buffer > 0 && l:buffer == l:current_buffer
if l:buffer > 0 if l:buffer != l:orig_buffer
let l:lines = getbufline(l:buffer, 1, '$') call ale#util#Execute('silent edit ' . a:filename)
let l:buffer = bufnr('')
" Add empty line if there's trailing newline, like readfile() does.
if getbufvar(l:buffer, '&eol')
let l:lines += ['']
endif
else
let l:lines = readfile(a:filename, 'b')
endif endif
if l:is_current_buffer let l:lines = getbufline(l:buffer, 1, '$')
let l:pos = getpos('.')[1:2]
else " Add empty line if there's trailing newline, like readfile() does.
let l:pos = [1, 1] if getbufvar(l:buffer, '&eol')
let l:lines += ['']
endif endif
let l:pos = getpos('.')[1:2]
" Changes have to be sorted so we apply them from bottom-to-top " Changes have to be sorted so we apply them from bottom-to-top
for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp'))) for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp')))
let l:line = l:code_edit.start.line let l:line = l:code_edit.start.line
@ -155,46 +154,25 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
endif endif
endfor endfor
if l:buffer > 0 " Make sure to add a trailing newline if and only if it should be added.
" Make sure ale#util#{Writefile,SetBufferContents} add trailing if l:lines[-1] is# '' && getbufvar(l:buffer, '&eol')
" newline if and only if it should be added.
if l:lines[-1] is# '' && getbufvar(l:buffer, '&eol')
call remove(l:lines, -1)
else
call setbufvar(l:buffer, '&eol', 0)
endif
elseif exists('+fixeol') && &fixeol && l:lines[-1] is# ''
" Not in buffer, ale#util#Writefile can't check &eol and always adds
" newline if &fixeol: remove to prevent double trailing newline.
call remove(l:lines, -1) call remove(l:lines, -1)
endif
if a:should_save || l:buffer < 0
call ale#util#Writefile(l:buffer, l:lines, a:filename)
else else
call ale#util#SetBufferContents(l:buffer, l:lines) call setbufvar(l:buffer, '&eol', 0)
endif endif
if l:is_current_buffer call ale#util#SetBufferContents(l:buffer, l:lines)
if a:should_save
call ale#util#Execute(':e!')
endif
call setpos('.', [0, l:pos[0], l:pos[1], 0]) call ale#lsp#NotifyForChanges(l:conn_id, l:buffer)
if l:should_save
call ale#util#Execute('silent w!')
endif endif
if a:should_save && l:buffer > 0 && !l:is_current_buffer call setpos('.', [0, l:pos[0], l:pos[1], 0])
" Set up a one-time use event that will delete itself to reload the
" buffer next time it's entered to view the changes made to it.
execute 'augroup ALECodeActionReloadGroup' . l:buffer
autocmd!
execute printf( if l:orig_buffer != l:buffer && bufexists(l:orig_buffer)
\ 'autocmd BufEnter <buffer=%d>' call ale#util#Execute('silent buf ' . string(l:orig_buffer))
\ . ' call ale#code_action#ReloadBuffer()',
\ l:buffer
\)
augroup END
endif endif
endfunction endfunction

View File

@ -1,6 +1,6 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com> " Author: Jerko Steiner <jerko.steiner@gmail.com>
" Description: Organize imports support for tsserver " Description: Organize imports support for tsserver
"
function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
if get(a:response, 'command', '') isnot# 'organizeImports' if get(a:response, 'command', '') isnot# 'organizeImports'
return return
@ -17,7 +17,10 @@ function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
\ 'description': 'Organize Imports', \ 'description': 'Organize Imports',
\ 'changes': l:file_code_edits, \ 'changes': l:file_code_edits,
\ }, \ },
\ {} \ {
\ 'conn_id': a:conn_id,
\ 'should_save': !&hidden,
\ },
\) \)
endfunction endfunction

View File

@ -84,7 +84,8 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
\ 'changes': l:changes, \ 'changes': l:changes,
\ }, \ },
\ { \ {
\ 'should_save': 1, \ 'conn_id': a:conn_id,
\ 'should_save': !&hidden,
\ }, \ },
\) \)
endfunction endfunction
@ -116,7 +117,8 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
\ 'changes': l:changes, \ 'changes': l:changes,
\ }, \ },
\ { \ {
\ 'should_save': 1, \ 'conn_id': a:conn_id,
\ 'should_save': !&hidden,
\ }, \ },
\) \)
endif endif

View File

@ -1820,8 +1820,8 @@ g:ale_open_list *g:ale_open_list*
autocmd! autocmd!
autocmd QuitPre * if empty(&buftype) | lclose | endif autocmd QuitPre * if empty(&buftype) | lclose | endif
augroup END augroup END
<
<
g:ale_pattern_options *g:ale_pattern_options* g:ale_pattern_options *g:ale_pattern_options*
Type: |Dictionary| Type: |Dictionary|
@ -3435,6 +3435,10 @@ ALERename *ALERename*
The symbol where the cursor is resting will be the symbol renamed, and a The symbol where the cursor is resting will be the symbol renamed, and a
prompt will open to request a new name. prompt will open to request a new name.
The rename operation will save all modified buffers when `set nohidden` is
set, because that disables leaving unsaved buffers in the background. See
`:help hidden` for more details.
ALEFileRename *ALEFileRename* ALEFileRename *ALEFileRename*
Rename a file and fix imports using `tsserver`. Rename a file and fix imports using `tsserver`.

View File

@ -1,4 +1,15 @@
Before: Before:
let g:notified_changes = []
runtime autoload/ale/lsp.vim
function! ale#lsp#NotifyForChanges(conn_id, buffer) abort
call add(g:notified_changes, {
\ 'conn_id': a:conn_id,
\ 'buffer': a:buffer
\})
endfunction
Save g:ale_enabled Save g:ale_enabled
let g:ale_enabled = 0 let g:ale_enabled = 0
@ -36,10 +47,10 @@ Before:
After: After:
" Close the extra buffers if we opened it. " Close the extra buffers if we opened it.
if bufnr(g:file1) != -1 if bufnr(g:file1) != -1 && buflisted(bufnr(g:file1))
execute ':bp! | :bd! ' . bufnr(g:file1) execute ':bp! | :bd! ' . bufnr(g:file1)
endif endif
if bufnr(g:file2) != -1 if bufnr(g:file2) != -1 && buflisted(bufnr(g:file2))
execute ':bp! | :bd! ' . bufnr(g:file2) execute ':bp! | :bd! ' . bufnr(g:file2)
endif endif
@ -50,12 +61,16 @@ After:
call delete(g:file2) call delete(g:file2)
endif endif
unlet! g:notified_changes
" unlet! g:expected_notified_changes
unlet! g:file1 unlet! g:file1
unlet! g:file2 unlet! g:file2
unlet! g:test unlet! g:test
unlet! g:changes unlet! g:changes
delfunction WriteFileAndEdit delfunction WriteFileAndEdit
runtime autoload/ale/lsp.vim
Restore Restore
@ -118,7 +133,7 @@ Execute(It should modify and save multiple files):
\ }] \ }]
\ }], \ }],
\ }, \ },
\ {'should_save': 1}, \ {'should_save': 1, 'conn_id': 'test_conn'},
\) \)
AssertEqual [ AssertEqual [
@ -140,6 +155,13 @@ Execute(It should modify and save multiple files):
\ '', \ '',
\], readfile(g:file2, 'b') \], readfile(g:file2, 'b')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file1),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file2),
\}], g:notified_changes
Execute(Beginning of file can be modified): Execute(Beginning of file can be modified):
let g:test.text = [ let g:test.text = [
@ -166,7 +188,7 @@ Execute(Beginning of file can be modified):
\ }], \ }],
\ }] \ }]
\ }, \ },
\ {'should_save': 1}, \ {'should_save': 1, 'conn_id': 'test_conn'},
\) \)
AssertEqual [ AssertEqual [
@ -174,6 +196,11 @@ Execute(Beginning of file can be modified):
\ 'type B: number', \ 'type B: number',
\] + g:test.text + [''], readfile(g:file1, 'b') \] + g:test.text + [''], readfile(g:file1, 'b')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file1),
\}], g:notified_changes
Execute(End of file can be modified): Execute(End of file can be modified):
let g:test.text = [ let g:test.text = [
@ -200,7 +227,7 @@ Execute(End of file can be modified):
\ }], \ }],
\ }] \ }]
\ }, \ },
\ {'should_save': 1}, \ {'should_save': 1, 'conn_id': 'test_conn'},
\) \)
AssertEqual g:test.text + [ AssertEqual g:test.text + [
@ -209,6 +236,11 @@ Execute(End of file can be modified):
\ '', \ '',
\], readfile(g:file1, 'b') \], readfile(g:file1, 'b')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file1),
\}], g:notified_changes
Execute(Current buffer contents will be reloaded): Execute(Current buffer contents will be reloaded):
let g:test.text = [ let g:test.text = [
@ -238,7 +270,7 @@ Execute(Current buffer contents will be reloaded):
\ }], \ }],
\ }] \ }]
\ }, \ },
\ {'should_save': 1}, \ {'should_save': 1, 'conn_id': 'test_conn'},
\) \)
AssertEqual [ AssertEqual [
@ -251,6 +283,55 @@ Execute(Current buffer contents will be reloaded):
\ 'type B: number', \ 'type B: number',
\] + g:test.text, getbufline(g:test.buffer, 1, '$') \] + g:test.text, getbufline(g:test.buffer, 1, '$')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file1),
\}], g:notified_changes
Execute(Unlisted buffer contents will be modified correctly):
let g:test.text = [
\ 'class Name {',
\ ' value: string',
\ '}',
\]
call writefile(g:test.text, g:file1, 'S')
execute 'edit ' . g:file1
let g:test.buffer = bufnr(g:file1)
execute 'bd'
AssertEqual bufnr(g:file1), g:test.buffer
call ale#code_action#HandleCodeAction(
\ {
\ 'changes': [{
\ 'fileName': g:file1,
\ 'textChanges': [{
\ 'start': {
\ 'line': 1,
\ 'offset': 1,
\ },
\ 'end': {
\ 'line': 1,
\ 'offset': 1,
\ },
\ 'newText': "type A: string\ntype B: number\n",
\ }],
\ }]
\ },
\ {'should_save': 1, 'conn_id': 'test_conn'},
\)
AssertEqual [
\ 'type A: string',
\ 'type B: number',
\] + g:test.text + [''], readfile(g:file1, 'b')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(g:file1),
\}], g:notified_changes
# Tests for cursor repositioning. In comments `=` designates change range, and # Tests for cursor repositioning. In comments `=` designates change range, and
# `C` cursor position # `C` cursor position
@ -261,13 +342,32 @@ Execute(Cursor will not move when it is before text change):
let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
call setpos('.', [0, 1, 1, 0]) call setpos('.', [0, 1, 1, 0])
call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1}) call ale#code_action#HandleCodeAction(g:test.changes, {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\})
AssertEqual [1, 1], getpos('.')[1:2] AssertEqual [1, 1], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
call setpos('.', [0, 2, 2, 0]) call setpos('.', [0, 2, 2, 0])
call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1}) call ale#code_action#HandleCodeAction(g:test.changes, {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\})
AssertEqual [2, 2], getpos('.')[1:2] AssertEqual [2, 2], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ====C==== # ====C====
Execute(Cursor column will move to the change end when cursor between start/end): Execute(Cursor column will move to the change end when cursor between start/end):
let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
@ -276,11 +376,34 @@ Execute(Cursor column will move to the change end when cursor between start/end)
call WriteFileAndEdit() call WriteFileAndEdit()
call setpos('.', [0, 2, r, 0]) call setpos('.', [0, 2, r, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1}) call ale#code_action#HandleCodeAction(g:test.changes, {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\})
AssertEqual ' value2: string', getline('.') AssertEqual ' value2: string', getline('.')
AssertEqual [2, 9], getpos('.')[1:2] AssertEqual [2, 9], getpos('.')[1:2]
endfor endfor
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}, {
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ====C # ====C
Execute(Cursor column will move back when new text is shorter): Execute(Cursor column will move back when new text is shorter):
@ -289,11 +412,18 @@ Execute(Cursor column will move back when new text is shorter):
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(2, 3, 2, 8, 'val'), \ g:test.create_change(2, 3, 2, 8, 'val'),
\ {'should_save': 1}, \ {
\) \ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual ' val: string', getline('.') AssertEqual ' val: string', getline('.')
AssertEqual [2, 6], getpos('.')[1:2] AssertEqual [2, 6], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ==== C # ==== C
Execute(Cursor column will move forward when new text is longer): Execute(Cursor column will move forward when new text is longer):
@ -302,10 +432,19 @@ Execute(Cursor column will move forward when new text is longer):
call setpos('.', [0, 2, 8, 0]) call setpos('.', [0, 2, 8, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(2, 3, 2, 8, 'longValue'), {'should_save': 1}) \ g:test.create_change(2, 3, 2, 8, 'longValue'),
\ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual ' longValue: string', getline('.') AssertEqual ' longValue: string', getline('.')
AssertEqual [2, 12], getpos('.')[1:2] AssertEqual [2, 12], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ========= # =========
# = # =
# C # C
@ -314,10 +453,19 @@ Execute(Cursor line will move when updates are happening on lines above):
call setpos('.', [0, 3, 1, 0]) call setpos('.', [0, 3, 1, 0])
AssertEqual '}', getline('.') AssertEqual '}', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(1, 1, 2, 1, "test\ntest\n"), {'should_save': 1}) \ g:test.create_change(1, 1, 2, 1, "test\ntest\n"),
\ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual '}', getline('.') AssertEqual '}', getline('.')
AssertEqual [4, 1], getpos('.')[1:2] AssertEqual [4, 1], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ========= # =========
# =C # =C
@ -326,10 +474,19 @@ Execute(Cursor line and column will move when change on lines above and just bef
call setpos('.', [0, 2, 2, 0]) call setpos('.', [0, 2, 2, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"), {'should_save': 1}) \ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"),
\ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual '123 value: string', getline('.') AssertEqual '123 value: string', getline('.')
AssertEqual [3, 5], getpos('.')[1:2] AssertEqual [3, 5], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# ========= # =========
# ======C== # ======C==
# = # =
@ -338,10 +495,19 @@ Execute(Cursor line and column will move at the end of changes):
call setpos('.', [0, 2, 10, 0]) call setpos('.', [0, 2, 10, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(1, 1, 3, 1, "test\n"), {'should_save': 1}) \ g:test.create_change(1, 1, 3, 1, "test\n"),
\ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual '}', getline('.') AssertEqual '}', getline('.')
AssertEqual [2, 1], getpos('.')[1:2] AssertEqual [2, 1], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
# C == # C ==
# === # ===
Execute(Cursor will not move when changes happening on lines >= cursor, but after cursor): Execute(Cursor will not move when changes happening on lines >= cursor, but after cursor):
@ -349,23 +515,42 @@ Execute(Cursor will not move when changes happening on lines >= cursor, but afte
call setpos('.', [0, 2, 3, 0]) call setpos('.', [0, 2, 3, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(2, 10, 3, 1, "number\n"), {'should_save': 1}) \ g:test.create_change(2, 10, 3, 1, "number\n"),
\ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual ' value: number', getline('.') AssertEqual ' value: number', getline('.')
AssertEqual [2, 3], getpos('.')[1:2] AssertEqual [2, 3], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
Execute(Cursor will not move when change covers entire file): Execute(Cursor will not move when change covers entire file):
call WriteFileAndEdit() call WriteFileAndEdit()
call setpos('.', [0, 2, 3, 0]) call setpos('.', [0, 2, 3, 0])
call ale#code_action#HandleCodeAction( call ale#code_action#HandleCodeAction(
\ g:test.create_change(1, 1, len(g:test.text) + 1, 1, \ g:test.create_change(1, 1, len(g:test.text) + 1, 1,
\ join(g:test.text + ['x'], "\n")), \ join(g:test.text + ['x'], "\n")),
\ {'should_save': 1}) \ {
\ 'should_save': 1,
\ 'conn_id': 'test_conn',
\ })
AssertEqual [2, 3], getpos('.')[1:2] AssertEqual [2, 3], getpos('.')[1:2]
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
Execute(It should just modify file when should_save is set to v:false): Execute(It should just modify file when should_save is set to v:false):
call WriteFileAndEdit() call WriteFileAndEdit()
let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n") let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n")
call ale#code_action#HandleCodeAction(g:test.change, {}) call ale#code_action#HandleCodeAction(g:test.change, {
\ 'conn_id': 'test_conn',
\})
AssertEqual 1, getbufvar(bufnr(''), '&modified') AssertEqual 1, getbufvar(bufnr(''), '&modified')
AssertEqual [ AssertEqual [
\ 'import { writeFile } from ''fs'';', \ 'import { writeFile } from ''fs'';',
@ -374,6 +559,11 @@ Execute(It should just modify file when should_save is set to v:false):
\ '}', \ '}',
\], getline(1, '$') \], getline(1, '$')
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
Given typescript(An example TypeScript file): Given typescript(An example TypeScript file):
type Foo = {} type Foo = {}
@ -393,7 +583,14 @@ Execute():
\ {'end': {'offset': 14, 'line': 9}, 'newText': 'foo', 'start': {'offset': 3, 'line': 9}}, \ {'end': {'offset': 14, 'line': 9}, 'newText': 'foo', 'start': {'offset': 3, 'line': 9}},
\] \]
call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0) call ale#code_action#ApplyChanges(expand('%:p'), g:changes, {
\ 'conn_id': 'test_conn',
\})
AssertEqual [{
\ 'conn_id': 'test_conn',
\ 'buffer': bufnr(''),
\}], g:notified_changes
Expect(The changes should be applied correctly): Expect(The changes should be applied correctly):
type Foo = {} type Foo = {}

View File

@ -34,20 +34,16 @@ Before:
Save &fileformats Save &fileformats
set fileformats=unix set fileformats=unix
" two files, one accessed through a buffer, the other using write/readfile only let g:file = tempname()
let g:files = [tempname(), tempname()]
function! TestChanges(contents, changes, mode) abort function! TestChanges(contents, changes) abort
let l:file = g:files[a:mode is 'file' ? 0 : 1] call writefile(split(a:contents, '\n', 1), g:file, 'bS')
call writefile(split(a:contents, '\n', 1), l:file, 'bS')
if a:mode isnot 'file' call ale#code_action#ApplyChanges(g:file, a:changes, {
execute 'edit ' . l:file \ 'should_save': 1,
endif \})
call ale#code_action#ApplyChanges(l:file, a:changes, a:mode isnot 'buffer')
if a:mode is 'buffer' return join(readfile(g:file, 'b'), "\n")
execute 'write ' . l:file
endif
return join(readfile(l:file, 'b'), "\n")
endfunction! endfunction!
function! MkPos(line, offset) abort function! MkPos(line, offset) abort
@ -63,17 +59,15 @@ Before:
endfunction! endfunction!
After: After:
for g:file in g:files if bufnr(g:file) != -1
if bufnr(g:file) != -1 execute ':bp! | :bd! ' . bufnr(g:file)
execute ':bp! | :bd! ' . bufnr(g:file) endif
endif
if filereadable(g:file)
call delete(g:file)
endif
endfor
unlet! g:files g:file
unlet! g:mode if filereadable(g:file)
call delete(g:file)
endif
unlet! g:file
delfunction TestChanges delfunction TestChanges
delfunction MkPos delfunction MkPos
@ -83,97 +77,67 @@ After:
Restore Restore
Execute(Preserve (no)eol at eof): Execute(Preserve (no)eol at eof):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "noeol", TestChanges("noeol", [])
Log g:mode AssertEqual "eol\n", TestChanges("eol\n", [])
AssertEqual "noeol", TestChanges("noeol", [], g:mode) AssertEqual "eols\n\n", TestChanges("eols\n\n", [])
AssertEqual "eol\n", TestChanges("eol\n", [], g:mode)
AssertEqual "eols\n\n", TestChanges("eols\n\n", [], g:mode)
endfor
" there doesn't seem to be a way to tell if a buffer is empty or contains one
" empty line :-(
AssertEqual "", TestChanges("", [], 'file')
Execute(Respect fixeol): Execute(Respect fixeol):
set fixeol set fixeol
for g:mode in ['save', 'file', 'buffer']
Log g:mode silent echo "vscode skip" | AssertEqual "noeol\n", TestChanges("noeol", [])
silent echo "vscode skip" | AssertEqual "noeol\n", TestChanges("noeol", [], g:mode) silent echo "vscode skip" | AssertEqual "eol\n", TestChanges("eol\n", [])
silent echo "vscode skip" | AssertEqual "eol\n", TestChanges("eol\n", [], g:mode)
endfor
Execute(Add/del eol at eof): Execute(Add/del eol at eof):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "addeol\n", TestChanges("addeol", [MkInsert(MkPos(1, 7), "\n")])
Log g:mode AssertEqual "deleol", TestChanges("deleol\n", [MkDelete(MkPos(1, 7), MkPos(1, 8))])
AssertEqual "addeol\n", TestChanges("addeol", [MkInsert(MkPos(1, 7), "\n")], g:mode)
AssertEqual "deleol", TestChanges("deleol\n", [MkDelete(MkPos(1, 7), MkPos(1, 8))], g:mode)
endfor
Execute(One character insertions to first line): Execute(One character insertions to first line):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "xabc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(1, 0), "x")])
Log g:mode AssertEqual "xabc\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(1, 1), "x")])
AssertEqual "xabc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(1, 0), "x")], g:mode) AssertEqual "axbc\ndef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(1, 2), "x")])
AssertEqual "xabc\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(1, 1), "x")], g:mode) AssertEqual "abcx\ndef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(1, 4), "x")])
AssertEqual "axbc\ndef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(1, 2), "x")], g:mode) AssertEqual "abc\nxdef5\nghi\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(1, 5), "x")])
AssertEqual "abcx\ndef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(1, 4), "x")], g:mode) AssertEqual "abc\nxdef6\nghi\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(1, 6), "x")])
AssertEqual "abc\nxdef5\nghi\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(1, 5), "x")], g:mode)
AssertEqual "abc\nxdef6\nghi\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(1, 6), "x")], g:mode)
endfor
Execute(One character + newline insertions to first line): Execute(One character + newline insertions to first line):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "x\nabc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(1, 0), "x\n")])
Log g:mode AssertEqual "x\nabc\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(1, 1), "x\n")])
AssertEqual "x\nabc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(1, 0), "x\n")], g:mode) AssertEqual "ax\nbc\ndef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(1, 2), "x\n")])
AssertEqual "x\nabc\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(1, 1), "x\n")], g:mode) AssertEqual "abcx\n\ndef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(1, 4), "x\n")])
AssertEqual "ax\nbc\ndef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(1, 2), "x\n")], g:mode) AssertEqual "abc\nx\ndef5\nghi\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(1, 5), "x\n")])
AssertEqual "abcx\n\ndef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(1, 4), "x\n")], g:mode) AssertEqual "abc\nx\ndef6\nghi\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(1, 6), "x\n")])
AssertEqual "abc\nx\ndef5\nghi\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(1, 5), "x\n")], g:mode)
AssertEqual "abc\nx\ndef6\nghi\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(1, 6), "x\n")], g:mode)
endfor
Execute(One character insertions near end): Execute(One character insertions near end):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "abc\ndef1\nghxi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "x")])
Log g:mode AssertEqual "abc\ndef2\nghix\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "x")])
AssertEqual "abc\ndef1\nghxi\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "x")], g:mode) AssertEqual "abc\ndef3\nghi\nx", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "x")])
AssertEqual "abc\ndef2\nghix\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "x")], g:mode) AssertEqual "abc\ndef4\nghi\nx", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "x")])
AssertEqual "abc\ndef3\nghi\nx", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "x")], g:mode) AssertEqual "abc\ndef5\nghi\nx", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "x")])
AssertEqual "abc\ndef4\nghi\nx", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "x")], g:mode) AssertEqual "abc\ndef6\nghi\nx", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(4, 2), "x")])
AssertEqual "abc\ndef5\nghi\nx", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "x")], g:mode) AssertEqual "abc\ndef7\nghi\nx", TestChanges("abc\ndef7\nghi\n", [MkInsert(MkPos(5, 1), "x")])
AssertEqual "abc\ndef6\nghi\nx", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(4, 2), "x")], g:mode) AssertEqual "abc\ndef8\nghi\nx", TestChanges("abc\ndef8\nghi\n", [MkInsert(MkPos(5, 2), "x")])
AssertEqual "abc\ndef7\nghi\nx", TestChanges("abc\ndef7\nghi\n", [MkInsert(MkPos(5, 1), "x")], g:mode)
AssertEqual "abc\ndef8\nghi\nx", TestChanges("abc\ndef8\nghi\n", [MkInsert(MkPos(5, 2), "x")], g:mode)
endfor
Execute(One character + newline insertions near end): Execute(One character + newline insertions near end):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "abc\ndef1\nghx\ni\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "x\n")])
Log g:mode AssertEqual "abc\ndef2\nghix\n\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "x\n")])
AssertEqual "abc\ndef1\nghx\ni\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "x\n")], g:mode) AssertEqual "abc\ndef3\nghi\nx\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "x\n")])
AssertEqual "abc\ndef2\nghix\n\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "x\n")], g:mode) AssertEqual "abc\ndef4\nghi\nx\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "x\n")])
AssertEqual "abc\ndef3\nghi\nx\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "x\n")], g:mode) AssertEqual "abc\ndef5\nghi\nx\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "x\n")])
AssertEqual "abc\ndef4\nghi\nx\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "x\n")], g:mode) AssertEqual "abc\ndef6\nghi\nx\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(4, 2), "x\n")])
AssertEqual "abc\ndef5\nghi\nx\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "x\n")], g:mode)
AssertEqual "abc\ndef6\nghi\nx\n", TestChanges("abc\ndef6\nghi\n", [MkInsert(MkPos(4, 2), "x\n")], g:mode)
endfor
Execute(Newline insertions near end): Execute(Newline insertions near end):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "abc\ndef1\ngh\ni\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "\n")])
Log g:mode AssertEqual "abc\ndef2\nghi\n\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "\n")])
AssertEqual "abc\ndef1\ngh\ni\n", TestChanges("abc\ndef1\nghi\n", [MkInsert(MkPos(3, 3), "\n")], g:mode) AssertEqual "abc\ndef3\nghi\n\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "\n")])
AssertEqual "abc\ndef2\nghi\n\n", TestChanges("abc\ndef2\nghi\n", [MkInsert(MkPos(3, 4), "\n")], g:mode) AssertEqual "abc\ndef4\nghi\n\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "\n")])
AssertEqual "abc\ndef3\nghi\n\n", TestChanges("abc\ndef3\nghi\n", [MkInsert(MkPos(3, 5), "\n")], g:mode) AssertEqual "abc\ndef5\nghi\n\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "\n")])
AssertEqual "abc\ndef4\nghi\n\n", TestChanges("abc\ndef4\nghi\n", [MkInsert(MkPos(3, 6), "\n")], g:mode)
AssertEqual "abc\ndef5\nghi\n\n", TestChanges("abc\ndef5\nghi\n", [MkInsert(MkPos(4, 1), "\n")], g:mode)
endfor
Execute(Single char deletions): Execute(Single char deletions):
for g:mode in ['save', 'file', 'buffer'] AssertEqual "bc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkDelete(MkPos(1, 1), MkPos(1, 2))])
Log g:mode AssertEqual "ab\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkDelete(MkPos(1, 3), MkPos(1, 4))])
AssertEqual "bc\ndef1\nghi\n", TestChanges("abc\ndef1\nghi\n", [MkDelete(MkPos(1, 1), MkPos(1, 2))], g:mode) AssertEqual "abcdef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkDelete(MkPos(1, 4), MkPos(1, 5))])
AssertEqual "ab\ndef2\nghi\n", TestChanges("abc\ndef2\nghi\n", [MkDelete(MkPos(1, 3), MkPos(1, 4))], g:mode) AssertEqual "abcdef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkDelete(MkPos(1, 4), MkPos(1, 6))])
AssertEqual "abcdef3\nghi\n", TestChanges("abc\ndef3\nghi\n", [MkDelete(MkPos(1, 4), MkPos(1, 5))], g:mode) AssertEqual "abc\ndef5\ngh\n", TestChanges("abc\ndef5\nghi\n", [MkDelete(MkPos(3, 3), MkPos(3, 4))])
AssertEqual "abcdef4\nghi\n", TestChanges("abc\ndef4\nghi\n", [MkDelete(MkPos(1, 4), MkPos(1, 6))], g:mode) AssertEqual "abc\ndef6\nghi", TestChanges("abc\ndef6\nghi\n", [MkDelete(MkPos(3, 4), MkPos(3, 5))])
AssertEqual "abc\ndef5\ngh\n", TestChanges("abc\ndef5\nghi\n", [MkDelete(MkPos(3, 3), MkPos(3, 4))], g:mode) AssertEqual "abc\ndef7\nghi", TestChanges("abc\ndef7\nghi\n", [MkDelete(MkPos(3, 4), MkPos(3, 6))])
AssertEqual "abc\ndef6\nghi", TestChanges("abc\ndef6\nghi\n", [MkDelete(MkPos(3, 4), MkPos(3, 5))], g:mode) AssertEqual "abc\ndef8\nghi\n", TestChanges("abc\ndef8\nghi\n", [MkDelete(MkPos(4, 1), MkPos(4, 2))])
AssertEqual "abc\ndef7\nghi", TestChanges("abc\ndef7\nghi\n", [MkDelete(MkPos(3, 4), MkPos(3, 6))], g:mode)
AssertEqual "abc\ndef8\nghi\n", TestChanges("abc\ndef8\nghi\n", [MkDelete(MkPos(4, 1), MkPos(4, 2))], g:mode)
endfor

View File

@ -10,7 +10,7 @@ Execute():
\ {'end': {'offset': 15, 'line': 3}, 'newText': " return c\n\n\ndef main():\n c = func_qtffgsvi()\n", 'start': {'offset': 15, 'line': 3}} \ {'end': {'offset': 15, 'line': 3}, 'newText': " return c\n\n\ndef main():\n c = func_qtffgsvi()\n", 'start': {'offset': 15, 'line': 3}}
\] \]
call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0) call ale#code_action#ApplyChanges(expand('%:p'), g:changes, {})
Expect(The changes should be applied correctly): Expect(The changes should be applied correctly):
def func_qtffgsvi(): def func_qtffgsvi():
@ -40,7 +40,7 @@ Execute():
\ {'end': {'offset': 42, 'line': 8}, 'newText': "ivlpdpao(f)\n", 'start': {'offset': 33, 'line': 6}} \ {'end': {'offset': 42, 'line': 8}, 'newText': "ivlpdpao(f)\n", 'start': {'offset': 33, 'line': 6}}
\] \]
call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0) call ale#code_action#ApplyChanges(expand('%:p'), g:changes, {})
Expect(The changes should be applied correctly): Expect(The changes should be applied correctly):
import sys import sys

View File

@ -59,7 +59,7 @@ Before:
function! ale#code_action#HandleCodeAction(code_action, options) abort function! ale#code_action#HandleCodeAction(code_action, options) abort
let g:handle_code_action_called = 1 let g:handle_code_action_called = 1
Assert !get(a:options, 'should_save') AssertEqual !&hidden, get(a:options, 'should_save')
call add(g:code_actions, a:code_action) call add(g:code_actions, a:code_action)
endfunction endfunction

View File

@ -59,7 +59,7 @@ Before:
function! ale#code_action#HandleCodeAction(code_action, options) abort function! ale#code_action#HandleCodeAction(code_action, options) abort
let g:handle_code_action_called = 1 let g:handle_code_action_called = 1
Assert get(a:options, 'should_save') AssertEqual !&hidden, get(a:options, 'should_save', 0)
call add(g:code_actions, a:code_action) call add(g:code_actions, a:code_action)
endfunction endfunction