Merge pull request #1632 from zed0/master

Add the cquery LSP
This commit is contained in:
w0rp 2018-06-06 21:31:36 +01:00 committed by GitHub
commit 67753de531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 186 additions and 12 deletions

View File

@ -93,7 +93,7 @@ formatting.
| Bash | 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) | | Bash | 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) | | 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/), [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) !!, [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) | | 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| | 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|
| Chef | [foodcritic](http://www.foodcritic.io/) | | Chef | [foodcritic](http://www.foodcritic.io/) |
@ -749,8 +749,9 @@ ALE cannot easily detect which compiler flags to use.
Some tools and build configurations can generate Some tools and build configurations can generate
[compile_commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html) [compile_commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html)
files. The `cppcheck`, `clangcheck` and `clangtidy` linters can read these files. The `cppcheck`, `clangcheck`, `clangtidy` and `cquery` linters can read
files for automatically determining the appropriate compiler flags to use. these files for automatically determining the appropriate compiler flags to
use.
For linting with compilers like `gcc` and `clang`, and with other tools, you For linting with compilers like `gcc` and `clang`, and with other tools, you
will need to tell ALE which compiler flags to use yourself. You can use will need to tell ALE which compiler flags to use yourself. You can use

View File

@ -0,0 +1,34 @@
" Author: Ben Falconer <ben@falconers.me.uk>
" Description: A language server for C++
call ale#Set('cpp_cquery_executable', 'cquery')
call ale#Set('cpp_cquery_cache_directory', expand('~/.cache/cquery'))
function! ale_linters#cpp#cquery#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:h') : ''
endfunction
function! ale_linters#cpp#cquery#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'cpp_cquery_executable')
endfunction
function! ale_linters#cpp#cquery#GetCommand(buffer) abort
let l:executable = ale_linters#cpp#cquery#GetExecutable(a:buffer)
return ale#Escape(l:executable)
endfunction
function! ale_linters#cpp#cquery#GetInitializationOptions(buffer) abort
return {'cacheDirectory': ale#Var(a:buffer, 'cpp_cquery_cache_directory')}
endfunction
call ale#linter#Define('cpp', {
\ 'name': 'cquery',
\ 'lsp': 'stdio',
\ 'executable_callback': 'ale_linters#cpp#cquery#GetExecutable',
\ 'command_callback': 'ale_linters#cpp#cquery#GetCommand',
\ 'project_root_callback': 'ale_linters#cpp#cquery#GetProjectRoot',
\ 'initialization_options_callback': 'ale_linters#cpp#cquery#GetInitializationOptions',
\ 'language': 'cpp',
\})

View File

@ -227,6 +227,21 @@ function! ale#linter#PreProcess(linter) abort
throw '`completion_filter` must be a callback' throw '`completion_filter` must be a callback'
endif endif
endif endif
if has_key(a:linter, 'initialization_options_callback')
if has_key(a:linter, 'initialization_options')
throw 'Only one of `initialization_options` or '
\ . '`initialization_options_callback` should be set'
endif
let l:obj.initialization_options_callback = a:linter.initialization_options_callback
if !s:IsCallback(l:obj.initialization_options_callback)
throw '`initialization_options_callback` must be a callback if defined'
endif
elseif has_key(a:linter, 'initialization_options')
let l:obj.initialization_options = a:linter.initialization_options
endif
endif endif
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout') let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
@ -451,12 +466,20 @@ function! ale#linter#StartLSP(buffer, linter, callback) abort
return {} return {}
endif endif
let l:initialization_options = {}
if has_key(a:linter, 'initialization_options_callback')
let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
elseif has_key(a:linter, 'initialization_options')
let l:initialization_options = a:linter.initialization_options
endif
if a:linter.lsp is# 'socket' if a:linter.lsp is# 'socket'
let l:address = ale#linter#GetAddress(a:buffer, a:linter) let l:address = ale#linter#GetAddress(a:buffer, a:linter)
let l:conn_id = ale#lsp#ConnectToAddress( let l:conn_id = ale#lsp#ConnectToAddress(
\ l:address, \ l:address,
\ l:root, \ l:root,
\ a:callback, \ a:callback,
\ l:initialization_options,
\) \)
else else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
@ -474,6 +497,7 @@ function! ale#linter#StartLSP(buffer, linter, callback) abort
\ l:command, \ l:command,
\ l:root, \ l:root,
\ a:callback, \ a:callback,
\ l:initialization_options,
\) \)
endif endif

View File

