From 381fe1badfe39283f28ca02e0b724e6d49ab23ff Mon Sep 17 00:00:00 2001 From: w0rp Date: Tue, 14 May 2019 00:21:58 +0100 Subject: [PATCH] Close #829 - Close LSP documents when buffers are deleted --- autoload/ale/engine.vim | 4 + autoload/ale/lsp.vim | 29 +++++ test/lsp/test_closing_documents.vader | 176 ++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 test/lsp/test_closing_documents.vader diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 7db808d6..491d3c2e 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -710,6 +710,10 @@ function! ale#engine#Cleanup(buffer) abort return endif + if exists('*ale#lsp#CloseDocument') + call ale#lsp#CloseDocument(a:buffer) + endif + if !has_key(g:ale_buffer_info, a:buffer) return endif diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 7186d2a9..986e4c1b 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -484,6 +484,35 @@ function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort return l:opened endfunction +" Notify LSP servers or tsserver that a document is closed, if opened before. +" If a document is closed, 1 will be returned, otherwise 0 will be returned. +" +" Only the buffer number is required here. A message will be sent to every +" language server that was notified previously of the document being opened. +function! ale#lsp#CloseDocument(buffer) abort + let l:closed = 0 + + " The connection keys are sorted so the messages are easier to test, and + " so messages are sent in a consistent order. + for l:conn_id in sort(keys(s:connections)) + let l:conn = s:connections[l:conn_id] + + if l:conn.initialized && has_key(l:conn.open_documents, a:buffer) + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Close(a:buffer) + else + let l:message = ale#lsp#message#DidClose(a:buffer) + endif + + call ale#lsp#Send(l:conn_id, l:message) + call remove(l:conn.open_documents, a:buffer) + let l:closed = 1 + endif + endfor + + return l:closed +endfunction + " Notify LSP servers or tsserver that a document has changed, if needed. " If a notification is sent, 1 will be returned, otherwise 0 will be returned. function! ale#lsp#NotifyForChanges(conn_id, buffer) abort diff --git a/test/lsp/test_closing_documents.vader b/test/lsp/test_closing_documents.vader new file mode 100644 index 00000000..e6e11415 --- /dev/null +++ b/test/lsp/test_closing_documents.vader @@ -0,0 +1,176 @@ +Before: + runtime autoload/ale/lsp.vim + + let g:message_list = [] + + function! MarkAllConnectionsInitialized() abort + for l:conn in values(ale#lsp#GetConnections()) + let l:conn.initialized = 1 + endfor + endfunction + + function! MarkDocumentOpened() abort + for l:conn in values(ale#lsp#GetConnections()) + let l:conn.open_documents[bufnr('')] = 1 + endfor + endfunction + + function! ale#lsp#Send(conn_id, message) abort + let l:connections = ale#lsp#GetConnections() + + if !l:connections[a:conn_id].initialized + throw 'LSP server not initialized yet!' + endif + + call add(g:message_list, [a:conn_id] + a:message) + endfunction + + call ale#lsp#ResetConnections() + +After: + unlet! g:message_list + delfunction MarkAllConnectionsInitialized + delfunction MarkDocumentOpened + + call ale#lsp#ResetConnections() + + runtime autoload/ale/lsp.vim + +Execute(No errors should be thrown if the connection is not initialized): + call ale#lsp#Register('command', '/foo', {}) + call MarkDocumentOpened() + + call ale#engine#Cleanup(bufnr('')) + AssertEqual [], g:message_list + +Execute(No messages should be sent if the document wasn't opened): + call ale#lsp#Register('command', '/foo', {}) + call MarkAllConnectionsInitialized() + + call ale#engine#Cleanup(bufnr('')) + AssertEqual [], g:message_list + +Execute(A message should be sent if the document was opened): + call ale#lsp#Register('command', '/foo', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list + +Execute(A message should be sent if the document was opened for tsserver): + call ale#lsp#Register('command', '/foo', {}) + call ale#lsp#MarkConnectionAsTsserver('command:/foo') + + call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'ts@open', {'file': expand('%:p')}], + \ ['command:/foo', 1, 'ts@close', {'file': expand('%:p')}], + \ ], + \ g:message_list + +Execute(Re-opening and closing the documents should work): + call ale#lsp#Register('command', '/foo', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') + call ale#engine#Cleanup(bufnr('')) + call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 2, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list + +Execute(Messages for closing documents should be sent to each server): + call ale#lsp#Register('command', '/foo', {}) + call ale#lsp#Register('command', '/bar', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') + call ale#lsp#OpenDocument('command:/bar', bufnr(''), 'lang') + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 2, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/bar', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/bar', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list