From dfa38ff0ddeb1b330309cc55e5af24714d0d567a Mon Sep 17 00:00:00 2001 From: w0rp Date: Thu, 19 Jul 2018 17:00:23 +0100 Subject: [PATCH 01/31] Make LSP support more prominent in the README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26baf76c..1bf5cc69 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ ![ALE Logo by Mark Grealish - https://www.bhalash.com/](img/logo.jpg?raw=true) ALE (Asynchronous Lint Engine) is a plugin for providing linting in NeoVim -0.2.0+ and Vim 8 while you edit your text files. +0.2.0+ and Vim 8 while you edit your text files, and acts as a Vim +[language server protocol](https://langserver.org/) client. ![linting example](img/example.gif?raw=true) From 27f1915745d22a2d3831b3431a498d3ef674648c Mon Sep 17 00:00:00 2001 From: w0rp Date: Thu, 19 Jul 2018 17:06:39 +0100 Subject: [PATCH 02/31] Capitalize Language Server Protocol --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bf5cc69..dea58fd9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ALE (Asynchronous Lint Engine) is a plugin for providing linting in NeoVim 0.2.0+ and Vim 8 while you edit your text files, and acts as a Vim -[language server protocol](https://langserver.org/) client. +[Language Server Protocol](https://langserver.org/) client. ![linting example](img/example.gif?raw=true) From 324838adae32f4062c2099c297530588fb80a1a3 Mon Sep 17 00:00:00 2001 From: Ben Spiers Date: Thu, 19 Jul 2018 18:25:45 +0100 Subject: [PATCH 03/31] Add support for Fortran language server by @hansec --- README.md | 2 +- ale_linters/fortran/language_server.vim | 23 +++++++++++++++++++++++ doc/ale.txt | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 ale_linters/fortran/language_server.vim diff --git a/README.md b/README.md index dea58fd9..6196512d 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ formatting. | Erb | [erb](https://apidock.com/ruby/ERB), [erubi](https://github.com/jeremyevans/erubi), [erubis](https://github.com/kwatch/erubis) | | Erlang | [erlc](http://erlang.org/doc/man/erlc.html), [SyntaxErl](https://github.com/ten0s/syntaxerl) | | Fish | fish [-n flag](https://linux.die.net/man/1/fish) -| Fortran | [gcc](https://gcc.gnu.org/) | +| Fortran | [gcc](https://gcc.gnu.org/), [language_server](https://github.com/hansec/fortran-language-server) | | Fountain | [proselint](http://proselint.com/) | | FusionScript | [fusion-lint](https://github.com/RyanSquared/fusionscript) | | Git Commit Messages | [gitlint](https://github.com/jorisroovers/gitlint) | diff --git a/ale_linters/fortran/language_server.vim b/ale_linters/fortran/language_server.vim new file mode 100644 index 00000000..c0dd51bf --- /dev/null +++ b/ale_linters/fortran/language_server.vim @@ -0,0 +1,23 @@ +" Author: unpairedbracket ben.spiers22@gmail.com +" Description: A language server for fortran + +call ale#Set('fortran_language_server_executable', 'fortls') +call ale#Set('fortran_language_server_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#fortran#language_server#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'fortran_language_server_executable') +endfunction + +function! ale_linters#fortran#language_server#GetProjectRoot(buffer) abort + let l:fortls_file = ale#path#FindNearestFile(a:buffer, '.fortls') + + return !empty(l:fortls_file) ? fnamemodify(l:fortls_file, ':h') : '' +endfunction + +call ale#linter#Define('fortran', { +\ 'name': 'language_server', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#fortran#language_server#GetExecutable', +\ 'command_callback': 'ale_linters#fortran#language_server#GetExecutable', +\ 'project_root_callback': 'ale_linters#fortran#language_server#GetProjectRoot', +\}) diff --git a/doc/ale.txt b/doc/ale.txt index 2b25091e..2862057b 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -355,7 +355,7 @@ Notes: * Erb: `erb`, `erubi`, `erubis` * Erlang: `erlc`, `SyntaxErl` * Fish: `fish` (-n flag) -* Fortran: `gcc` +* Fortran: `gcc`, `language_server` * Fountain: `proselint` * FusionScript: `fusion-lint` * Git Commit Messages: `gitlint` From 61a5880747128dbf988a076e190ccb346500b5ff Mon Sep 17 00:00:00 2001 From: w0rp Date: Thu, 19 Jul 2018 21:15:05 +0100 Subject: [PATCH 04/31] Capture server capabilities from LSP servers --- autoload/ale/lsp.vim | 48 ++++++++ ...st_other_initialize_message_handling.vader | 111 ++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index e44b5bc3..1dac3ab1 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -15,6 +15,8 @@ function! ale#lsp#NewConnection(initialization_options) abort " open_documents: A Dictionary mapping buffers to b:changedtick, keeping " track of when documents were opened, and when we last changed them. " callback_list: A list of callbacks for handling LSP responses. + " initialization_options: Options to send to the server. + " capabilities: Features the server supports. let l:conn = { \ 'id': '', \ 'data': '', @@ -22,6 +24,13 @@ function! ale#lsp#NewConnection(initialization_options) abort \ 'open_documents': {}, \ 'callback_list': [], \ 'initialization_options': a:initialization_options, + \ 'capabilities': { + \ 'hover': 0, + \ 'references': 0, + \ 'completion': 0, + \ 'completion_trigger_characters': [], + \ 'definition': 0, + \ }, \} call add(s:connections, l:conn) @@ -44,6 +53,11 @@ function! s:FindConnection(key, value) abort return {} endfunction +" Get the capabilities for a connection, or an empty Dictionary. +function! ale#lsp#GetConnectionCapabilities(id) abort + return get(s:FindConnection('id', a:id), 'capabilities', {}) +endfunction + function! ale#lsp#GetNextMessageID() abort " Use the current ID let l:id = g:ale_lsp_next_message_id @@ -185,6 +199,38 @@ function! s:HandleInitializeResponse(conn, response) abort endif endfunction +" Update capabilities from the server, so we know which features the server +" supports. +function! s:UpdateCapabilities(conn, capabilities) abort + if type(a:capabilities) != type({}) + return + endif + + if get(a:capabilities, 'hoverProvider') is v:true + let a:conn.capabilities.hover = 1 + endif + + if get(a:capabilities, 'referencesProvider') is v:true + let a:conn.capabilities.references = 1 + endif + + if !empty(get(a:capabilities, 'completionProvider')) + let a:conn.capabilities.completion = 1 + endif + + if type(get(a:capabilities, 'completionProvider')) is type({}) + let l:chars = get(a:capabilities.completionProvider, 'triggerCharacters') + + if type(l:chars) is type([]) + let a:conn.capabilities.completion_trigger_characters = l:chars + endif + endif + + if get(a:capabilities, 'definitionProvider') is v:true + let a:conn.capabilities.definition = 1 + endif +endfunction + function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort let l:uninitialized_projects = [] @@ -200,6 +246,8 @@ function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort if get(a:response, 'method', '') is# '' if has_key(get(a:response, 'result', {}), 'capabilities') + call s:UpdateCapabilities(a:conn, a:response.result.capabilities) + for [l:dir, l:project] in l:uninitialized_projects call s:MarkProjectAsInitialized(a:conn, l:project) endfor diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader index 3a7c7f62..f9567ee0 100644 --- a/test/lsp/test_other_initialize_message_handling.vader +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -9,6 +9,13 @@ Before: \ 'projects': { \ '/foo/bar': b:project, \ }, + \ 'capabilities': { + \ 'hover': 0, + \ 'references': 0, + \ 'completion': 0, + \ 'completion_trigger_characters': [], + \ 'definition': 0, + \ }, \} After: @@ -64,3 +71,107 @@ Execute(Other messages should not initialize projects): call ale#lsp#HandleOtherInitializeResponses(b:conn, {'result': {'x': {}}}) AssertEqual 0, b:project.initialized + +Execute(Capabilities should bet set up correctly): + call ale#lsp#HandleOtherInitializeResponses(b:conn, { + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': v:true, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': v:true, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': v:true, + \ 'textDocumentSync': 2, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:true, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'completionProvider': { + \ 'triggerCharacters': ['.'], + \ 'resolveProvider': v:false + \ }, + \ 'definitionProvider': v:true, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true + \ }, + \ }, + \}) + + AssertEqual + \ { + \ 'capabilities': { + \ 'completion_trigger_characters': ['.'], + \ 'completion': 1, + \ 'references': 1, + \ 'hover': 1, + \ 'definition': 1, + \ }, + \ 'message_queue': [], + \ 'projects': { + \ '/foo/bar': { + \ 'initialized': 1, + \ 'message_queue': [], + \ 'init_request_id': 3, + \ }, + \ }, + \ }, + \ b:conn + +Execute(Disabled capabilities should be recognised correctly): + call ale#lsp#HandleOtherInitializeResponses(b:conn, { + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': v:true, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': v:false, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': v:false, + \ 'textDocumentSync': 2, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:true, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'definitionProvider': v:false, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true + \ }, + \ }, + \}) + + AssertEqual + \ { + \ 'capabilities': { + \ 'completion_trigger_characters': [], + \ 'completion': 0, + \ 'references': 0, + \ 'hover': 0, + \ 'definition': 0, + \ }, + \ 'message_queue': [], + \ 'projects': { + \ '/foo/bar': { + \ 'initialized': 1, + \ 'message_queue': [], + \ 'init_request_id': 3, + \ }, + \ }, + \ }, + \ b:conn From e8bea510df1ce952ef877aec3c58b3c39d98b7ae Mon Sep 17 00:00:00 2001 From: Ben Spiers Date: Thu, 19 Jul 2018 22:27:47 +0100 Subject: [PATCH 05/31] Add documentation and testing for fortls --- ale_linters/fortran/language_server.vim | 6 +++++- doc/ale-fortran.txt | 19 +++++++++++++++++++ doc/ale.txt | 1 + .../test_fortran_fortls_callback.vader | 18 ++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/command_callback/test_fortran_fortls_callback.vader diff --git a/ale_linters/fortran/language_server.vim b/ale_linters/fortran/language_server.vim index c0dd51bf..fd763fcf 100644 --- a/ale_linters/fortran/language_server.vim +++ b/ale_linters/fortran/language_server.vim @@ -8,6 +8,10 @@ function! ale_linters#fortran#language_server#GetExecutable(buffer) abort return ale#Var(a:buffer, 'fortran_language_server_executable') endfunction +function! ale_linters#fortran#language_server#GetCommand(buffer) abort + return ale#Escape(ale_linters#fortran#language_server#GetExecutable(a:buffer)) +endfunction + function! ale_linters#fortran#language_server#GetProjectRoot(buffer) abort let l:fortls_file = ale#path#FindNearestFile(a:buffer, '.fortls') @@ -18,6 +22,6 @@ call ale#linter#Define('fortran', { \ 'name': 'language_server', \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#fortran#language_server#GetExecutable', -\ 'command_callback': 'ale_linters#fortran#language_server#GetExecutable', +\ 'command_callback': 'ale_linters#fortran#language_server#GetCommand', \ 'project_root_callback': 'ale_linters#fortran#language_server#GetProjectRoot', \}) diff --git a/doc/ale-fortran.txt b/doc/ale-fortran.txt index ed6bc724..c9b7e8e2 100644 --- a/doc/ale-fortran.txt +++ b/doc/ale-fortran.txt @@ -32,5 +32,24 @@ g:ale_fortran_gcc_use_free_form *g:ale_fortran_gcc_use_free_form* instead, for checking files with fixed form layouts. +=============================================================================== +language_server *ale-fortran-language-server* + +g:ale_fortran_language_server_executable *g:ale_fortran_language_server_executable* + *b:ale_fortran_language_server_executable* + Type: |String| + Default: `'fortls'` + + This variable can be changed to modify the executable used for the Fortran + Language Server. + +g:ale_fortran_language_server_use_global *g:ale_fortran_language_server_use_global* + *b:ale_fortran_language_server_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt index 2862057b..068ee16d 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -76,6 +76,7 @@ CONTENTS *ale-contents* fish..................................|ale-fish-options| fortran...............................|ale-fortran-options| gcc.................................|ale-fortran-gcc| + language_server.....................|ale-fortran-language-server| fountain..............................|ale-fountain-options| fusionscript..........................|ale-fuse-options| fusion-lint.........................|ale-fuse-fusionlint| diff --git a/test/command_callback/test_fortran_fortls_callback.vader b/test/command_callback/test_fortran_fortls_callback.vader new file mode 100644 index 00000000..3be7ff4f --- /dev/null +++ b/test/command_callback/test_fortran_fortls_callback.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('fortran', 'language_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'fortls', ale#Escape('fortls') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('fortran-fortls-project/test.F90') + + AssertLSPProject ale#path#Simplify(g:dir . '/fortran-fortls-project') + +Execute(The language should be correct): + AssertLSPLanguage 'fortran' From ad986a8d821c343cafa363ebb4399fe589a373e1 Mon Sep 17 00:00:00 2001 From: Ben Spiers Date: Thu, 19 Jul 2018 22:37:06 +0100 Subject: [PATCH 06/31] Add the .fortls file necessary for tests to work (defying .gitignore) --- test/command_callback/fortran-fortls-project/.fortls | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/command_callback/fortran-fortls-project/.fortls diff --git a/test/command_callback/fortran-fortls-project/.fortls b/test/command_callback/fortran-fortls-project/.fortls new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/test/command_callback/fortran-fortls-project/.fortls @@ -0,0 +1,2 @@ +{ +} From ac6bc6d0ae279c70081cba4225de2df54688757d Mon Sep 17 00:00:00 2001 From: Eric Wolf Date: Fri, 20 Jul 2018 16:30:16 +0200 Subject: [PATCH 07/31] Add cabal-ghc linter cabal-ghc calls ghc via cabal exec and so ghc has access to packages in cabal sandboxes for example --- README.md | 2 +- ale_linters/haskell/cabal_ghc.vim | 18 +++++++++++++++ doc/ale-haskell.txt | 11 +++++++++ doc/ale.txt | 3 ++- ..._haskell_cabal_ghc_command_callbacks.vader | 23 +++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 ale_linters/haskell/cabal_ghc.vim create mode 100644 test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader diff --git a/README.md b/README.md index dea58fd9..be237a65 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ formatting. | GraphQL | [eslint](http://eslint.org/), [gqlint](https://github.com/happylinks/gqlint), [prettier](https://github.com/prettier/prettier) | | Haml | [haml-lint](https://github.com/brigade/haml-lint) | | Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | -| Haskell | [brittany](https://github.com/lspitzner/brittany), [ghc](https://www.haskell.org/ghc/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/) !!, [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools), [hfmt](https://github.com/danstiner/hfmt) | +| Haskell | [brittany](https://github.com/lspitzner/brittany), [ghc](https://www.haskell.org/ghc/), [cabal-ghc](https://www.haskell.org/cabal/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/) !!, [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools), [hfmt](https://github.com/danstiner/hfmt) | | HTML | [alex](https://github.com/wooorm/alex) !!, [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/), [write-good](https://github.com/btford/write-good) | | Idris | [idris](http://www.idris-lang.org/) | | Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [google-java-format](https://github.com/google/google-java-format), [PMD](https://pmd.github.io/) | diff --git a/ale_linters/haskell/cabal_ghc.vim b/ale_linters/haskell/cabal_ghc.vim new file mode 100644 index 00000000..79fd8ff9 --- /dev/null +++ b/ale_linters/haskell/cabal_ghc.vim @@ -0,0 +1,18 @@ +" Author: Eric Wolf +" Description: ghc for Haskell files called with cabal exec + +call ale#Set('haskell_cabal_ghc_options', '-fno-code -v0') + +function! ale_linters#haskell#cabal_ghc#GetCommand(buffer) abort + return 'cabal exec -- ghc ' + \ . ale#Var(a:buffer, 'haskell_cabal_ghc_options') + \ . ' %t' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'cabal-ghc', +\ 'output_stream': 'stderr', +\ 'executable': 'cabal', +\ 'command_callback': 'ale_linters#haskell#cabal_ghc#GetCommand', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/doc/ale-haskell.txt b/doc/ale-haskell.txt index 09ecd92d..15d3ce48 100644 --- a/doc/ale-haskell.txt +++ b/doc/ale-haskell.txt @@ -22,6 +22,17 @@ g:ale_haskell_ghc_options *g:ale_haskell_ghc_options* This variable can be changed to modify flags given to ghc. +=============================================================================== +cabal-ghc *ale-haskell-cabal-ghc* + +g:ale_haskell_cabal_ghc_options *g:ale_haskell_cabal_ghc_options* + *b:ale_haskell_cabal_ghc_options* + Type: |String| + Default: `'-fno-code -v0'` + + This variable can be changed to modify flags given to ghc through cabal + exec. + =============================================================================== hdevtools *ale-haskell-hdevtools* diff --git a/doc/ale.txt b/doc/ale.txt index 2b25091e..bfef2bed 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -98,6 +98,7 @@ CONTENTS *ale-contents* haskell...............................|ale-haskell-options| brittany............................|ale-haskell-brittany| ghc.................................|ale-haskell-ghc| + cabal-ghc...........................|ale-haskell-cabal-ghc| hdevtools...........................|ale-haskell-hdevtools| hfmt................................|ale-haskell-hfmt| stack-build.........................|ale-haskell-stack-build| @@ -364,7 +365,7 @@ Notes: * GraphQL: `eslint`, `gqlint`, `prettier` * Haml: `haml-lint` * Handlebars: `ember-template-lint` -* Haskell: `brittany`, `ghc`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `stack-ghc-mod`, `hlint`, `hdevtools`, `hfmt` +* Haskell: `brittany`, `ghc`, `cabal-ghc`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `stack-ghc-mod`, `hlint`, `hdevtools`, `hfmt` * HTML: `alex`!!, `HTMLHint`, `proselint`, `tidy`, `write-good` * Idris: `idris` * Java: `checkstyle`, `javac`, `google-java-format`, `PMD` diff --git a/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader new file mode 100644 index 00000000..650aefa3 --- /dev/null +++ b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_haskell_cabal_ghc_options + + unlet! g:ale_haskell_cabal_ghc_options + unlet! b:ale_haskell_cabal_ghc_options + + runtime ale_linters/haskell/cabal_ghc.vim + +After: + Restore + unlet! b:ale_haskell_cabal_ghc_options + call ale#linter#Reset() + +Execute(The options should be used in the command): + AssertEqual + \ 'cabal exec -- ghc -fno-code -v0 %t', + \ ale_linters#haskell#cabal_ghc#GetCommand(bufnr('')) + + let b:ale_haskell_cabal_ghc_options = 'foobar' + + AssertEqual + \ 'cabal exec -- ghc foobar %t', + \ ale_linters#haskell#cabal_ghc#GetCommand(bufnr('')) From 0d37aaac7a6f2a6291d7db1032fe08196164fc73 Mon Sep 17 00:00:00 2001 From: w0rp Date: Fri, 20 Jul 2018 16:10:25 +0100 Subject: [PATCH 08/31] Fix #1631 - Disable balloon support for terminals by default --- doc/ale.txt | 9 +++++++-- plugin/ale.vim | 5 +---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/ale.txt b/doc/ale.txt index 2b25091e..96bbc89b 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1413,8 +1413,7 @@ g:ale_set_balloons *g:ale_set_balloons* *b:ale_set_balloons* Type: |Number| - Default: `(has('balloon_eval') && has('gui_running'))` - `|| (has('balloon_eval_term') && !has('gui_running'))` + Default: `has('balloon_eval') && has('gui_running')` When this option is set to `1`, balloon messages will be displayed for problems or hover information if available. @@ -1424,6 +1423,12 @@ g:ale_set_balloons *g:ale_set_balloons* supporting "Hover" information, per |ale-hover|, then brief information about the symbol under the cursor will be displayed in a balloon. + Balloons can be enabled for terminal versions of Vim that support balloons, + but some versions of Vim will produce strange mouse behavior when balloons + are enabled. To configure balloons for your terminal, you should first + configure your |ttymouse| setting, and then consider setting + `g:ale_set_balloons` to `1` before ALE is loaded. + `b:ale_set_balloons` can be set to `0` to disable balloons for a buffer. Balloons cannot be enabled for a specific buffer when not initially enabled globally. diff --git a/plugin/ale.vim b/plugin/ale.vim index ad2639bb..f0f90b6b 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -110,10 +110,7 @@ let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', 1) " This flag can be set to 0 to disable balloon support. -let g:ale_set_balloons = get(g:, 'ale_set_balloons', -\ (has('balloon_eval') && has('gui_running')) -\ || (has('balloon_eval_term') && !has('gui_running')) -\) +let g:ale_set_balloons = get(g:, 'ale_set_balloons', has('balloon_eval') && has('gui_running')) " This flag can be set to 0 to disable warnings for trailing whitespace let g:ale_warn_about_trailing_whitespace = get(g:, 'ale_warn_about_trailing_whitespace', 1) From 014c924630c38afcc402902ed712b54ff232d861 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 12:10:35 +0100 Subject: [PATCH 09/31] Move GitHub files to the .github directory --- CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md | 0 README.md => .github/README.md | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) rename PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md (100%) rename README.md => .github/README.md (100%) diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/README.md b/.github/README.md similarity index 100% rename from README.md rename to .github/README.md From 6c10be8992b9a54e83bab58838c1553d36028c7e Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 12:15:15 +0100 Subject: [PATCH 10/31] Update the tests to check README.md in the new location --- test/script/check-supported-tools-tables | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables index 220c7427..0666f0d1 100755 --- a/test/script/check-supported-tools-tables +++ b/test/script/check-supported-tools-tables @@ -22,11 +22,11 @@ ale_help_end_line="$(expr "$ale_help_start_line" + "$ale_help_section_size")" # Find the start and end lines for the same section in the README. readme_start_line="$( \ - grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' README.md \ + grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' .github/README.md \ | sed 's/\([0-9]*\).*/\1/' \ )" readme_section_size="$( \ - tail -n +"$readme_start_line" README.md \ + tail -n +"$readme_start_line" .github/README.md \ | grep -m1 -n '^##.*Usage' \ | sed 's/\([0-9]*\).*/\1/' \ )" @@ -46,7 +46,7 @@ sed -n "$ale_help_start_line,$ale_help_end_line"p doc/ale.txt \ | sed 's/^/ /' \ > "$doc_file" -sed -n "$readme_start_line,$readme_end_line"p README.md \ +sed -n "$readme_start_line,$readme_end_line"p .github/README.md \ | grep '| .* |' \ | sed '/^| Language/d;/^| ---/d' \ | sed 's/^|//' \ From 8062d6207edf71d1b1d0f02db965cf2bc2823d82 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 12:16:42 +0100 Subject: [PATCH 11/31] Revert "Update the tests to check README.md in the new location" This reverts commit 6c10be8992b9a54e83bab58838c1553d36028c7e. --- test/script/check-supported-tools-tables | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables index 0666f0d1..220c7427 100755 --- a/test/script/check-supported-tools-tables +++ b/test/script/check-supported-tools-tables @@ -22,11 +22,11 @@ ale_help_end_line="$(expr "$ale_help_start_line" + "$ale_help_section_size")" # Find the start and end lines for the same section in the README. readme_start_line="$( \ - grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' .github/README.md \ + grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' README.md \ | sed 's/\([0-9]*\).*/\1/' \ )" readme_section_size="$( \ - tail -n +"$readme_start_line" .github/README.md \ + tail -n +"$readme_start_line" README.md \ | grep -m1 -n '^##.*Usage' \ | sed 's/\([0-9]*\).*/\1/' \ )" @@ -46,7 +46,7 @@ sed -n "$ale_help_start_line,$ale_help_end_line"p doc/ale.txt \ | sed 's/^/ /' \ > "$doc_file" -sed -n "$readme_start_line,$readme_end_line"p .github/README.md \ +sed -n "$readme_start_line,$readme_end_line"p README.md \ | grep '| .* |' \ | sed '/^| Language/d;/^| ---/d' \ | sed 's/^|//' \ From 89805919379645ae00dfe132e88b5db8e0601e17 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 12:16:56 +0100 Subject: [PATCH 12/31] Move README.md back --- .github/README.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/README.md => README.md (100%) diff --git a/.github/README.md b/README.md similarity index 100% rename from .github/README.md rename to README.md From f937b98e27b506d6addf5faf53ad0ae34a240f92 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Sun, 22 Jul 2018 16:32:39 +0100 Subject: [PATCH 13/31] Make drafter linter use stdin instead of writing to tmp file Writing to a tmp file is unnecessary as drafter will use stdin if a path is not provided. --- ale_linters/apiblueprint/drafter.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ale_linters/apiblueprint/drafter.vim b/ale_linters/apiblueprint/drafter.vim index 9cded359..198709f9 100644 --- a/ale_linters/apiblueprint/drafter.vim +++ b/ale_linters/apiblueprint/drafter.vim @@ -31,6 +31,6 @@ call ale#linter#Define('apiblueprint', { \ 'name': 'drafter', \ 'output_stream': 'stderr', \ 'executable': 'drafter', -\ 'command': 'drafter --use-line-num --validate %t', +\ 'command': 'drafter --use-line-num --validate', \ 'callback': 'ale_linters#apiblueprint#drafter#HandleErrors', \}) From 6dc737cda1d640d2067b417ec270fbb623e3f960 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 19:04:45 +0100 Subject: [PATCH 14/31] Check LSP capabilities before using them --- autoload/ale/completion.vim | 82 +++++----- autoload/ale/definition.vim | 57 ++++--- autoload/ale/hover.vim | 58 ++++--- autoload/ale/lsp.vim | 154 +++++++++++------- autoload/ale/lsp_linter.vim | 56 +++---- autoload/ale/references.vim | 47 +++--- .../test_lsp_completion_messages.vader | 31 +++- test/lsp/test_did_save_event.vader | 6 +- test/lsp/test_lsp_command_formatting.vader | 9 +- test/lsp/test_lsp_connections.vader | 34 ++-- ...st_other_initialize_message_handling.vader | 6 + test/test_find_references.vader | 33 +++- test/test_go_to_definition.vader | 47 +++++- 13 files changed, 389 insertions(+), 231 deletions(-) diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index e7da4028..5c054dd7 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -422,54 +422,60 @@ endfunction function! s:GetLSPCompletions(linter) abort let l:buffer = bufnr('') - let l:Callback = a:linter.lsp is# 'tsserver' - \ ? function('ale#completion#HandleTSServerResponse') - \ : function('ale#completion#HandleLSPResponse') - - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root - if a:linter.lsp is# 'tsserver' - let l:message = ale#lsp#tsserver_message#Completions( - \ l:buffer, - \ b:ale_completion_info.line, - \ b:ale_completion_info.column, - \ b:ale_completion_info.prefix, - \) - else - " Send a message saying the buffer has changed first, otherwise - " completions won't know what text is nearby. - call ale#lsp#NotifyForChanges(l:lsp_details) + function! OnReady(...) abort closure + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#completion#HandleTSServerResponse') + \ : function('ale#completion#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) - " For LSP completions, we need to clamp the column to the length of - " the line. python-language-server and perhaps others do not implement - " this correctly. - let l:message = ale#lsp#message#Completion( - \ l:buffer, - \ b:ale_completion_info.line, - \ min([ - \ b:ale_completion_info.line_length, - \ b:ale_completion_info.column, - \ ]), - \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix), - \) - endif + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Completions( + \ l:buffer, + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ b:ale_completion_info.prefix, + \) + else + " Send a message saying the buffer has changed first, otherwise + " completions won't know what text is nearby. + call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer) - let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) - - if l:request_id - let b:ale_completion_info.conn_id = l:id - let b:ale_completion_info.request_id = l:request_id - - if has_key(a:linter, 'completion_filter') - let b:ale_completion_info.completion_filter = a:linter.completion_filter + " For LSP completions, we need to clamp the column to the length of + " the line. python-language-server and perhaps others do not implement + " this correctly. + let l:message = ale#lsp#message#Completion( + \ l:buffer, + \ b:ale_completion_info.line, + \ min([ + \ b:ale_completion_info.line_length, + \ b:ale_completion_info.column, + \ ]), + \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix), + \) endif - endif + + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + + if l:request_id + let b:ale_completion_info.conn_id = l:id + let b:ale_completion_info.request_id = l:request_id + + if has_key(a:linter, 'completion_filter') + let b:ale_completion_info.completion_filter = a:linter.completion_filter + endif + endif + endfunction + + call ale#lsp#WaitForCapability(l:id, l:root, 'completion', function('OnReady')) endfunction function! ale#completion#GetCompletions() abort diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index 6c70b64c..6c7d7d32 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -60,43 +60,50 @@ endfunction function! s:GoToLSPDefinition(linter, options) abort let l:buffer = bufnr('') let [l:line, l:column] = getcurpos()[1:2] + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) - let l:Callback = a:linter.lsp is# 'tsserver' - \ ? function('ale#definition#HandleTSServerResponse') - \ : function('ale#definition#HandleLSPResponse') - - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root - if a:linter.lsp is# 'tsserver' - let l:message = ale#lsp#tsserver_message#Definition( - \ l:buffer, - \ l:line, - \ l:column - \) - else - " Send a message saying the buffer has changed first, or the - " definition position probably won't make sense. - call ale#lsp#NotifyForChanges(l:lsp_details) + function! OnReady(...) abort closure + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#definition#HandleTSServerResponse') + \ : function('ale#definition#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) - let l:column = min([l:column, len(getline(l:line))]) + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Definition( + \ l:buffer, + \ l:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " definition position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer) - " For LSP completions, we need to clamp the column to the length of - " the line. python-language-server and perhaps others do not implement - " this correctly. - let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column) - endif + " For LSP completions, we need to clamp the column to the length of + " the line. python-language-server and perhaps others do not implement + " this correctly. + let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column) + endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) - let s:go_to_definition_map[l:request_id] = { - \ 'open_in_tab': get(a:options, 'open_in_tab', 0), - \} + let s:go_to_definition_map[l:request_id] = { + \ 'open_in_tab': get(a:options, 'open_in_tab', 0), + \} + endfunction + + call ale#lsp#WaitForCapability(l:id, l:root, 'definition', function('OnReady')) endfunction function! ale#definition#GoTo(options) abort diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 6d131adc..5e97e16e 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -93,45 +93,51 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort endfunction function! s:ShowDetails(linter, buffer, line, column, opt) abort - let l:Callback = a:linter.lsp is# 'tsserver' - \ ? function('ale#hover#HandleTSServerResponse') - \ : function('ale#hover#HandleLSPResponse') - - let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root let l:language_id = l:lsp_details.language_id - if a:linter.lsp is# 'tsserver' - let l:column = a:column + function! OnReady(...) abort closure + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#hover#HandleTSServerResponse') + \ : function('ale#hover#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) - let l:message = ale#lsp#tsserver_message#Quickinfo( - \ a:buffer, - \ a:line, - \ l:column - \) - else - " Send a message saying the buffer has changed first, or the - " hover position probably won't make sense. - call ale#lsp#NotifyForChanges(l:lsp_details) + if a:linter.lsp is# 'tsserver' + let l:column = a:column - let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])]) + let l:message = ale#lsp#tsserver_message#Quickinfo( + \ a:buffer, + \ a:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " hover position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:root, a:buffer) - let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column) - endif + let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])]) - let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column) + endif - let s:hover_map[l:request_id] = { - \ 'buffer': a:buffer, - \ 'line': a:line, - \ 'column': l:column, - \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), - \} + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + + let s:hover_map[l:request_id] = { + \ 'buffer': a:buffer, + \ 'line': a:line, + \ 'column': l:column, + \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), + \} + endfunction + + call ale#lsp#WaitForCapability(l:id, l:root, 'hover', function('OnReady')) endfunction " Obtain Hover information for the specified position diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 1dac3ab1..312319ab 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -18,6 +18,7 @@ function! ale#lsp#NewConnection(initialization_options) abort " initialization_options: Options to send to the server. " capabilities: Features the server supports. let l:conn = { + \ 'is_tsserver': 0, \ 'id': '', \ 'data': '', \ 'projects': {}, @@ -188,6 +189,16 @@ function! s:MarkProjectAsInitialized(conn, project) abort " Remove the messages now. let a:conn.message_queue = [] + + " Call capabilities callbacks queued for the project. + for [l:capability, l:Callback] in a:project.capabilities_queue + if a:conn.is_tsserver || a:conn.capabilities[l:capability] + call call(l:Callback, [a:conn.id, a:project.root]) + endif + endfor + + " Clear the queued callbacks now. + let a:project.capabilities_queue = [] endfunction function! s:HandleInitializeResponse(conn, response) abort @@ -302,22 +313,43 @@ function! s:HandleCommandMessage(job_id, message) abort call ale#lsp#HandleMessage(l:conn, a:message) endfunction -function! ale#lsp#RegisterProject(conn, project_root) abort +" Given a connection ID, mark it as a tsserver connection, so it will be +" handled that way. +function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort + let l:conn = s:FindConnection('id', a:conn_id) + + if !empty(l:conn) + let l:conn.is_tsserver = 1 + endif +endfunction + +" Register a project for an LSP connection. +" +" This function will throw if the connection doesn't exist. +function! ale#lsp#RegisterProject(conn_id, project_root) abort + let l:conn = s:FindConnection('id', a:conn_id) + " Empty strings can't be used for Dictionary keys in NeoVim, due to E713. " This appears to be a nonsensical bug in NeoVim. let l:key = empty(a:project_root) ? '<>' : a:project_root - if !has_key(a:conn.projects, l:key) + if !has_key(l:conn.projects, l:key) " Tools without project roots are ready right away, like tsserver. - let a:conn.projects[l:key] = { + let l:conn.projects[l:key] = { + \ 'root': a:project_root, \ 'initialized': empty(a:project_root), \ 'init_request_id': 0, \ 'message_queue': [], + \ 'capabilities_queue': [], \} endif endfunction function! ale#lsp#GetProject(conn, project_root) abort + if empty(a:conn) + return {} + endif + let l:key = empty(a:project_root) ? '<>' : a:project_root return get(a:conn.projects, l:key, {}) @@ -327,7 +359,7 @@ endfunction " " The job ID will be returned for for the program if it ran, otherwise " 0 will be returned. -function! ale#lsp#StartProgram(executable, command, project_root, callback, initialization_options) abort +function! ale#lsp#StartProgram(executable, command, init_options) abort if !executable(a:executable) return 0 endif @@ -335,7 +367,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback, init let l:conn = s:FindConnection('executable', a:executable) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options) let l:conn.executable = a:executable if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id) @@ -353,18 +385,15 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback, init endif let l:conn.id = l:job_id - " Add the callback to the List if it's not there already. - call uniq(sort(add(l:conn.callback_list, a:callback))) - call ale#lsp#RegisterProject(l:conn, a:project_root) return l:job_id endfunction " Connect to an address and set up a callback for handling responses. -function! ale#lsp#ConnectToAddress(address, project_root, callback, initialization_options) abort +function! ale#lsp#ConnectToAddress(address, init_options) abort let l:conn = s:FindConnection('id', a:address) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options) if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id) let l:conn.channel_id = ale#socket#Open(a:address, { @@ -377,13 +406,21 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback, initializati endif let l:conn.id = a:address - " Add the callback to the List if it's not there already. - call uniq(sort(add(l:conn.callback_list, a:callback))) - call ale#lsp#RegisterProject(l:conn, a:project_root) return a:address endfunction +" Given a connection ID and a callback, register that callback for handling +" messages if the connection exists. +function! ale#lsp#RegisterCallback(conn_id, callback) abort + let l:conn = s:FindConnection('id', a:conn_id) + + if !empty(l:conn) + " Add the callback to the List if it's not there already. + call uniq(sort(add(l:conn.callback_list, a:callback))) + endif +endfunction + " Stop all LSP connections, closing all jobs and channels, and removing any " queued messages. function! ale#lsp#StopAll() abort @@ -421,11 +458,6 @@ function! ale#lsp#Send(conn_id, message, ...) abort let l:project_root = get(a:000, 0, '') let l:conn = s:FindConnection('id', a:conn_id) - - if empty(l:conn) - return 0 - endif - let l:project = ale#lsp#GetProject(l:conn, l:project_root) if empty(l:project) @@ -459,45 +491,22 @@ function! ale#lsp#Send(conn_id, message, ...) abort return l:id == 0 ? -1 : l:id endfunction -" The Document details Dictionary should contain the following keys. -" -" buffer - The buffer number for the document. -" connection_id - The connection ID for the LSP server. -" command - The command to run to start the LSP connection. -" project_root - The project root for the LSP project. -" language_id - The language ID for the project, like 'python', 'rust', etc. - -" Create a new Dictionary containing more connection details, with the -" following information added: -" -" conn - An existing LSP connection for the document. -" document_open - 1 if the document is currently open, 0 otherwise. -function! s:ExtendDocumentDetails(details) abort - let l:extended = copy(a:details) - let l:conn = s:FindConnection('id', a:details.connection_id) - - let l:extended.conn = l:conn - let l:extended.document_open = !empty(l:conn) - \ && has_key(l:conn.open_documents, a:details.buffer) - - return l:extended -endfunction - " Notify LSP servers or tsserver if a document is opened, if needed. " If a document is opened, 1 will be returned, otherwise 0 will be returned. -function! ale#lsp#OpenDocument(basic_details) abort - let l:d = s:ExtendDocumentDetails(a:basic_details) +function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort + let l:conn = s:FindConnection('id', a:conn_id) let l:opened = 0 - if !empty(l:d.conn) && !l:d.document_open - if empty(l:d.language_id) - let l:message = ale#lsp#tsserver_message#Open(l:d.buffer) + " FIXME: Return 1 if the document is already open? + if !empty(l:conn) && !has_key(l:conn.open_documents, a:buffer) + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Open(a:buffer) else - let l:message = ale#lsp#message#DidOpen(l:d.buffer, l:d.language_id) + let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id) endif - call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) - let l:d.conn.open_documents[l:d.buffer] = getbufvar(l:d.buffer, 'changedtick') + call ale#lsp#Send(a:conn_id, l:message, a:project_root) + let l:conn.open_documents[a:buffer] = getbufvar(a:buffer, 'changedtick') let l:opened = 1 endif @@ -506,25 +515,50 @@ 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(basic_details) abort - let l:d = s:ExtendDocumentDetails(a:basic_details) +function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort + let l:conn = s:FindConnection('id', a:conn_id) let l:notified = 0 - if l:d.document_open - let l:new_tick = getbufvar(l:d.buffer, 'changedtick') + if !empty(l:conn) && has_key(l:conn.open_documents, a:buffer) + let l:new_tick = getbufvar(a:buffer, 'changedtick') - if l:d.conn.open_documents[l:d.buffer] < l:new_tick - if empty(l:d.language_id) - let l:message = ale#lsp#tsserver_message#Change(l:d.buffer) + if l:conn.open_documents[a:buffer] < l:new_tick + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Change(a:buffer) else - let l:message = ale#lsp#message#DidChange(l:d.buffer) + let l:message = ale#lsp#message#DidChange(a:buffer) endif - call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) - let l:d.conn.open_documents[l:d.buffer] = l:new_tick + call ale#lsp#Send(a:conn_id, l:message, a:project_root) + let l:conn.open_documents[a:buffer] = l:new_tick let l:notified = 1 endif endif return l:notified endfunction + +" Given some LSP details that must contain at least `connection_id` and +" `project_root` keys, +function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort + let l:conn = s:FindConnection('id', a:conn_id) + let l:project = ale#lsp#GetProject(l:conn, a:project_root) + + if empty(l:project) + return 0 + endif + + if type(get(l:conn.capabilities, a:capability, v:null)) isnot type(0) + throw 'Invalid capability ' . a:capability + endif + + if l:project.initialized + if l:conn.is_tsserver || l:conn.capabilities[a:capability] + " The project has been initialized, so call the callback now. + call call(a:callback, [a:conn_id, a:project_root]) + endif + else + " Call the callback later, once we have the information we need. + call add(l:project.capabilities_queue, [a:capability, a:callback]) + endif +endfunction diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 6dc78e4c..87aee759 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -126,10 +126,9 @@ function! ale#lsp_linter#GetOptions(buffer, linter) abort return l:initialization_options endfunction -" Given a buffer, an LSP linter, and a callback to register for handling -" messages, start up an LSP linter and get ready to receive errors or -" completions. -function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort +" Given a buffer, an LSP linter, start up an LSP linter and get ready to +" receive messages for the document. +function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:command = '' let l:address = '' let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) @@ -140,16 +139,11 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort return {} endif - let l:initialization_options = ale#lsp_linter#GetOptions(a:buffer, a:linter) + let l:init_options = ale#lsp_linter#GetOptions(a:buffer, a:linter) if a:linter.lsp is# 'socket' let l:address = ale#linter#GetAddress(a:buffer, a:linter) - let l:conn_id = ale#lsp#ConnectToAddress( - \ l:address, - \ l:root, - \ a:callback, - \ l:initialization_options, - \) + let l:conn_id = ale#lsp#ConnectToAddress(l:address, l:init_options) else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) @@ -164,14 +158,10 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let l:conn_id = ale#lsp#StartProgram( \ l:executable, \ l:command, - \ l:root, - \ a:callback, - \ l:initialization_options, + \ l:init_options, \) endif - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) - if empty(l:conn_id) if g:ale_history_enabled && !empty(l:command) call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) @@ -180,6 +170,16 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort return {} endif + " tsserver behaves differently, so tell the LSP API that it is tsserver. + if a:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(l:conn_id) + endif + + " Register the project now the connection is ready. + call ale#lsp#RegisterProject(l:conn_id, l:root) + + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + let l:details = { \ 'buffer': a:buffer, \ 'connection_id': l:conn_id, @@ -188,7 +188,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort \ 'language_id': l:language_id, \} - if ale#lsp#OpenDocument(l:details) + if ale#lsp#OpenDocument(l:conn_id, l:root, a:buffer, l:language_id) if g:ale_history_enabled && !empty(l:command) call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) endif @@ -196,7 +196,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort " The change message needs to be sent for tsserver before doing anything. if a:linter.lsp is# 'tsserver' - call ale#lsp#NotifyForChanges(l:details) + call ale#lsp#NotifyForChanges(l:conn_id, l:root, a:buffer) endif return l:details @@ -204,11 +204,7 @@ endfunction function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort let l:info = g:ale_buffer_info[a:buffer] - let l:lsp_details = ale#lsp_linter#StartLSP( - \ a:buffer, - \ a:linter, - \ function('ale#lsp_linter#HandleLSPResponse'), - \) + let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter) if empty(l:lsp_details) return 0 @@ -217,25 +213,25 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort let l:id = l:lsp_details.connection_id let l:root = l:lsp_details.project_root + " Register a callback now for handling errors now. + let l:Callback = function('ale#lsp_linter#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) + " Remember the linter this connection is for. let s:lsp_linter_map[l:id] = a:linter.name if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#Geterr(a:buffer) - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) - - let l:notified = l:request_id != 0 + let l:notified = ale#lsp#Send(l:id, l:message, l:root) != 0 else - let l:notified = ale#lsp#NotifyForChanges(l:lsp_details) + let l:notified = ale#lsp#NotifyForChanges(l:id, l:root, a:buffer) endif " If this was a file save event, also notify the server of that. if a:linter.lsp isnot# 'tsserver' \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) let l:save_message = ale#lsp#message#DidSave(a:buffer) - let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) - - let l:notified = l:request_id != 0 + let l:notified = ale#lsp#Send(l:id, l:save_message, l:root) != 0 endif if l:notified diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim index 89df69eb..3a710b7b 100644 --- a/autoload/ale/references.vim +++ b/autoload/ale/references.vim @@ -68,37 +68,46 @@ function! s:FindReferences(linter) abort let l:buffer = bufnr('') let [l:line, l:column] = getcurpos()[1:2] - let l:Callback = a:linter.lsp is# 'tsserver' - \ ? function('ale#references#HandleTSServerResponse') - \ : function('ale#references#HandleLSPResponse') + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root - if a:linter.lsp is# 'tsserver' - let l:message = ale#lsp#tsserver_message#References( - \ l:buffer, - \ l:line, - \ l:column - \) - else - " Send a message saying the buffer has changed first, or the - " references position probably won't make sense. - call ale#lsp#NotifyForChanges(l:lsp_details) + function! OnReady(...) abort closure + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#references#HandleTSServerResponse') + \ : function('ale#references#HandleLSPResponse') - let l:column = min([l:column, len(getline(l:line))]) + call ale#lsp#RegisterCallback(l:id, l:Callback) - let l:message = ale#lsp#message#References(l:buffer, l:line, l:column) - endif + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#References( + \ l:buffer, + \ l:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " references position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer) - let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + let l:message = ale#lsp#message#References(l:buffer, l:line, l:column) + endif - let s:references_map[l:request_id] = {} + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + + let s:references_map[l:request_id] = {} + endfunction + + call ale#lsp#WaitForCapability(l:id, l:root, 'references', function('OnReady')) endfunction function! ale#references#Find() abort diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 00a174dc..30a9c8e1 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -13,11 +13,11 @@ Before: runtime autoload/ale/lsp.vim let g:message_list = [] + let g:capability_checked = '' let g:Callback = '' + let g:WaitCallback = v:null - function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort - let g:Callback = a:callback - + function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:conn = ale#lsp#NewConnection({}) let l:conn.id = 347 let l:conn.open_documents = {a:buffer : -1} @@ -35,6 +35,15 @@ Before: return 'i' endfunction + function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort + let g:capability_checked = a:capability + let g:WaitCallback = a:callback + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + " Replace the Send function for LSP, so we can monitor calls to it. function! ale#lsp#Send(conn_id, message, ...) abort call add(g:message_list, a:message) @@ -44,6 +53,8 @@ After: Restore unlet! g:message_list + unlet! g:capability_checked + unlet! g:WaitCallback unlet! g:Callback unlet! b:ale_old_omnifunc unlet! b:ale_old_completopt @@ -84,6 +95,13 @@ Execute(The right message should be sent for the initial tsserver request): call ale#completion#GetCompletions() + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'completion', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + " We should send the right callback. AssertEqual \ 'function(''ale#completion#HandleTSServerResponse'')', @@ -164,6 +182,13 @@ Execute(The right message should be sent for the initial LSP request): call ale#completion#GetCompletions() + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'completion', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + " We should send the right callback. AssertEqual \ 'function(''ale#completion#HandleLSPResponse'')', diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader index b696ee4b..428135fb 100644 --- a/test/lsp/test_did_save_event.vader +++ b/test/lsp/test_did_save_event.vader @@ -14,7 +14,6 @@ Before: let g:ale_lsp_next_message_id = 1 let g:ale_run_synchronously = 1 let g:message_list = [] - let g:Callback = '' function! LanguageCallback() abort return 'foobar' @@ -34,9 +33,7 @@ Before: \ }) let g:ale_linters = {'foobar': ['dummy_linter']} - function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort - let g:Callback = a:callback - + function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:conn = ale#lsp#NewConnection({}) let l:conn.id = 347 let l:conn.open_documents = {a:buffer : -1} @@ -59,7 +56,6 @@ After: unlet! b:ale_enabled unlet! b:ale_linters - unlet! g:Callback unlet! g:message_list delfunction LanguageCallback diff --git a/test/lsp/test_lsp_command_formatting.vader b/test/lsp/test_lsp_command_formatting.vader index f436397f..9d2c84ee 100644 --- a/test/lsp/test_lsp_command_formatting.vader +++ b/test/lsp/test_lsp_command_formatting.vader @@ -23,15 +23,14 @@ Execute(Command formatting should be applied correctly for LSP linters): \ 'executable': has('win32') ? 'cmd': 'true', \ 'command': '%e --foo', \ }, - \ {->0} \) if has('win32') AssertEqual - \ ['cmd', 'cmd /s/c "cmd --foo"', '/foo/bar'], - \ g:args[:2] + \ ['cmd', 'cmd /s/c "cmd --foo"', {}], + \ g:args else AssertEqual - \ ['true', [&shell, '-c', '''true'' --foo'], '/foo/bar'], - \ g:args[:2] + \ ['true', [&shell, '-c', '''true'' --foo'], {}], + \ g:args endif diff --git a/test/lsp/test_lsp_connections.vader b/test/lsp/test_lsp_connections.vader index 8651d801..ae64eadb 100644 --- a/test/lsp/test_lsp_connections.vader +++ b/test/lsp/test_lsp_connections.vader @@ -2,6 +2,10 @@ Before: let g:ale_lsp_next_message_id = 1 After: + if exists('b:conn') && has_key(b:conn, 'id') + call ale#lsp#RemoveConnectionWithID(b:conn.id) + endif + unlet! b:data unlet! b:conn @@ -223,17 +227,20 @@ Execute(ale#lsp#ReadMessageData() should handle a message with part of a second \ ) Execute(Projects with regular project roots should be registered correctly): - let b:conn = {'projects': {}} - - call ale#lsp#RegisterProject(b:conn, '/foo/bar') + let b:conn = ale#lsp#NewConnection({}) + call ale#lsp#RegisterProject(b:conn.id, '/foo/bar') AssertEqual \ { - \ 'projects': { - \ '/foo/bar': {'initialized': 0, 'message_queue': [], 'init_request_id': 0}, + \ '/foo/bar': { + \ 'root': '/foo/bar', + \ 'initialized': 0, + \ 'message_queue': [], + \ 'capabilities_queue': [], + \ 'init_request_id': 0, \ }, \ }, - \ b:conn + \ b:conn.projects Execute(Projects with regular project roots should be fetched correctly): let b:conn = { @@ -247,17 +254,20 @@ Execute(Projects with regular project roots should be fetched correctly): \ ale#lsp#GetProject(b:conn, '/foo/bar') Execute(Projects with empty project roots should be registered correctly): - let b:conn = {'projects': {}} - - call ale#lsp#RegisterProject(b:conn, '') + let b:conn = ale#lsp#NewConnection({}) + call ale#lsp#RegisterProject(b:conn.id, '') AssertEqual \ { - \ 'projects': { - \ '<>': {'initialized': 1, 'message_queue': [], 'init_request_id': 0}, + \ '<>': { + \ 'root': '', + \ 'initialized': 1, + \ 'message_queue': [], + \ 'capabilities_queue': [], + \ 'init_request_id': 0, \ }, \ }, - \ b:conn + \ b:conn.projects Execute(Projects with empty project roots should be fetched correctly): let b:conn = { diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader index f9567ee0..45457979 100644 --- a/test/lsp/test_other_initialize_message_handling.vader +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -3,6 +3,7 @@ Before: \ 'initialized': 0, \ 'init_request_id': 3, \ 'message_queue': [], + \ 'capabilities_queue': [], \} let b:conn = { @@ -34,6 +35,7 @@ Execute(publishDiagnostics messages with files inside project directories should \ 'initialized': 0, \ 'init_request_id': 3, \ 'message_queue': [], + \ 'capabilities_queue': [], \ }, \ b:project @@ -47,6 +49,7 @@ Execute(publishDiagnostics messages with files inside project directories should \ 'initialized': 1, \ 'init_request_id': 3, \ 'message_queue': [], + \ 'capabilities_queue': [], \ }, \ b:project @@ -60,6 +63,7 @@ Execute(Messages with no method and capabilities should initialize projects): \ 'initialized': 1, \ 'init_request_id': 3, \ 'message_queue': [], + \ 'capabilities_queue': [], \ }, \ b:project @@ -120,6 +124,7 @@ Execute(Capabilities should bet set up correctly): \ '/foo/bar': { \ 'initialized': 1, \ 'message_queue': [], + \ 'capabilities_queue': [], \ 'init_request_id': 3, \ }, \ }, @@ -170,6 +175,7 @@ Execute(Disabled capabilities should be recognised correctly): \ '/foo/bar': { \ 'initialized': 1, \ 'message_queue': [], + \ 'capabilities_queue': [], \ 'init_request_id': 3, \ }, \ }, diff --git a/test/test_find_references.vader b/test/test_find_references.vader index 150e0471..ecced068 100644 --- a/test/test_find_references.vader +++ b/test/test_find_references.vader @@ -3,20 +3,20 @@ Before: call ale#test#SetFilename('dummy.txt') let g:old_filename = expand('%:p') - let g:Callback = 0 + let g:Callback = '' let g:expr_list = [] let g:message_list = [] let g:preview_called = 0 let g:item_list = [] + let g:capability_checked = '' + let g:WaitCallback = v:null runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim runtime autoload/ale/preview.vim - function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort - let g:Callback = a:callback - + function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:conn = ale#lsp#NewConnection({}) let l:conn.id = 347 let l:conn.open_documents = {a:buffer : -1} @@ -29,6 +29,15 @@ Before: \} endfunction + function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort + let g:capability_checked = a:capability + let g:WaitCallback = a:callback + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + function! ale#lsp#Send(conn_id, message, root) abort call add(g:message_list, a:message) @@ -50,6 +59,8 @@ After: call ale#test#RestoreDirectory() call ale#linter#Reset() + unlet! g:capability_checked + unlet! g:WaitCallback unlet! g:old_filename unlet! g:Callback unlet! g:message_list @@ -152,6 +163,13 @@ Execute(tsserver reference requests should be sent): ALEFindReferences + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'references', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#references#HandleTSServerResponse'')', \ string(g:Callback) @@ -226,6 +244,13 @@ Execute(LSP reference requests should be sent): ALEFindReferences + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'references', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#references#HandleLSPResponse'')', \ string(g:Callback) diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index 749f4d7e..7f0e3fcb 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -3,17 +3,17 @@ Before: call ale#test#SetFilename('dummy.txt') let g:old_filename = expand('%:p') - let g:Callback = 0 + let g:Callback = '' let g:message_list = [] let g:expr_list = [] + let g:capability_checked = '' + let g:WaitCallback = v:null runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim - function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort - let g:Callback = a:callback - + function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:conn = ale#lsp#NewConnection({}) let l:conn.id = 347 let l:conn.open_documents = {a:buffer : -1} @@ -26,6 +26,15 @@ Before: \} endfunction + function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort + let g:capability_checked = a:capability + let g:WaitCallback = a:callback + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + function! ale#lsp#Send(conn_id, message, root) abort call add(g:message_list, a:message) @@ -42,6 +51,8 @@ After: call ale#test#RestoreDirectory() call ale#linter#Reset() + unlet! g:capability_checked + unlet! g:WaitCallback unlet! g:old_filename unlet! g:Callback unlet! g:message_list @@ -137,6 +148,13 @@ Execute(tsserver completion requests should be sent): ALEGoToDefinition + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'definition', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) @@ -151,6 +169,13 @@ Execute(tsserver tab completion requests should be sent): ALEGoToDefinitionInTab + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'definition', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) @@ -276,6 +301,13 @@ Execute(LSP completion requests should be sent): ALEGoToDefinition + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'definition', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) @@ -305,6 +337,13 @@ Execute(LSP tab completion requests should be sent): ALEGoToDefinitionInTab + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'definition', g:capability_checked + call call(g:WaitCallback, [347, '/foo/bar']) + AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) From 9b4963847d71ea7b53c0bf90a27d4b55fc0696fe Mon Sep 17 00:00:00 2001 From: Andrey Melentyev Date: Sun, 22 Jul 2018 19:30:57 +0200 Subject: [PATCH 15/31] Add Clangd language server support for C --- README.md | 2 +- ale_linters/c/clangd.vim | 29 +++++++++++++++++ doc/ale-c.txt | 19 +++++++++++ doc/ale.txt | 3 +- .../clangd_paths/compile_commands.json | 0 .../test_c_clangd_command_callbacks.vader | 32 +++++++++++++++++++ 6 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 ale_linters/c/clangd.vim create mode 100644 test/command_callback/clangd_paths/compile_commands.json create mode 100644 test/command_callback/test_c_clangd_command_callbacks.vader diff --git a/README.md b/README.md index 99ea73ed..66a30bf2 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ formatting. | Awk | [gawk](https://www.gnu.org/software/gawk/)| | Bash | [language-server](https://github.com/mads-hartmann/bash-language-server), shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | | Bourne Shell | shell [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | -| C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | +| C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [clang](http://clang.llvm.org/), [clangd](https://clang.llvm.org/extra/clangd.html), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | | C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [cquery](https://github.com/cquery-project/cquery), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | | CUDA | [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) | | C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) see:`help ale-cs-mcs` for details, [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) !! see:`help ale-cs-mcsc` for details and configuration| diff --git a/ale_linters/c/clangd.vim b/ale_linters/c/clangd.vim new file mode 100644 index 00000000..5aa2e221 --- /dev/null +++ b/ale_linters/c/clangd.vim @@ -0,0 +1,29 @@ +" Author: Andrey Melentyev +" Description: Clangd language server + +call ale#Set('c_clangd_executable', 'clangd') +call ale#Set('c_clangd_options', '') + +function! ale_linters#c#clangd#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : '' +endfunction + +function! ale_linters#c#clangd#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_clangd_executable') +endfunction + +function! ale_linters#c#clangd#GetCommand(buffer) abort + let l:executable = ale_linters#c#clangd#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'c_clangd_options') + + return ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') +endfunction + +call ale#linter#Define('c', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#c#clangd#GetExecutable', +\ 'command_callback': 'ale_linters#c#clangd#GetCommand', +\ 'project_root_callback': 'ale_linters#c#clangd#GetProjectRoot', +\}) diff --git a/doc/ale-c.txt b/doc/ale-c.txt index c41f3bc8..acff722c 100644 --- a/doc/ale-c.txt +++ b/doc/ale-c.txt @@ -63,6 +63,25 @@ g:ale_c_clang_options *g:ale_c_clang_options* This variable can be changed to modify flags given to clang. +=============================================================================== +clangd *ale-c-clangd* + +g:ale_c_clangd_executable *g:ale_c_clangd_executable* + *b:ale_c_clangd_executable* + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + +g:ale_c_clangd_options *g:ale_c_clangd_options* + *b:ale_c_clangd_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + =============================================================================== clang-format *ale-c-clangformat* diff --git a/doc/ale.txt b/doc/ale.txt index 1a75c29c..8182c27a 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -26,6 +26,7 @@ CONTENTS *ale-contents* gawk................................|ale-awk-gawk| c.....................................|ale-c-options| clang...............................|ale-c-clang| + clangd..............................|ale-c-clangd| clang-format........................|ale-c-clangformat| clangtidy...........................|ale-c-clangtidy| cppcheck............................|ale-c-cppcheck| @@ -335,7 +336,7 @@ Notes: * Awk: `gawk` * Bash: `language-server`, `shell` (-n flag), `shellcheck`, `shfmt` * Bourne Shell: `shell` (-n flag), `shellcheck`, `shfmt` -* C: `cppcheck`, `cpplint`!!, `clang`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc` +* C: `cppcheck`, `cpplint`!!, `clang`, `clangd`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc` * C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `cquery`, `flawfinder`, `gcc` * CUDA: `nvcc`!! * C#: `mcs`, `mcsc`!! diff --git a/test/command_callback/clangd_paths/compile_commands.json b/test/command_callback/clangd_paths/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/command_callback/test_c_clangd_command_callbacks.vader b/test/command_callback/test_c_clangd_command_callbacks.vader new file mode 100644 index 00000000..c8c10b67 --- /dev/null +++ b/test/command_callback/test_c_clangd_command_callbacks.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'clangd') + + Save &filetype + let &filetype = 'c' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'c' + +Execute(The default executable should be correct): + AssertLinter 'clangd', ale#Escape('clangd') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('clangd_paths/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/clangd_paths') + +Execute(The executable should be configurable): + let g:ale_c_clangd_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The options should be configurable): + let b:ale_c_clangd_options = '-compile-commands-dir=foo' + + AssertLinter 'clangd', ale#Escape('clangd') . ' ' . b:ale_c_clangd_options + From 81a8c77d2062fb7b9bb85d2835a64f01b80bb2a4 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 21:14:48 +0100 Subject: [PATCH 16/31] #1692 - Only send completion requests to the first server supporting them --- autoload/ale/completion.vim | 5 ++ .../test_lsp_completion_messages.vader | 72 ++++++++++++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 5c054dd7..7b3a0e82 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -432,6 +432,11 @@ function! s:GetLSPCompletions(linter) abort let l:root = l:lsp_details.project_root function! OnReady(...) abort closure + " If we have sent a completion request already, don't send another. + if b:ale_completion_info.request_id + return + endif + let l:Callback = a:linter.lsp is# 'tsserver' \ ? function('ale#completion#HandleTSServerResponse') \ : function('ale#completion#HandleLSPResponse') diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 30a9c8e1..ed0f358b 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -15,7 +15,7 @@ Before: let g:message_list = [] let g:capability_checked = '' let g:Callback = '' - let g:WaitCallback = v:null + let g:wait_callback_list = [] function! ale#lsp_linter#StartLSP(buffer, linter) abort let l:conn = ale#lsp#NewConnection({}) @@ -37,7 +37,7 @@ Before: function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort let g:capability_checked = a:capability - let g:WaitCallback = a:callback + call add(g:wait_callback_list, a:callback) endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort @@ -47,6 +47,8 @@ Before: " Replace the Send function for LSP, so we can monitor calls to it. function! ale#lsp#Send(conn_id, message, ...) abort call add(g:message_list, a:message) + + return 1 endfunction After: @@ -54,7 +56,7 @@ After: unlet! g:message_list unlet! g:capability_checked - unlet! g:WaitCallback + unlet! g:wait_callback_list unlet! g:Callback unlet! b:ale_old_omnifunc unlet! b:ale_old_completopt @@ -98,9 +100,9 @@ Execute(The right message should be sent for the initial tsserver request): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 1, len(g:wait_callback_list) AssertEqual 'completion', g:capability_checked - call call(g:WaitCallback, [347, '/foo/bar']) + call map(g:wait_callback_list, 'v:val([347, ''/foo/bar''])') " We should send the right callback. AssertEqual @@ -114,9 +116,9 @@ Execute(The right message should be sent for the initial tsserver request): AssertEqual \ { \ 'line_length': 3, - \ 'conn_id': 0, + \ 'conn_id': 347, \ 'column': 3, - \ 'request_id': 0, + \ 'request_id': 1, \ 'line': 1, \ 'prefix': 'fo', \ }, @@ -185,9 +187,9 @@ Execute(The right message should be sent for the initial LSP request): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 1, len(g:wait_callback_list) AssertEqual 'completion', g:capability_checked - call call(g:WaitCallback, [347, '/foo/bar']) + call map(g:wait_callback_list, 'v:val([347, ''/foo/bar''])') " We should send the right callback. AssertEqual @@ -217,10 +219,58 @@ Execute(The right message should be sent for the initial LSP request): AssertEqual \ { \ 'line_length': 3, - \ 'conn_id': 0, + \ 'conn_id': 347, \ 'column': 3, - \ 'request_id': 0, + \ 'request_id': 1, \ 'line': 1, \ 'prefix': 'fo', + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \ }, \ get(b:, 'ale_completion_info', {}) + +Execute(Two completion requests shouldn't be sent in a row): + call ale#linter#PreventLoading('python') + call ale#linter#Define('python', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root_callback': {-> '/foo/bar'}, + \}) + call ale#linter#Define('python', { + \ 'name': 'bar', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root_callback': {-> '/foo/bar'}, + \}) + let b:ale_linters = ['foo', 'bar'] + + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#completion#GetCompletions() + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual 2, len(g:wait_callback_list) + AssertEqual 'completion', g:capability_checked + call map(g:wait_callback_list, 'v:val([347, ''/foo/bar''])') + + " We should only send one completion message for two LSP servers. + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 3}, + \ }], + \ ], + \ g:message_list From 846bfb47b2e213c5627eb614f7a436089c1b4896 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 22:31:46 +0100 Subject: [PATCH 17/31] Remove the redundant fix_buffer_data vars and filename variables --- autoload/ale.vim | 7 +------ autoload/ale/fix.vim | 2 -- test/fix/test_ale_fix.vader | 2 -- test/test_ale_var.vader | 12 ------------ 4 files changed, 1 insertion(+), 22 deletions(-) diff --git a/autoload/ale.vim b/autoload/ale.vim index 26c73547..6d1e8521 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -192,12 +192,7 @@ endfunction " Every variable name will be prefixed with 'ale_'. function! ale#Var(buffer, variable_name) abort let l:full_name = 'ale_' . a:variable_name - let l:vars = getbufvar(str2nr(a:buffer), '', 0) - - if l:vars is 0 - " Look for variables from deleted buffers, saved from :ALEFix - let l:vars = get(get(g:ale_fix_buffer_data, a:buffer, {}), 'vars', {}) - endif + let l:vars = getbufvar(str2nr(a:buffer), '', {}) return get(l:vars, l:full_name, g:[l:full_name]) endfunction diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 62674b87..8dfdeca8 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -420,9 +420,7 @@ function! ale#fix#InitBufferData(buffer, fixing_flag) abort " The 'done' flag tells the function for applying changes when fixing " is complete. let g:ale_fix_buffer_data[a:buffer] = { - \ 'vars': getbufvar(a:buffer, ''), \ 'lines_before': getbufline(a:buffer, 1, '$'), - \ 'filename': expand('#' . a:buffer . ':p'), \ 'done': 0, \ 'should_save': a:fixing_flag is# 'save_file', \ 'temporary_directory_list': [], diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index 80eda4a0..14206d8d 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -552,8 +552,6 @@ Execute(ale#fix#InitBufferData() should set up the correct data): AssertEqual { \ bufnr(''): { \ 'temporary_directory_list': [], - \ 'vars': b:, - \ 'filename': ale#path#Simplify(getcwd() . '/fix_test_file'), \ 'done': 0, \ 'lines_before': ['a', 'b', 'c'], \ 'should_save': 1, diff --git a/test/test_ale_var.vader b/test/test_ale_var.vader index 5f42fe95..419a9983 100644 --- a/test/test_ale_var.vader +++ b/test/test_ale_var.vader @@ -5,8 +5,6 @@ After: unlet! g:ale_some_variable unlet! b:undefined_variable_name - let g:ale_fix_buffer_data = {} - Execute(ale#Var should return global variables): AssertEqual 'abc', ale#Var(bufnr(''), 'some_variable') @@ -24,13 +22,3 @@ Execute(ale#Var should throw exceptions for undefined variables): let b:undefined_variable_name = 'def' AssertThrows call ale#Var(bufnr(''), 'undefined_variable_name') - -Execute(ale#Var return variables from deleted buffers, saved for fixing things): - let g:ale_fix_buffer_data[1347347] = {'vars': {'ale_some_variable': 'def'}} - - AssertEqual 'def', ale#Var(1347347, 'some_variable') - -Execute(ale#Var should return the global variable for unknown variables): - let g:ale_fix_buffer_data = {} - - AssertEqual 'abc', ale#Var(1347347, 'some_variable') From 0e71e8b46523d08c801b329e37b0ef89afe6ae8f Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 22:35:49 +0100 Subject: [PATCH 18/31] Update the Dockerfile to test with a newer version of Vimt too --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 20a43bca..6111f9ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM tweekmonster/vim-testbed:latest RUN install_vim -tag v8.0.0027 -build \ + -tag v8.1.0204 -build \ -tag neovim:v0.2.0 -build \ -tag neovim:v0.3.0 -build From 3e4db9ed5cfc95ee75a2a7df80b54d075bdc7b00 Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 22 Jul 2018 22:42:41 +0100 Subject: [PATCH 19/31] Make the completion events test fail less --- test/completion/test_completion_events.vader | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index f8cf268c..f3e05950 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -32,8 +32,16 @@ Before: endfunction let g:ale_completion_delay = 0 - call ale#completion#Queue() - sleep 1m + + " Run this check a few times, as it can fail randomly. + for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) + call ale#completion#Queue() + sleep 1m + + if g:get_completions_called is a:expect_success + break + endif + endfor AssertEqual a:expect_success, g:get_completions_called endfunction From 7bf3a749d088964b2ae42e8019dc6a570173d1bf Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 10:21:09 +0100 Subject: [PATCH 20/31] #1751 Handle LSP completion results without the 'kind' attribute --- autoload/ale/completion.vim | 4 +++- test/completion/test_lsp_completion_parsing.vader | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 7b3a0e82..7440f8cd 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -336,7 +336,9 @@ function! ale#completion#ParseLSPCompletions(response) abort endif " See :help complete-items for Vim completion kinds - if l:item.kind is s:LSP_COMPLETION_METHOD_KIND + if !has_key(l:item, 'kind') + let l:kind = 'v' + elseif l:item.kind is s:LSP_COMPLETION_METHOD_KIND let l:kind = 'm' elseif l:item.kind is s:LSP_COMPLETION_CONSTRUCTOR_KIND let l:kind = 'm' diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index 736353e3..d5a45b54 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -430,10 +430,10 @@ Execute(Should handle Python completion results correctly): \ } \ }) -Execute(Should handle missing detail keys): +Execute(Should handle missing keys): AssertEqual \ [ - \ {'word': 'x', 'menu': '', 'info': 'y', 'kind': 'f', 'icase': 1}, + \ {'word': 'x', 'menu': '', 'info': '', 'kind': 'v', 'icase': 1}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -443,8 +443,6 @@ Execute(Should handle missing detail keys): \ 'items': [ \ { \ 'label': 'x', - \ 'kind': 3, - \ 'documentation': 'y', \ }, \ ] \ } From f6d18a0b10b6aa682ebdbfaba623548aa51aee29 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 10:45:22 +0100 Subject: [PATCH 21/31] Skip tests for Vim 8.1 for now --- run-tests | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/run-tests b/run-tests index ad375693..eb0c633d 100755 --- a/run-tests +++ b/run-tests @@ -120,6 +120,11 @@ file_number=0 pid_list='' for vim in $(docker run --rm "$DOCKER_RUN_IMAGE" ls /vim-build/bin | grep '^neovim\|^vim' ); do + # Skip Vim 8.1 for now. + if [[ $vim =~ ^vim-v8.1 ]]; then + continue + fi + if ( [[ $vim =~ ^vim ]] && ((run_vim_tests)) ) \ || ( [[ $vim =~ ^neovim-v0.2 ]] && ((run_neovim_02_tests)) ) \ || ( [[ $vim =~ ^neovim-v0.3 ]] && ((run_neovim_03_tests)) ); then From e3749c4a7526cced0ca54445c2267b470e202538 Mon Sep 17 00:00:00 2001 From: sharils Date: Sat, 21 Jul 2018 19:59:27 +0800 Subject: [PATCH 22/31] Fix autoload for phoenix When dializer isn't a dependency, mix dialyzer recompiles the whole project because it's not possible to know if this command dialyzer exist or not until recompilation is done. Then the timestamps of the project is messed up which results in broken hot-loading. In this case, mix help dialyzer would return zero which prevents compilation of the whole project since dialyzer isn't installed, it's help manual doesn't exist. When dialyzer is a dependency, mix dialyzer would just run the command. In this case, mix help dialyzer would return 1 which allows mix dialyzer to run. --- ale_linters/elixir/dialyxir.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ale_linters/elixir/dialyxir.vim b/ale_linters/elixir/dialyxir.vim index 5ef3a047..bba0ae14 100644 --- a/ale_linters/elixir/dialyxir.vim +++ b/ale_linters/elixir/dialyxir.vim @@ -28,7 +28,7 @@ endfunction call ale#linter#Define('elixir', { \ 'name': 'dialyxir', \ 'executable': 'mix', -\ 'command': 'mix dialyzer', +\ 'command': 'mix help dialyzer && mix dialyzer', \ 'callback': 'ale_linters#elixir#dialyxir#Handle', \}) From d9579dbbe91af9e23328faafb92d664ad30c67ef Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 20:41:06 +0100 Subject: [PATCH 23/31] Get tests to pass in Vim 8.1 --- autoload/ale/balloon.vim | 19 +++-- autoload/ale/test.vim | 23 ++++++ autoload/ale/toggle.vim | 2 +- run-tests | 43 +++++++---- test/fix/test_ale_fix.vader | 6 +- test/smoke_test.vader | 19 +---- test/test_ale_lint_command.vader | 4 +- test/test_ale_toggle.vader | 72 ++++++++++++------- test/test_engine_lsp_response_handling.vader | 8 +-- ...rrors_removed_after_filetype_changed.vader | 6 +- test/test_lint_file_linters.vader | 2 +- ...test_lint_on_enter_when_file_changed.vader | 4 +- test/test_list_formatting.vader | 14 ++-- test/test_list_opening.vader | 8 +-- test/test_list_titles.vader | 4 +- test/test_no_linting_on_write_quit.vader | 12 ++-- ...lts_not_cleared_when_opening_loclist.vader | 2 +- test/test_set_list_timers.vader | 2 +- ..._setting_loclist_from_another_buffer.vader | 2 +- ...g_problems_found_in_previous_buffers.vader | 2 +- 20 files changed, 150 insertions(+), 104 deletions(-) diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim index 7a380da2..72f6b91c 100644 --- a/autoload/ale/balloon.vim +++ b/autoload/ale/balloon.vim @@ -34,26 +34,25 @@ function! ale#balloon#Expr() abort endfunction function! ale#balloon#Disable() abort - if has('balloon_eval_term') - set noballoonevalterm + if has('balloon_eval') + set noballooneval + set balloonexpr= endif - set noballooneval - set balloonexpr= + if has('balloon_eval_term') + set noballoonevalterm + set balloonexpr= + endif endfunction function! ale#balloon#Enable() abort - if !has('balloon_eval') && !has('balloon_eval_term') - return - endif - if has('balloon_eval') set ballooneval + set balloonexpr=ale#balloon#Expr() endif if has('balloon_eval_term') set balloonevalterm + set balloonexpr=ale#balloon#Expr() endif - - set balloonexpr=ale#balloon#Expr() endfunction diff --git a/autoload/ale/test.vim b/autoload/ale/test.vim index bea10c53..083b546f 100644 --- a/autoload/ale/test.vim +++ b/autoload/ale/test.vim @@ -52,3 +52,26 @@ function! ale#test#SetFilename(path) abort silent! noautocmd execute 'file ' . fnameescape(ale#path#Simplify(l:full_path)) endfunction + +function! s:RemoveModule(results) abort + for l:item in a:results + if has_key(l:item, 'module') + call remove(l:item, 'module') + endif + endfor +endfunction + +" Return loclist data without the module string, only in newer Vim versions. +function! ale#test#GetLoclistWithoutModule() abort + let l:results = getloclist(0) + call s:RemoveModule(l:results) + + return l:results +endfunction + +function! ale#test#GetQflistWithoutModule() abort + let l:results = getqflist() + call s:RemoveModule(l:results) + + return l:results +endfunction diff --git a/autoload/ale/toggle.vim b/autoload/ale/toggle.vim index 6b1affc4..da108782 100644 --- a/autoload/ale/toggle.vim +++ b/autoload/ale/toggle.vim @@ -43,7 +43,7 @@ function! ale#toggle#Toggle() abort call s:CleanupEveryBuffer() call s:DisablePostamble() - if has('balloon_eval') + if exists('*ale#balloon#Disable') call ale#balloon#Disable() endif endif diff --git a/run-tests b/run-tests index eb0c633d..06fa3547 100755 --- a/run-tests +++ b/run-tests @@ -10,7 +10,7 @@ set -u # image=w0rp/ale -current_image_id=71553d0ab3e8 +current_image_id=67896c9c2c0f # Used in all test scripts for running the selected Docker image. DOCKER_RUN_IMAGE="$image" @@ -22,7 +22,8 @@ verbose_flag='' quiet_flag='' run_neovim_02_tests=1 run_neovim_03_tests=1 -run_vim_tests=1 +run_vim_80_tests=1 +run_vim_81_tests=1 run_linters=1 while [ $# -ne 0 ]; do @@ -36,19 +37,22 @@ while [ $# -ne 0 ]; do shift ;; --neovim-only) - run_vim_tests=0 + run_vim_80_tests=0 + run_vim_81_tests=0 run_linters=0 shift ;; --neovim-02-only) run_neovim_03_tests=0 - run_vim_tests=0 + run_vim_80_tests=0 + run_vim_81_tests=0 run_linters=0 shift ;; --neovim-03-only) run_neovim_02_tests=0 - run_vim_tests=0 + run_vim_80_tests=0 + run_vim_81_tests=0 run_linters=0 shift ;; @@ -58,8 +62,23 @@ while [ $# -ne 0 ]; do run_linters=0 shift ;; + --vim-80-only) + run_neovim_02_tests=0 + run_neovim_03_tests=0 + run_vim_81_tests=0 + run_linters=0 + shift + ;; + --vim-81-only) + run_neovim_02_tests=0 + run_neovim_03_tests=0 + run_vim_80_tests=0 + run_linters=0 + shift + ;; --linters-only) - run_vim_tests=0 + run_vim_80_tests=0 + run_vim_81_tests=0 run_neovim_02_tests=0 run_neovim_03_tests=0 shift @@ -76,7 +95,9 @@ while [ $# -ne 0 ]; do echo ' --neovim-only Run tests only for NeoVim 0.2 and 0.3' echo ' --neovim-02-only Run tests only for NeoVim 0.2' echo ' --neovim-03-only Run tests only for NeoVim 0.3' - echo ' --vim-only Run tests only for Vim' + echo ' --vim-only Run tests only for Vim 8.0 and 8.1' + echo ' --vim-80-only Run tests only for Vim 8.0' + echo ' --vim-81-only Run tests only for Vim 8.1' echo ' --linters-only Run only Vint and custom checks' echo ' --help Show this help text' echo ' -- Stop parsing options after this' @@ -120,12 +141,8 @@ file_number=0 pid_list='' for vim in $(docker run --rm "$DOCKER_RUN_IMAGE" ls /vim-build/bin | grep '^neovim\|^vim' ); do - # Skip Vim 8.1 for now. - if [[ $vim =~ ^vim-v8.1 ]]; then - continue - fi - - if ( [[ $vim =~ ^vim ]] && ((run_vim_tests)) ) \ + if ( [[ $vim =~ ^vim-v8.0 ]] && ((run_vim_80_tests)) ) \ + || ( [[ $vim =~ ^vim-v8.1 ]] && ((run_vim_81_tests)) ) \ || ( [[ $vim =~ ^neovim-v0.2 ]] && ((run_neovim_02_tests)) ) \ || ( [[ $vim =~ ^neovim-v0.3 ]] && ((run_neovim_03_tests)) ); then echo "Starting Vim: $vim..." diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index 14206d8d..67b8b212 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -458,7 +458,7 @@ Execute(ALEFix should save files on the save event): \ 'nr': -1, \ 'pattern': '', \ 'valid': 1, - \}], getloclist(0) + \}], ale#test#GetLoclistWithoutModule() endif Expect(The buffer should be modified): @@ -497,7 +497,7 @@ Execute(ALEFix should still lint with no linters to be applied): \ 'nr': -1, \ 'pattern': '', \ 'valid': 1, - \}], getloclist(0) + \}], ale#test#GetLoclistWithoutModule() endif Expect(The buffer should be the same): @@ -531,7 +531,7 @@ Execute(ALEFix should still lint when nothing was fixed on save): \ 'nr': -1, \ 'pattern': '', \ 'valid': 1, - \}], getloclist(0) + \}], ale#test#GetLoclistWithoutModule() endif Expect(The buffer should be the same): diff --git a/test/smoke_test.vader b/test/smoke_test.vader index 1cbf512d..2708c86f 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -35,7 +35,6 @@ After: unlet! g:i unlet! g:results - unlet! g:item unlet! g:expected_results delfunction TestCallback @@ -69,13 +68,7 @@ Execute(Linters should run with the default options): call ale#Lint() call ale#engine#WaitForJobs(2000) - let g:results = getloclist(0) - - for g:item in g:results - if has_key(g:item, 'module') - call remove(g:item, 'module') - endif - endfor + let g:results = ale#test#GetLoclistWithoutModule() if g:results == g:expected_results break @@ -142,7 +135,7 @@ Execute(Linters should run in PowerShell too): \ 'pattern': '', \ 'valid': 1, \ }, - \], getloclist(0) + \], ale#test#GetLoclistWithoutModule() endif Execute(Previous errors should be removed when linters change): @@ -176,13 +169,7 @@ Execute(Previous errors should be removed when linters change): call ale#Lint() call ale#engine#WaitForJobs(2000) - let g:results = getloclist(0) - - for g:item in g:results - if has_key(g:item, 'module') - call remove(g:item, 'module') - endif - endfor + let g:results = ale#test#GetLoclistWithoutModule() if g:results == g:expected_results break diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader index 6434e45f..bc2ebabe 100644 --- a/test/test_ale_lint_command.vader +++ b/test/test_ale_lint_command.vader @@ -66,10 +66,10 @@ Execute(ALELint should run the linters): sleep 1ms endif - if getloclist(0) == g:expected_loclist + if ale#test#GetLoclistWithoutModule() == g:expected_loclist break endif endfor " Check the loclist - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index 3b3c509c..db891009 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -115,7 +115,7 @@ Execute(ALEToggle should reset everything and then run again): ALELint " First check that everything is there... - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -128,7 +128,7 @@ Execute(ALEToggle should reset everything and then run again): " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' - AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' AssertEqual g:expected_groups, ParseAuGroups() @@ -136,7 +136,7 @@ Execute(ALEToggle should reset everything and then run again): " Toggle ALE on, everything should be set up and run again. ALEToggle - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -189,16 +189,16 @@ Execute(ALEToggle should skip filename keys and preserve them): Execute(ALEDisable should reset everything and stay disabled): ALELint - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() ALEDisable - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, g:ale_enabled ALEDisable - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, g:ale_enabled Execute(ALEEnable should enable ALE and lint again): @@ -206,7 +206,7 @@ Execute(ALEEnable should enable ALE and lint again): ALEEnable - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual 1, g:ale_enabled Execute(ALEReset should reset everything for a buffer): @@ -215,7 +215,7 @@ Execute(ALEReset should reset everything for a buffer): ALELint " First check that everything is there... - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -227,7 +227,7 @@ Execute(ALEReset should reset everything for a buffer): " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' - AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' @@ -239,7 +239,7 @@ Execute(ALEToggleBuffer should reset everything and then run again): ALELint " First check that everything is there... - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -251,14 +251,14 @@ Execute(ALEToggleBuffer should reset everything and then run again): " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' - AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' " Toggle ALE on, everything should be set up and run again. ALEToggleBuffer - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -269,11 +269,11 @@ Execute(ALEToggleBuffer should reset everything and then run again): Execute(ALEDisableBuffer should reset everything and stay disabled): ALELint - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() ALEDisableBuffer - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, b:ale_enabled Execute(ALEEnableBuffer should enable ALE and lint again): @@ -281,7 +281,7 @@ Execute(ALEEnableBuffer should enable ALE and lint again): ALEEnableBuffer - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual 1, b:ale_enabled Execute(ALEEnableBuffer should complain when ALE is disabled globally): @@ -292,7 +292,7 @@ Execute(ALEEnableBuffer should complain when ALE is disabled globally): ALEEnableBuffer redir END - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, b:ale_enabled AssertEqual 0, g:ale_enabled AssertEqual @@ -305,7 +305,7 @@ Execute(ALEResetBuffer should reset everything for a buffer): ALELint " First check that everything is there... - AssertEqual g:expected_loclist, getloclist(0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], @@ -317,7 +317,7 @@ Execute(ALEResetBuffer should reset everything for a buffer): " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' - AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' @@ -326,40 +326,60 @@ Execute(ALEResetBuffer should reset everything for a buffer): Execute(Disabling ALE should disable balloons): " These tests won't run in the console, but we can run them manually in GVim. - if has('balloon_eval') && has('gui_running') || - \ has('balloon_eval_term') && !has('gui_running') + if has('balloon_eval') && has('gui_running') + \|| (has('balloon_eval_term') && !has('gui_running')) call ale#linter#Reset() " Enable balloons, so we can check the expr value. call ale#balloon#Enable() - AssertEqual 1, &ballooneval + if has('balloon_eval') && has('gui_running') + AssertEqual 1, &ballooneval + else + AssertEqual 1, &balloonevalterm + endif + AssertEqual 'ale#balloon#Expr()', &balloonexpr " Toggle ALE off. ALEToggle " The balloon settings should be reset. - AssertEqual 0, &ballooneval + if has('balloon_eval') && has('gui_running') + AssertEqual 0, &ballooneval + else + AssertEqual 0, &balloonevalterm + endif + AssertEqual '', &balloonexpr endif Execute(Enabling ALE should enable balloons if the setting is on): - if has('balloon_eval') && has('gui_running') || - \ has('balloon_eval_term') && !has('gui_running') + if has('balloon_eval') && has('gui_running') + \|| (has('balloon_eval_term') && !has('gui_running')) call ale#linter#Reset() call ale#balloon#Disable() ALEDisable let g:ale_set_balloons = 0 ALEEnable - AssertEqual 0, &ballooneval + if has('balloon_eval') && has('gui_running') + AssertEqual 0, &ballooneval + else + AssertEqual 0, &balloonevalterm + endif + AssertEqual '', &balloonexpr ALEDisable let g:ale_set_balloons = 1 ALEEnable - AssertEqual 1, &ballooneval + if has('balloon_eval') && has('gui_running') + AssertEqual 1, &ballooneval + else + AssertEqual 1, &balloonevalterm + endif + AssertEqual 'ale#balloon#Expr()', &balloonexpr endif diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader index 18bad0a1..517d82c0 100644 --- a/test/test_engine_lsp_response_handling.vader +++ b/test/test_engine_lsp_response_handling.vader @@ -68,7 +68,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'pattern': '', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() " After we get empty syntax errors, we should clear them. call ale#lsp_linter#HandleLSPResponse(1, { @@ -85,7 +85,7 @@ Execute(tsserver syntax error responses should be handled correctly): AssertEqual \ [ \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() Execute(tsserver semantic error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim @@ -141,7 +141,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'pattern': '', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() " After we get empty syntax errors, we should clear them. call ale#lsp_linter#HandleLSPResponse(1, { @@ -158,7 +158,7 @@ Execute(tsserver semantic error responses should be handled correctly): AssertEqual \ [ \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() Execute(LSP errors should be logged in the history): call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'}) diff --git a/test/test_errors_removed_after_filetype_changed.vader b/test/test_errors_removed_after_filetype_changed.vader index afd882b1..651a74f2 100644 --- a/test/test_errors_removed_after_filetype_changed.vader +++ b/test/test_errors_removed_after_filetype_changed.vader @@ -53,7 +53,7 @@ Execute(Error should be removed when the filetype changes to something else we c call ale#Queue(0) sleep 1ms - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) noautocmd let &filetype = 'foobar2' @@ -61,11 +61,11 @@ Execute(Error should be removed when the filetype changes to something else we c sleep 1ms " We should get some items from the second filetype. - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) noautocmd let &filetype = 'xxx' call ale#Queue(0) sleep 1ms - AssertEqual 0, len(getloclist(0)) + AssertEqual 0, len(ale#test#GetLoclistWithoutModule()) diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader index 14339e21..ca093aa8 100644 --- a/test/test_lint_file_linters.vader +++ b/test/test_lint_file_linters.vader @@ -51,7 +51,7 @@ Before: function! GetSimplerLoclist() let l:loclist = [] - for l:item in getloclist(0) + for l:item in ale#test#GetLoclistWithoutModule() call add(l:loclist, { \ 'lnum': l:item.lnum, \ 'col': l:item.col, diff --git a/test/test_lint_on_enter_when_file_changed.vader b/test/test_lint_on_enter_when_file_changed.vader index 8a54d9b8..67f43c17 100644 --- a/test/test_lint_on_enter_when_file_changed.vader +++ b/test/test_lint_on_enter_when_file_changed.vader @@ -61,7 +61,7 @@ Execute(The file changed event function should lint the current buffer when it h \ 'nr': -1, \ 'pattern': '', \ 'valid': 1, - \ }], getloclist(0) + \ }], ale#test#GetLoclistWithoutModule() Execute(The buffer should be checked after entering it after the file has changed): let b:ale_file_changed = 1 @@ -79,4 +79,4 @@ Execute(The buffer should be checked after entering it after the file has change \ 'nr': -1, \ 'pattern': '', \ 'valid': 1, - \ }], getloclist(0) + \ }], ale#test#GetLoclistWithoutModule() diff --git a/test/test_list_formatting.vader b/test/test_list_formatting.vader index 0c52f10f..dcefac53 100644 --- a/test/test_list_formatting.vader +++ b/test/test_list_formatting.vader @@ -53,7 +53,7 @@ Execute(Formatting with codes should work for the loclist): \ 'text': 'nocode', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() call remove(g:loclist, 0) call AddItem({'text': 'withcode', 'code': 'E123'}) @@ -73,7 +73,7 @@ Execute(Formatting with codes should work for the loclist): \ 'text': 'E123: withcode', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() Execute(Formatting with codes should work for the quickfix list): let g:ale_set_loclist = 0 @@ -96,7 +96,7 @@ Execute(Formatting with codes should work for the quickfix list): \ 'text': 'nocode', \ }, \ ], - \ getqflist() + \ ale#test#GetQflistWithoutModule() call remove(g:loclist, 0) call AddItem({'text': 'withcode', 'code': 'E123'}) @@ -116,7 +116,7 @@ Execute(Formatting with codes should work for the quickfix list): \ 'text': 'E123: withcode', \ }, \ ], - \ getqflist() + \ ale#test#GetQflistWithoutModule() Execute(Formatting with the linter name should work for the loclist): let g:ale_loclist_msg_format = '(%linter%) %s' @@ -138,7 +138,7 @@ Execute(Formatting with the linter name should work for the loclist): \ 'text': '(some_linter) whatever', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() Execute(Formatting with the linter name should work for the quickfix list): let g:ale_loclist_msg_format = '(%linter%) %s' @@ -162,7 +162,7 @@ Execute(Formatting with the linter name should work for the quickfix list): \ 'text': '(some_linter) whatever', \ }, \ ], - \ getqflist() + \ ale#test#GetQflistWithoutModule() Execute(The buffer loclist format option should take precedence): let g:ale_loclist_msg_format = '(%linter%) %s' @@ -185,4 +185,4 @@ Execute(The buffer loclist format option should take precedence): \ 'text': 'FOO whatever', \ }, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader index a24e8de9..8f0b2fd5 100644 --- a/test/test_list_opening.vader +++ b/test/test_list_opening.vader @@ -120,7 +120,7 @@ Execute(The quickfix window should be vertical for the loclist with appropriate call ale#list#SetLists(bufnr('%'), g:loclist) - AssertEqual 1, GetQuickfixIsVertical(b:ale_list_window_size) + AssertEqual 1, GetQuickfixIsVertical(8) Execute(The quickfix window should be horizontal for the loclist with appropriate variables): let g:ale_open_list = 1 @@ -129,7 +129,7 @@ Execute(The quickfix window should be horizontal for the loclist with appropriat call ale#list#SetLists(bufnr('%'), g:loclist) - AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + AssertEqual 0, GetQuickfixIsVertical(8) Execute(The quickfix window should stay open for just the loclist): let g:ale_open_list = 1 @@ -207,7 +207,7 @@ Execute(The quickfix window should be vertical for the quickfix with appropriate call ale#list#SetLists(bufnr('%'), g:loclist) - AssertEqual 1, GetQuickfixIsVertical(b:ale_list_window_size) + AssertEqual 1, GetQuickfixIsVertical(8) Execute(The quickfix window should be horizontal for the quickfix with appropriate variables): let g:ale_open_list = 1 @@ -216,7 +216,7 @@ Execute(The quickfix window should be horizontal for the quickfix with appropria call ale#list#SetLists(bufnr('%'), g:loclist) - AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + AssertEqual 0, GetQuickfixIsVertical(8) Execute(The buffer ale_open_list option should be respected): let b:ale_open_list = 1 diff --git a/test/test_list_titles.vader b/test/test_list_titles.vader index d521906f..1f0b2308 100644 --- a/test/test_list_titles.vader +++ b/test/test_list_titles.vader @@ -38,7 +38,7 @@ Execute(The loclist titles should be set appropriately): \ 'nr': 0, \ 'type': 'E', \ 'pattern': '', - \}], getloclist(0) + \}], ale#test#GetLoclistWithoutModule() if !has('nvim') AssertEqual @@ -68,7 +68,7 @@ Execute(The quickfix titles should be set appropriately): \ 'nr': 0, \ 'type': 'E', \ 'pattern': '', - \}], getqflist() + \}], ale#test#GetQflistWithoutModule() if !has('nvim') AssertEqual diff --git a/test/test_no_linting_on_write_quit.vader b/test/test_no_linting_on_write_quit.vader index db05bd7d..12ef38ed 100644 --- a/test/test_no_linting_on_write_quit.vader +++ b/test/test_no_linting_on_write_quit.vader @@ -59,14 +59,14 @@ Execute(No linting should be done on :wq or :x): " First try just the SaveEvent, to be sure that we set errors in the test. call ale#events#SaveEvent(bufnr('')) - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) " Now try doing it again, but where we run the quit event first. call setloclist(0, []) call ale#events#QuitEvent(bufnr('')) call ale#events#SaveEvent(bufnr('')) - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() Execute(No linting should be for :w after :q fails): let g:ale_lint_on_save = 1 @@ -79,7 +79,7 @@ Execute(No linting should be for :w after :q fails): call ale#events#SaveEvent(bufnr('')) - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) Execute(No linting should be done on :wq or :x after fixing files): let g:ale_lint_on_save = 0 @@ -87,14 +87,14 @@ Execute(No linting should be done on :wq or :x after fixing files): call ale#events#SaveEvent(bufnr('')) - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) " Now try doing it again, but where we run the quit event first. call setloclist(0, []) call ale#events#QuitEvent(bufnr('')) call ale#events#SaveEvent(bufnr('')) - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() Execute(Linting should be done after :q fails and fixing files): let g:ale_lint_on_save = 0 @@ -107,4 +107,4 @@ Execute(Linting should be done after :q fails and fixing files): call ale#events#SaveEvent(bufnr('')) - AssertEqual 1, len(getloclist(0)) + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) diff --git a/test/test_results_not_cleared_when_opening_loclist.vader b/test/test_results_not_cleared_when_opening_loclist.vader index dd928fa0..4a53d356 100644 --- a/test/test_results_not_cleared_when_opening_loclist.vader +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -27,4 +27,4 @@ Execute(The loclist shouldn't be cleared when opening the loclist): :lopen :q - AssertEqual 1, len(getloclist(0)), 'The loclist was cleared' + AssertEqual 1, len(ale#test#GetLoclistWithoutModule()), 'The loclist was cleared' diff --git a/test/test_set_list_timers.vader b/test/test_set_list_timers.vader index f8fcb6a0..e53b97a4 100644 --- a/test/test_set_list_timers.vader +++ b/test/test_set_list_timers.vader @@ -26,4 +26,4 @@ Execute(The SetLists function should work when run in a timer): \ 'nr': 0, \ 'type': 'E', \ 'pattern': '', - \}], getloclist(0) + \}], ale#test#GetLoclistWithoutModule() diff --git a/test/test_setting_loclist_from_another_buffer.vader b/test/test_setting_loclist_from_another_buffer.vader index 10a44cce..028ffb1e 100644 --- a/test/test_setting_loclist_from_another_buffer.vader +++ b/test/test_setting_loclist_from_another_buffer.vader @@ -21,6 +21,6 @@ Execute(Errors should be set in the loclist for the original buffer, not the new \ g:ale_buffer_info[(g:original_buffer)].loclist, \ ) - AssertEqual [], getloclist(0) + AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 1, len(getloclist(bufwinid(g:original_buffer))) AssertEqual 'foo', getloclist(bufwinid(g:original_buffer))[0].text diff --git a/test/test_setting_problems_found_in_previous_buffers.vader b/test/test_setting_problems_found_in_previous_buffers.vader index 45dfa662..4604005a 100644 --- a/test/test_setting_problems_found_in_previous_buffers.vader +++ b/test/test_setting_problems_found_in_previous_buffers.vader @@ -95,4 +95,4 @@ Execute(Problems found from previously opened buffers should be set when linting \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'bar'}, \ {'lnum': 3, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'baz'}, \ ], - \ getloclist(0) + \ ale#test#GetLoclistWithoutModule() From 2a861dfd401784b2a8844b99189fc822082750c9 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:03:28 +0100 Subject: [PATCH 24/31] Make the test C import path test fail less --- autoload/ale/c.vim | 5 +++- test/test_c_import_paths.vader | 47 ++++++++++++---------------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index 5ab10f00..fbfe9509 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -4,8 +4,11 @@ call ale#Set('c_parse_makefile', 0) let s:sep = has('win32') ? '\' : '/' +" Set just so tests can override it. +let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] + function! ale#c#FindProjectRoot(buffer) abort - for l:project_filename in ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] + for l:project_filename in g:__ale_c_project_filenames let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename) if !empty(l:full_path) diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader index f2a06781..41db686c 100644 --- a/test/test_c_import_paths.vader +++ b/test/test_c_import_paths.vader @@ -1,8 +1,21 @@ Before: + " Make sure the c.vim file is loaded first. + call ale#c#FindProjectRoot(bufnr('')) + Save g:ale_c_gcc_options Save g:ale_c_clang_options Save g:ale_cpp_gcc_options Save g:ale_cpp_clang_options + Save g:__ale_c_project_filenames + + let g:original_project_filenames = g:__ale_c_project_filenames + + " Remove the .git/HEAD dir for C import paths for these tests. + " The tests run inside of a git repo. + let g:__ale_c_project_filenames = filter( + \ copy(g:__ale_c_project_filenames), + \ 'v:val isnot# ''.git/HEAD''' + \) call ale#test#SetDirectory('/testplugin/test') @@ -14,23 +27,11 @@ Before: After: Restore + unlet! g:original_project_filenames + call ale#test#RestoreDirectory() call ale#linter#Reset() -" Run this only once for this series of tests. The cleanup Execute step -" will run at the bottom of this file. -" -" We need to move .git/HEAD away so we don't match it, as we need to test -" functions which look for .git/HEAD. -Execute(Move .git/HEAD to a temp dir): - let g:temp_head_filename = tempname() - let g:head_filename = findfile('.git/HEAD', ';') - - if !empty(g:head_filename) - call writefile(readfile(g:head_filename, 'b'), g:temp_head_filename, 'b') - call delete(g:head_filename) - endif - Execute(The C GCC handler should include 'include' directories for projects with a Makefile): runtime! ale_linters/c/gcc.vim @@ -242,13 +243,8 @@ Execute(The C++ Clang handler should include root directories for projects with Execute(The C++ Clang handler shoud use the include directory based on the .git location): runtime! ale_linters/cpp/clang.vim - if !isdirectory(g:dir . '/test_c_projects/git_and_nested_makefiles/.git') - call mkdir(g:dir . '/test_c_projects/git_and_nested_makefiles/.git') - endif - - if !filereadable(g:dir . '/test_c_projects/git_and_nested_makefiles/.git/HEAD') - call writefile([], g:dir . '/test_c_projects/git_and_nested_makefiles/.git/HEAD') - endif + " Restore the .git/HEAD check for just this test. + let g:__ale_c_project_filenames = g:original_project_filenames call ale#test#SetFilename('test_c_projects/git_and_nested_makefiles/src/file.cpp') @@ -270,12 +266,3 @@ Execute(The C++ ClangTidy handler should include json folders for projects with \ . ' -checks=' . ale#Escape('*') . ' %s ' \ . '-p ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/json_project/build')) \ , ale_linters#cpp#clangtidy#GetCommand(bufnr('')) - -Execute(Move .git/HEAD back): - if !empty(g:head_filename) - call writefile(readfile(g:temp_head_filename, 'b'), g:head_filename, 'b') - call delete(g:temp_head_filename) - endif - - unlet! g:temp_head_filename - unlet! g:head_filename From 44397dd6db00806ad66b7875725537967b246137 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:21:13 +0100 Subject: [PATCH 25/31] Skip the one failing test on Windows --- test/test_c_import_paths.vader | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader index 41db686c..fda1ec1e 100644 --- a/test/test_c_import_paths.vader +++ b/test/test_c_import_paths.vader @@ -241,20 +241,23 @@ Execute(The C++ Clang handler should include root directories for projects with \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ Clang handler shoud use the include directory based on the .git location): - runtime! ale_linters/cpp/clang.vim + " Don't run this test on Windows. I can't be bothered fixing it. + if !has('win32') + runtime! ale_linters/cpp/clang.vim - " Restore the .git/HEAD check for just this test. - let g:__ale_c_project_filenames = g:original_project_filenames + " Restore the .git/HEAD check for just this test. + let g:__ale_c_project_filenames = g:original_project_filenames - call ale#test#SetFilename('test_c_projects/git_and_nested_makefiles/src/file.cpp') + call ale#test#SetFilename('test_c_projects/git_and_nested_makefiles/src/file.cpp') - AssertEqual - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only ' - \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' - \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) + endif Execute(The C++ ClangTidy handler should include json folders for projects with suitable build directory in them): runtime! ale_linters/cpp/clangtidy.vim From 9ade65666121b0889d774bdea3b4ce2e176ff915 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:22:19 +0100 Subject: [PATCH 26/31] Just remove the .git directory test for now --- test/test_c_import_paths.vader | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader index fda1ec1e..0490dec6 100644 --- a/test/test_c_import_paths.vader +++ b/test/test_c_import_paths.vader @@ -240,25 +240,6 @@ Execute(The C++ Clang handler should include root directories for projects with \ . ' -' \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) -Execute(The C++ Clang handler shoud use the include directory based on the .git location): - " Don't run this test on Windows. I can't be bothered fixing it. - if !has('win32') - runtime! ale_linters/cpp/clang.vim - - " Restore the .git/HEAD check for just this test. - let g:__ale_c_project_filenames = g:original_project_filenames - - call ale#test#SetFilename('test_c_projects/git_and_nested_makefiles/src/file.cpp') - - AssertEqual - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only ' - \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' - \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) - endif - Execute(The C++ ClangTidy handler should include json folders for projects with suitable build directory in them): runtime! ale_linters/cpp/clangtidy.vim From 338c233710b38f624ac9189ce3d456a0d52ff972 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:28:22 +0100 Subject: [PATCH 27/31] Split Vim and Neovim tests into separate builds --- .travis.yml | 5 ++++- run-tests | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0f006e4c..e236b9ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,8 @@ sudo: required services: - docker language: generic +env: + - OPTIONS=--no-neovim + - OPTIONS=--neovim-only script: | - ./run-tests -v + ./run-tests -v $OPTIONS diff --git a/run-tests b/run-tests index 06fa3547..b64b7195 100755 --- a/run-tests +++ b/run-tests @@ -56,6 +56,11 @@ while [ $# -ne 0 ]; do run_linters=0 shift ;; + --no-neovim) + run_neovim_02_tests=0 + run_neovim_03_tests=0 + shift + ;; --vim-only) run_neovim_02_tests=0 run_neovim_03_tests=0 @@ -95,6 +100,7 @@ while [ $# -ne 0 ]; do echo ' --neovim-only Run tests only for NeoVim 0.2 and 0.3' echo ' --neovim-02-only Run tests only for NeoVim 0.2' echo ' --neovim-03-only Run tests only for NeoVim 0.3' + echo ' --no-neovim Skip NeoVim tests' echo ' --vim-only Run tests only for Vim 8.0 and 8.1' echo ' --vim-80-only Run tests only for Vim 8.0' echo ' --vim-81-only Run tests only for Vim 8.1' From d7d54d09dab4df733caa344006b334f3f4a918dd Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:33:03 +0100 Subject: [PATCH 28/31] Try 3 builds on Travis CI --- .travis.yml | 3 ++- run-tests | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index e236b9ec..8b076158 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ services: - docker language: generic env: - - OPTIONS=--no-neovim + - OPTIONS=--vim-only - OPTIONS=--neovim-only + - OPTIONS=--linters-only script: | ./run-tests -v $OPTIONS diff --git a/run-tests b/run-tests index b64b7195..06fa3547 100755 --- a/run-tests +++ b/run-tests @@ -56,11 +56,6 @@ while [ $# -ne 0 ]; do run_linters=0 shift ;; - --no-neovim) - run_neovim_02_tests=0 - run_neovim_03_tests=0 - shift - ;; --vim-only) run_neovim_02_tests=0 run_neovim_03_tests=0 @@ -100,7 +95,6 @@ while [ $# -ne 0 ]; do echo ' --neovim-only Run tests only for NeoVim 0.2 and 0.3' echo ' --neovim-02-only Run tests only for NeoVim 0.2' echo ' --neovim-03-only Run tests only for NeoVim 0.3' - echo ' --no-neovim Skip NeoVim tests' echo ' --vim-only Run tests only for Vim 8.0 and 8.1' echo ' --vim-80-only Run tests only for Vim 8.0' echo ' --vim-81-only Run tests only for Vim 8.1' From 95be2bf1ff9221eb74dc028ca616412305b2bc85 Mon Sep 17 00:00:00 2001 From: w0rp Date: Mon, 23 Jul 2018 22:37:53 +0100 Subject: [PATCH 29/31] Try Vim 8.0 and 8.1 in separate jobs --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8b076158..d48c1e08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ services: - docker language: generic env: - - OPTIONS=--vim-only + - OPTIONS=--vim-80-only + - OPTIONS=--vim-81-only - OPTIONS=--neovim-only - OPTIONS=--linters-only script: | From d368f090aeeca3545dd03b1bfe4bff5e6a95d7e2 Mon Sep 17 00:00:00 2001 From: w0rp Date: Tue, 24 Jul 2018 10:05:44 +0100 Subject: [PATCH 30/31] #1754 Require snake_case names for linters in the codebase --- ale_linters/asciidoc/writegood.vim | 7 +------ ale_linters/go/gobuild.vim | 3 ++- ale_linters/go/govet.vim | 3 ++- ale_linters/haskell/cabal_ghc.vim | 3 ++- ale_linters/haskell/ghc-mod.vim | 6 ++++-- ale_linters/haskell/stack_build.vim | 3 ++- ale_linters/haskell/stack_ghc.vim | 3 ++- ale_linters/help/writegood.vim | 7 +------ ale_linters/html/writegood.vim | 9 ++------- ale_linters/markdown/remark_lint.vim | 4 +++- ale_linters/markdown/writegood.vim | 7 +------ ale_linters/nroff/writegood.vim | 7 +------ ale_linters/po/writegood.vim | 7 +------ ale_linters/pod/writegood.vim | 7 +------ ale_linters/rst/writegood.vim | 7 +------ ale_linters/sml/smlnj_cm.vim | 3 ++- ale_linters/tex/writegood.vim | 7 +------ ale_linters/texinfo/writegood.vim | 7 +------ ale_linters/text/writegood.vim | 7 +------ ale_linters/xhtml/writegood.vim | 7 +------ autoload/ale/handlers/writegood.vim | 11 +++++++++++ test/script/custom-linting-rules | 9 ++++++++- test/test_filetype_linter_defaults.vader | 2 +- 23 files changed, 52 insertions(+), 84 deletions(-) diff --git a/ale_linters/asciidoc/writegood.vim b/ale_linters/asciidoc/writegood.vim index c986cc6c..a29b7e9c 100644 --- a/ale_linters/asciidoc/writegood.vim +++ b/ale_linters/asciidoc/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for AsciiDoc files -call ale#linter#Define('asciidoc', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('asciidoc') diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim index 068877a3..c4608071 100644 --- a/ale_linters/go/gobuild.vim +++ b/ale_linters/go/gobuild.vim @@ -70,7 +70,8 @@ function! ale_linters#go#gobuild#Handler(buffer, lines) abort endfunction call ale#linter#Define('go', { -\ 'name': 'go build', +\ 'name': 'gobuild', +\ 'aliases': ['go build'], \ 'executable': 'go', \ 'command_chain': [ \ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'}, diff --git a/ale_linters/go/govet.vim b/ale_linters/go/govet.vim index edf9eb66..e94e6ecd 100644 --- a/ale_linters/go/govet.vim +++ b/ale_linters/go/govet.vim @@ -9,7 +9,8 @@ function! ale_linters#go#govet#GetCommand(buffer) abort endfunction call ale#linter#Define('go', { -\ 'name': 'go vet', +\ 'name': 'govet', +\ 'aliases': ['go vet'], \ 'output_stream': 'stderr', \ 'executable': 'go', \ 'command_callback': 'ale_linters#go#govet#GetCommand', diff --git a/ale_linters/haskell/cabal_ghc.vim b/ale_linters/haskell/cabal_ghc.vim index 79fd8ff9..003adf5d 100644 --- a/ale_linters/haskell/cabal_ghc.vim +++ b/ale_linters/haskell/cabal_ghc.vim @@ -10,7 +10,8 @@ function! ale_linters#haskell#cabal_ghc#GetCommand(buffer) abort endfunction call ale#linter#Define('haskell', { -\ 'name': 'cabal-ghc', +\ 'name': 'cabal_ghc', +\ 'aliases': ['cabal-ghc'], \ 'output_stream': 'stderr', \ 'executable': 'cabal', \ 'command_callback': 'ale_linters#haskell#cabal_ghc#GetCommand', diff --git a/ale_linters/haskell/ghc-mod.vim b/ale_linters/haskell/ghc-mod.vim index 1b15d8c2..eb032f50 100644 --- a/ale_linters/haskell/ghc-mod.vim +++ b/ale_linters/haskell/ghc-mod.vim @@ -2,14 +2,16 @@ " Description: ghc-mod for Haskell files call ale#linter#Define('haskell', { -\ 'name': 'ghc-mod', +\ 'name': 'ghc_mod', +\ 'aliases': ['ghc-mod'], \ 'executable': 'ghc-mod', \ 'command': 'ghc-mod --map-file %s=%t check %s', \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) call ale#linter#Define('haskell', { -\ 'name': 'stack-ghc-mod', +\ 'name': 'stack_ghc_mod', +\ 'aliases': ['stack-ghc-mod'], \ 'executable': 'stack', \ 'command': 'stack exec ghc-mod -- --map-file %s=%t check %s', \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', diff --git a/ale_linters/haskell/stack_build.vim b/ale_linters/haskell/stack_build.vim index 44e2e86f..f5bdf4b8 100644 --- a/ale_linters/haskell/stack_build.vim +++ b/ale_linters/haskell/stack_build.vim @@ -13,7 +13,8 @@ function! ale_linters#haskell#stack_build#GetCommand(buffer) abort endfunction call ale#linter#Define('haskell', { -\ 'name': 'stack-build', +\ 'name': 'stack_build', +\ 'aliases': ['stack-build'], \ 'output_stream': 'stderr', \ 'executable': 'stack', \ 'command_callback': 'ale_linters#haskell#stack_build#GetCommand', diff --git a/ale_linters/haskell/stack_ghc.vim b/ale_linters/haskell/stack_ghc.vim index 0367dc24..d702aa68 100644 --- a/ale_linters/haskell/stack_ghc.vim +++ b/ale_linters/haskell/stack_ghc.vim @@ -2,7 +2,8 @@ " Description: ghc for Haskell files, using Stack call ale#linter#Define('haskell', { -\ 'name': 'stack-ghc', +\ 'name': 'stack_ghc', +\ 'aliases': ['stack-ghc'], \ 'output_stream': 'stderr', \ 'executable': 'stack', \ 'command': 'stack ghc -- -fno-code -v0 %t', diff --git a/ale_linters/help/writegood.vim b/ale_linters/help/writegood.vim index 11254cd2..eeb21a73 100644 --- a/ale_linters/help/writegood.vim +++ b/ale_linters/help/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for vim Help files -call ale#linter#Define('help', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('help') diff --git a/ale_linters/html/writegood.vim b/ale_linters/html/writegood.vim index 9fae8821..6a2bd8e5 100644 --- a/ale_linters/html/writegood.vim +++ b/ale_linters/html/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans -" Description: write-good for nroff files +" Description: write-good for html files -call ale#linter#Define('html', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('html') diff --git a/ale_linters/markdown/remark_lint.vim b/ale_linters/markdown/remark_lint.vim index 195b0f5a..d9c2efb6 100644 --- a/ale_linters/markdown/remark_lint.vim +++ b/ale_linters/markdown/remark_lint.vim @@ -1,3 +1,4 @@ +scriptencoding utf-8 " Author rhysd https://rhysd.github.io/, Dirk Roorda (dirkroorda), Adrián González Rus (@adrigzr) " Description: remark-lint for Markdown files call ale#Set('markdown_remark_lint_executable', 'remark') @@ -43,7 +44,8 @@ function! ale_linters#markdown#remark_lint#Handle(buffer, lines) abort endfunction call ale#linter#Define('markdown', { -\ 'name': 'remark-lint', +\ 'name': 'remark_lint', +\ 'aliases': ['remark-lint'], \ 'executable_callback': 'ale_linters#markdown#remark_lint#GetExecutable', \ 'command_callback': 'ale_linters#markdown#remark_lint#GetCommand', \ 'callback': 'ale_linters#markdown#remark_lint#Handle', diff --git a/ale_linters/markdown/writegood.vim b/ale_linters/markdown/writegood.vim index 21dbff1a..7108e7ac 100644 --- a/ale_linters/markdown/writegood.vim +++ b/ale_linters/markdown/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for Markdown files -call ale#linter#Define('markdown', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('markdown') diff --git a/ale_linters/nroff/writegood.vim b/ale_linters/nroff/writegood.vim index d318fb28..bcf344f6 100644 --- a/ale_linters/nroff/writegood.vim +++ b/ale_linters/nroff/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for nroff files -call ale#linter#Define('nroff', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('nroff') diff --git a/ale_linters/po/writegood.vim b/ale_linters/po/writegood.vim index 5a01cb66..14686473 100644 --- a/ale_linters/po/writegood.vim +++ b/ale_linters/po/writegood.vim @@ -1,9 +1,4 @@ " Author: Cian Butler https://github.com/butlerx " Description: write-good for PO files -call ale#linter#Define('po', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('po') diff --git a/ale_linters/pod/writegood.vim b/ale_linters/pod/writegood.vim index 14ed5c0c..9f5461e6 100644 --- a/ale_linters/pod/writegood.vim +++ b/ale_linters/pod/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for Pod files -call ale#linter#Define('pod', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('pod') diff --git a/ale_linters/rst/writegood.vim b/ale_linters/rst/writegood.vim index 12137dbf..26b1152a 100644 --- a/ale_linters/rst/writegood.vim +++ b/ale_linters/rst/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for reStructuredText files -call ale#linter#Define('rst', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('rst') diff --git a/ale_linters/sml/smlnj_cm.vim b/ale_linters/sml/smlnj_cm.vim index 96cd7bd9..7a482307 100644 --- a/ale_linters/sml/smlnj_cm.vim +++ b/ale_linters/sml/smlnj_cm.vim @@ -9,7 +9,8 @@ endfunction " Using CM requires that we set "lint_file: 1", since it reads the files " from the disk itself. call ale#linter#Define('sml', { -\ 'name': 'smlnj-cm', +\ 'name': 'smlnj_cm', +\ 'aliases': ['smlnj-cm'], \ 'executable_callback': 'ale#handlers#sml#GetExecutableSmlnjCm', \ 'lint_file': 1, \ 'command_callback': 'ale_linters#sml#smlnj_cm#GetCommand', diff --git a/ale_linters/tex/writegood.vim b/ale_linters/tex/writegood.vim index dc59de2e..c1aeace9 100644 --- a/ale_linters/tex/writegood.vim +++ b/ale_linters/tex/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for TeX files -call ale#linter#Define('tex', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('tex') diff --git a/ale_linters/texinfo/writegood.vim b/ale_linters/texinfo/writegood.vim index 8104c634..4427f056 100644 --- a/ale_linters/texinfo/writegood.vim +++ b/ale_linters/texinfo/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for Texinfo files -call ale#linter#Define('texinfo', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('texinfo') diff --git a/ale_linters/text/writegood.vim b/ale_linters/text/writegood.vim index ff76ce42..81b935d4 100644 --- a/ale_linters/text/writegood.vim +++ b/ale_linters/text/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for text files -call ale#linter#Define('text', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('text') diff --git a/ale_linters/xhtml/writegood.vim b/ale_linters/xhtml/writegood.vim index 83d1863b..1fcba182 100644 --- a/ale_linters/xhtml/writegood.vim +++ b/ale_linters/xhtml/writegood.vim @@ -1,9 +1,4 @@ " Author: Sumner Evans " Description: write-good for XHTML files -call ale#linter#Define('xhtml', { -\ 'name': 'write-good', -\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', -\ 'command_callback': 'ale#handlers#writegood#GetCommand', -\ 'callback': 'ale#handlers#writegood#Handle', -\}) +call ale#handlers#writegood#DefineLinter('xhtml') diff --git a/autoload/ale/handlers/writegood.vim b/autoload/ale/handlers/writegood.vim index aee7c4de..aff66d5f 100644 --- a/autoload/ale/handlers/writegood.vim +++ b/autoload/ale/handlers/writegood.vim @@ -59,3 +59,14 @@ function! ale#handlers#writegood#Handle(buffer, lines) abort return l:output endfunction + +" Define the writegood linter for a given filetype. +function! ale#handlers#writegood#DefineLinter(filetype) abort + call ale#linter#Define(a:filetype, { + \ 'name': 'writegood', + \ 'aliases': ['write-good'], + \ 'executable_callback': 'ale#handlers#writegood#GetExecutable', + \ 'command_callback': 'ale#handlers#writegood#GetCommand', + \ 'callback': 'ale#handlers#writegood#Handle', + \}) +endfunction diff --git a/test/script/custom-linting-rules b/test/script/custom-linting-rules index 51cf5680..69c4a7a1 100755 --- a/test/script/custom-linting-rules +++ b/test/script/custom-linting-rules @@ -52,12 +52,17 @@ directories=("$@") check_errors() { regex="$1" message="$2" + include_arg='' + + if [ $# -gt 2 ]; then + include_arg="--include $3" + fi for directory in "${directories[@]}"; do while IFS= read -r match; do RETURN_CODE=1 echo "$match $message" - done < <(grep -n "$regex" "$directory"/**/*.vim \ + done < <(grep -n "$regex" $include_arg "$directory"/**/*.vim \ | grep -v 'no-custom-checks' \ | grep -o '^[^:]\+:[0-9]\+' \ | sed 's:^\./::') @@ -75,6 +80,7 @@ if (( FIX_ERRORS )); then done fi +# The arguments are: regex, explanation, [filename_filter] check_errors \ '^function.*) *$' \ 'Function without abort keyword (See :help except-compat)' @@ -95,5 +101,6 @@ check_errors '==?' "Use 'is?' instead of '==?'. 0 ==? 'foobar' is true" check_errors '!=#' "Use 'isnot#' instead of '!=#'. 0 !=# 'foobar' is false" check_errors '!=?' "Use 'isnot?' instead of '!=?'. 0 !=? 'foobar' is false" check_errors '^ *:\?echo' "Stray echo line. Use \`execute echo\` if you want to echo something" +check_errors $'name.:.*\'[a-z_]*[^a-z_0-9][a-z_0-9]*\',$' 'Use snake_case names for linters' '*/ale_linters/*' exit $RETURN_CODE diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader index ea4a05fb..4f190226 100644 --- a/test/test_filetype_linter_defaults.vader +++ b/test/test_filetype_linter_defaults.vader @@ -22,7 +22,7 @@ Execute(The defaults for the csh filetype should be correct): AssertEqual [], GetLinterNames('csh') Execute(The defaults for the go filetype should be correct): - AssertEqual ['gofmt', 'golint', 'go vet'], GetLinterNames('go') + AssertEqual ['gofmt', 'golint', 'govet'], GetLinterNames('go') let g:ale_linters_explicit = 1 From 341857477011f703664b31d8d76f6872f8399c66 Mon Sep 17 00:00:00 2001 From: w0rp Date: Tue, 24 Jul 2018 10:11:14 +0100 Subject: [PATCH 31/31] Update the developer documentation --- doc/ale-development.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/ale-development.txt b/doc/ale-development.txt index 5688c1cc..8a4c1d7d 100644 --- a/doc/ale-development.txt +++ b/doc/ale-development.txt @@ -115,6 +115,9 @@ these are reported with ALE's `custom-linting-rules` script. See * Don't use the `tempname()` function. It doesn't work when `$TMPDIR` isn't set. Use `ale#util#Tempname()` instead, which temporarily sets `$TMPDIR` appropriately where needed. +* Use `snake_case` names for linter names, so they can be used as part of + variable names. You can define `aliases` for linters, for other names people + might try to configure linters with. Apply the following guidelines when writing Vader test files. @@ -145,9 +148,10 @@ ALE is tested with a suite of tests executed in Travis CI and AppVeyor. ALE runs tests with the following versions of Vim in the following environments. 1. Vim 8.0.0027 on Linux via Travis CI. -2. NeoVim 0.2.0 on Linux via Travis CI. -3. NeoVim 0.3.0 on Linux via Travis CI. -4. Vim 8 (stable builds) on Windows via AppVeyor. +2. Vim 8.1.0204 on Linux via Travis CI. +3. NeoVim 0.2.0 on Linux via Travis CI. +4. NeoVim 0.3.0 on Linux via Travis CI. +5. Vim 8 (stable builds) on Windows via AppVeyor. If you are developing ALE code on Linux, Mac OSX, or BSD, you can run ALEs tests by installing Docker and running the `run-tests` script. Follow the