@ -6,7 +6,7 @@
let s:connections = [] let s:connections = []
let g:ale_lsp_next_message_id = 1 let g:ale_lsp_next_message_id = 1
function! s:NewConnection() abort function! s:NewConnection(initialization_options) abort
" id: The job ID as a Number, or the server address as a string. " id: The job ID as a Number, or the server address as a string.
" data: The message data received so far. " data: The message data received so far.
" executable: An executable only set for program connections. " executable: An executable only set for program connections.
@ -18,6 +18,7 @@ function! s:NewConnection() abort
\ 'projects': {}, \ 'projects': {},
\ 'open_documents': [], \ 'open_documents': [],
\ 'callback_list': [], \ 'callback_list': [],
\ 'initialization_options': a:initialization_options,
\} \}
call add(s:connections, l:conn) call add(s:connections, l:conn)
@ -271,7 +272,7 @@ endfunction
" "
" The job ID will be returned for for the program if it ran, otherwise " The job ID will be returned for for the program if it ran, otherwise
" 0 will be returned. " 0 will be returned.
function! ale#lsp#StartProgram(executable, command, project_root, callback) abort function! ale#lsp#StartProgram(executable, command, project_root, callback, initialization_options) abort
if !executable(a:executable) if !executable(a:executable)
return 0 return 0
endif endif
@ -279,7 +280,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor
let l:conn = s:FindConnection('executable', a:executable) let l:conn = s:FindConnection('executable', a:executable)
" Get the current connection or a new one. " Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() let l:conn = !empty(l:conn) ? l:conn : s:NewConnection(a:initialization_options)
let l:conn.executable = a:executable let l:conn.executable = a:executable
if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id) if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id)
@ -305,10 +306,10 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor
endfunction endfunction
" Connect to an address and set up a callback for handling responses. " Connect to an address and set up a callback for handling responses.
function! ale#lsp#ConnectToAddress(address, project_root, callback) abort function! ale#lsp#ConnectToAddress(address, project_root, callback, initialization_options) abort
let l:conn = s:FindConnection('id', a:address) let l:conn = s:FindConnection('id', a:address)
" Get the current connection or a new one. " Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() let l:conn = !empty(l:conn) ? l:conn : s:NewConnection(a:initialization_options)
if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open' if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open'
let l:conn.channnel = ch_open(a:address, { let l:conn.channnel = ch_open(a:address, {
@ -383,7 +384,7 @@ function! ale#lsp#Send(conn_id, message, ...) abort
" Only send the init message once. " Only send the init message once.
if !l:project.init_request_id if !l:project.init_request_id
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
\ ale#lsp#message#Initialize(l:project_root), \ ale#lsp#message#Initialize(l:project_root, l:conn.initialization_options),
\) \)
let l:project.init_request_id = l:init_id let l:project.init_request_id = l:init_id

View File

@ -24,12 +24,15 @@ function! ale#lsp#message#GetNextVersionID() abort
return l:id return l:id
endfunction endfunction
function! ale#lsp#message#Initialize(root_path) abort function! ale#lsp#message#Initialize(root_path, initialization_options) abort
" TODO: Define needed capabilities. " TODO: Define needed capabilities.
" NOTE: rootPath is deprecated in favour of rootUri
return [0, 'initialize', { return [0, 'initialize', {
\ 'processId': getpid(), \ 'processId': getpid(),
\ 'rootPath': a:root_path, \ 'rootPath': a:root_path,
\ 'capabilities': {}, \ 'capabilities': {},
\ 'initializationOptions': a:initialization_options,
\ 'rootUri': ale#path#ToURI(a:root_path),
\}] \}]
endfunction endfunction

View File

@ -156,6 +156,26 @@ g:ale_cpp_cpplint_options *g:ale_cpp_cpplint_options*
This variable can be changed to modify flags given to cpplint. This variable can be changed to modify flags given to cpplint.
===============================================================================
cquery *ale-cpp-cquery*
g:ale_cpp_cquery_executable *g:ale_cpp_cquery_executable*
*b:ale_cpp_cquery_executable*
Type: |String|
Default: `'cquery'`
This variable can be changed to use a different executable for cquery.
g:ale_cpp_cquery_cache_directory *g:ale_cpp_cquery_cache_directory*
*b:ale_cpp_cquery_cache_directory*
Type: |String|
Default: `'~/.cache/cquery'`
This variable can be changed to decide which directory cquery uses for its
cache.
=============================================================================== ===============================================================================
flawfinder *ale-cpp-flawfinder* flawfinder *ale-cpp-flawfinder*

View File

@ -44,6 +44,7 @@ CONTENTS *ale-contents*
clangtidy...........................|ale-cpp-clangtidy| clangtidy...........................|ale-cpp-clangtidy|
cppcheck............................|ale-cpp-cppcheck| cppcheck............................|ale-cpp-cppcheck|
cpplint.............................|ale-cpp-cpplint| cpplint.............................|ale-cpp-cpplint|
cquery..............................|ale-cpp-cquery|
flawfinder..........................|ale-cpp-flawfinder| flawfinder..........................|ale-cpp-flawfinder|
gcc.................................|ale-cpp-gcc| gcc.................................|ale-cpp-gcc|
c#....................................|ale-cs-options| c#....................................|ale-cs-options|
@ -319,7 +320,7 @@ Notes:
* Bash: `shell` (-n flag), `shellcheck`, `shfmt` * Bash: `shell` (-n flag), `shellcheck`, `shfmt`
* Bourne Shell: `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`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc`
* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `flawfinder`, `gcc` * C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `cquery`, `flawfinder`, `gcc`
* CUDA: `nvcc`!! * CUDA: `nvcc`!!
* C#: `mcs`, `mcsc`!! * C#: `mcs`, `mcsc`!!
* Chef: `foodcritic` * Chef: `foodcritic`
@ -2331,6 +2332,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
An optional `completion_filter` callback may be An optional `completion_filter` callback may be
defined for filtering completion results. defined for filtering completion results.
An optional `initialization_options` or
`initialization_options_callback` may be defined to
pass initialization options to the LSP.
`project_root_callback` A |String| or |Funcref| for a callback function `project_root_callback` A |String| or |Funcref| for a callback function
accepting a buffer number. A |String| should be accepting a buffer number. A |String| should be
returned representing the path to the project for the returned representing the path to the project for the
@ -2372,6 +2377,17 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
setting can make it easier to guess the linter name setting can make it easier to guess the linter name
by offering a few alternatives. by offering a few alternatives.
`initialization_options` A |Dictionary| of initialization options for LSPs.
This will be fed (as JSON) to the LSP in the
initialize command.
`initialization_options_callback`
A |String| or |Funcref| for a callback function
accepting a buffer number. A |Dictionary| should be
returned for initialization options to pass the LSP.
This can be used in place of `initialization_options`
when more complicated processing is needed.
Only one of `command`, `command_callback`, or `command_chain` should be Only one of `command`, `command_callback`, or `command_chain` should be
specified. `command_callback` is generally recommended when a command string specified. `command_callback` is generally recommended when a command string
needs to be generated dynamically, or any global options are used. needs to be generated dynamically, or any global options are used.

View File

@ -0,0 +1,48 @@
" Author: Ben Falconer <ben@falconers.me.uk>
" Description: A language server for C++
Before:
Save g:ale_cpp_cquery_executable
Save g:ale_cpp_cquery_cache_directory
unlet! g:ale_cpp_cquery_executable
unlet! b:ale_cpp_cquery_executable
unlet! g:ale_cpp_cquery_cache_directory
unlet! b:ale_cpp_cquery_cache_directory
runtime ale_linters/cpp/cquery.vim
After:
Restore
unlet! b:ale_cpp_cquery_executable
unlet! b:ale_cpp_cquery_cache_directory
call ale#linter#Reset()
Execute(The executable should be configurable):
AssertEqual 'cquery', ale_linters#cpp#cquery#GetExecutable(bufnr(''))
let b:ale_cpp_cquery_executable = 'foobar'
AssertEqual 'foobar', ale_linters#cpp#cquery#GetExecutable(bufnr(''))
Execute(The executable should be used in the command):
AssertEqual
\ ale#Escape('cquery'),
\ ale_linters#cpp#cquery#GetCommand(bufnr(''))
let b:ale_cpp_cquery_executable = 'foobar'
AssertEqual
\ ale#Escape('foobar'),
\ ale_linters#cpp#cquery#GetCommand(bufnr(''))
Execute(The cache directory should be configurable):
AssertEqual
\ {'cacheDirectory': expand('$HOME/.cache/cquery')},
\ ale_linters#cpp#cquery#GetInitializationOptions(bufnr(''))
let b:ale_cpp_cquery_cache_directory = '/foo/bar'
AssertEqual
\ {'cacheDirectory': '/foo/bar'},
\ ale_linters#cpp#cquery#GetInitializationOptions(bufnr(''))

View File

@ -16,9 +16,11 @@ Execute(ale#lsp#message#Initialize() should return correct messages):
\ 'processId': getpid(), \ 'processId': getpid(),
\ 'rootPath': '/foo/bar', \ 'rootPath': '/foo/bar',
\ 'capabilities': {}, \ 'capabilities': {},
\ 'initializationOptions': {'foo': 'bar'},
\ 'rootUri': 'file:///foo/bar',
\ } \ }
\ ], \ ],
\ ale#lsp#message#Initialize('/foo/bar') \ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'})
Execute(ale#lsp#message#Initialized() should return correct messages): Execute(ale#lsp#message#Initialized() should return correct messages):
AssertEqual [1, 'initialized'], ale#lsp#message#Initialized() AssertEqual [1, 'initialized'], ale#lsp#message#Initialized()

View File

@ -465,3 +465,28 @@ Execute(PreProcess should complain about address_callback for non-LSP linters):
AssertThrows call ale#linter#PreProcess(g:linter) AssertThrows call ale#linter#PreProcess(g:linter)
AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception
Execute(PreProcess should complain about using initialization_options and initialization_options_callback together):
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\ 'initialization_options': 'x',
\ 'initialization_options_callback': 'x',
\}
AssertThrows call ale#linter#PreProcess(g:linter)
AssertEqual 'Only one of `initialization_options` or `initialization_options_callback` should be set', g:vader_exception
Execute (PreProcess should throw when initialization_options_callback is not a callback):
AssertThrows call ale#linter#PreProcess({
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\ 'initialization_options_callback': {},
\})
AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception