diff --git a/ale_linters/markdown/harper.vim b/ale_linters/markdown/harper.vim new file mode 100644 index 00000000..98e902b3 --- /dev/null +++ b/ale_linters/markdown/harper.vim @@ -0,0 +1,32 @@ +" Author: Armand Halbert +" Description: Harper for Markdown files + +call ale#Set('markdown_harper_config', { +\ 'harper-ls': { +\ 'diagnosticSeverity': 'hint', +\ 'dialect': 'American', +\ 'linters': { +\ 'SpellCheck': v:true, +\ 'SentenceCapitalization': v:true, +\ 'RepeatedWords': v:true, +\ 'LongSentences': v:true, +\ 'AnA': v:true, +\ 'Spaces': v:true, +\ 'SpelledNumbers': v:false, +\ 'WrongQuotes': v:false, +\ }, +\ }, +\}) + +call ale#linter#Define('markdown', { +\ 'name': 'harper', +\ 'lsp': 'stdio', +\ 'executable': 'harper-ls', +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#markdown#harper#GetProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'markdown_harper_config')}, +\}) + +function! ale_linters#markdown#harper#GetProjectRoot(buffer) abort + return fnamemodify(bufname(a:buffer), ':p:h') +endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index b0b54c8b..f49f3ae0 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -329,6 +329,27 @@ function! ale#lsp#UpdateConfig(conn_id, buffer, config) abort return 1 endfunction +function! ale#lsp#GetConnectionConfig(conn_id) abort + let l:conn = get(s:connections, a:conn_id, {}) + + return get(l:conn, 'config', {}) +endfunction + +" Send a JSON-RPC response to a server-initiated request (e.g. workspace/configuration). +" Unlike ale#lsp#Send, which builds outgoing requests/notifications with a 'method' field, +" this sends a response with 'id' + 'result' fields to reply to a request the server sent us. +function! ale#lsp#SendResponse(conn_id, id, result) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return + endif + + let l:body = json_encode({'jsonrpc': '2.0', 'id': a:id, 'result': a:result}) + let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body + call s:SendMessageData(l:conn, l:data) +endfunction + function! ale#lsp#CallInitCallbacks(conn_id) abort let l:conn = get(s:connections, a:conn_id, {}) diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 3b3c403c..fae9f709 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -241,6 +241,10 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort \ : a:response.result.items call ale#lsp_linter#HandleLSPDiagnostics(a:conn_id, l:uri, l:diagnostics) + elseif l:method is# 'workspace/configuration' + let l:items = get(get(a:response, 'params', {}), 'items', []) + let l:config = ale#lsp#GetConnectionConfig(a:conn_id) + call ale#lsp#SendResponse(a:conn_id, a:response.id, map(copy(l:items), 'l:config')) elseif l:method is# 'window/showMessage' call ale#lsp_window#HandleShowMessage( \ s:lsp_linter_map[a:conn_id].name, diff --git a/doc/ale-markdown.txt b/doc/ale-markdown.txt index a309f138..7ee577e1 100644 --- a/doc/ale-markdown.txt +++ b/doc/ale-markdown.txt @@ -14,6 +14,35 @@ dprint *ale-markdown-dprint* See |ale-dprint-options| and https://dprint.dev/plugins/markdown +=============================================================================== +harper *ale-markdown-harper* + + *ale-options.markdown_harper_config* + *g:ale_markdown_harper_config* + *b:ale_markdown_harper_config* +markdown_harper_config +g:ale_markdown_harper_config + Type: |Dictionary| + Default: `{'harper-ls': {'diagnosticSeverity': 'hint', 'dialect': 'American', ...}}` + + Dictionary passed to harper-ls as LSP workspace configuration. The default + enables spell check, sentence capitalization, repeated words, long + sentences, a/an errors, and spacing rules, and disables spelled-out numbers + and wrong-quote checks. + + Example: > + let g:ale_markdown_harper_config = { + \ 'harper-ls': { + \ 'diagnosticSeverity': 'warning', + \ 'linters': { + \ 'SpellCheck': v:true, + \ 'LongSentences': v:false, + \ }, + \ }, + \} +< + + =============================================================================== markdownlint *ale-markdown-markdownlint* diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index 8cb95b56..fdd8f24d 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -404,6 +404,7 @@ Notes: * Markdown * `alex` * `cspell` + * `harper` * `languagetool`!! * `markdownlint`!! * `marksman` diff --git a/doc/ale.txt b/doc/ale.txt index 380914bc..e0192177 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -3738,6 +3738,7 @@ documented in additional help files. markdown................................|ale-markdown-options| cspell................................|ale-markdown-cspell| dprint................................|ale-markdown-dprint| + harper................................|ale-markdown-harper| markdownlint..........................|ale-markdown-markdownlint| marksman..............................|ale-markdown-marksman| mdl...................................|ale-markdown-mdl| diff --git a/supported-tools.md b/supported-tools.md index a038acbd..8988fa72 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -414,6 +414,7 @@ formatting. * Markdown * [alex](https://github.com/get-alex/alex) * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [harper](https://github.com/elijah-potter/harper) :speech_balloon: * [languagetool](https://languagetool.org/) :floppy_disk: :speech_balloon: * [markdownlint](https://github.com/DavidAnson/markdownlint) :floppy_disk: * [marksman](https://github.com/artempyanykh/marksman) :speech_balloon: diff --git a/test/linter/test_markdown_harper.vader b/test/linter/test_markdown_harper.vader new file mode 100644 index 00000000..92c1c1b3 --- /dev/null +++ b/test/linter/test_markdown_harper.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'harper') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'harper-ls', ale#Escape('harper-ls') . ' --stdio' + +Execute(Should accept configuration settings): + AssertLSPConfig g:ale_markdown_harper_config + + let b:ale_markdown_harper_config = {'harper-ls': {'diagnosticSeverity': 'warning'}} + AssertLSPConfig {'harper-ls': {'diagnosticSeverity': 'warning'}} diff --git a/test/lsp/test_engine_lsp_response_handling.vader b/test/lsp/test_engine_lsp_response_handling.vader index 7b18befc..153be2b0 100644 --- a/test/lsp/test_engine_lsp_response_handling.vader +++ b/test/lsp/test_engine_lsp_response_handling.vader @@ -534,6 +534,34 @@ Execute(LSP pull model diagnostic responses that are 'unchanged' should be handl \ g:ale_buffer_info[bufnr('')].loclist AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list +Execute(workspace/configuration requests should be answered with the connection config): + let g:sent_responses = [] + + function! ale#lsp#GetConnectionConfig(conn_id) abort + return {'foo': 'bar'} + endfunction + + function! ale#lsp#SendResponse(conn_id, id, result) abort + call add(g:sent_responses, [a:conn_id, a:id, a:result]) + endfunction + + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'pylsp', 'aliases': [], 'lsp': 'stdio'}}) + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'jsonrpc': '2.0', + \ 'id': 7, + \ 'method': 'workspace/configuration', + \ 'params': { + \ 'items': [{'section': 'foo'}, {'section': 'bar'}], + \ }, + \}) + + AssertEqual + \ [[1, 7, [{'foo': 'bar'}, {'foo': 'bar'}]]], + \ g:sent_responses + + unlet! g:sent_responses + runtime autoload/ale/lsp.vim + Execute(LSP errors should be logged in the history): call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'foobar', 'aliases': [], 'lsp': 'stdio'}}) call ale#lsp_linter#HandleLSPResponse(347, { diff --git a/test/lsp/test_update_config.vader b/test/lsp/test_update_config.vader index 2a2c85e6..75a9a37a 100644 --- a/test/lsp/test_update_config.vader +++ b/test/lsp/test_update_config.vader @@ -1,16 +1,26 @@ Before: runtime autoload/ale/lsp.vim + runtime autoload/ale/job.vim let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {}) + let g:sent_data = [] - " Stub out this function, so we test updating configs. + " Stub out these functions to capture calls without side effects. function! ale#lsp#Send(conn_id, message) abort endfunction + function! ale#job#SendRaw(job_id, data) abort + call add(g:sent_data, a:data) + endfunction + After: Restore unlet! g:conn_id + unlet! g:conn + unlet! g:sent_data + unlet! g:remainder + unlet! g:messages runtime autoload/ale/lsp.vim @@ -19,3 +29,27 @@ Execute(Only send updates when the configuration dictionary changes): AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1}) AssertEqual 0, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1}) AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {}) + +Execute(ale#lsp#GetConnectionConfig() should return empty dict for unknown connections): + AssertEqual {}, ale#lsp#GetConnectionConfig('unknown:conn') + +Execute(ale#lsp#GetConnectionConfig() should return the current connection config): + call ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'foo': 'bar'}) + AssertEqual {'foo': 'bar'}, ale#lsp#GetConnectionConfig(g:conn_id) + +Execute(ale#lsp#SendResponse() should do nothing for unknown connections): + " Should not throw + call ale#lsp#SendResponse('unknown:conn', 1, []) + AssertEqual [], g:sent_data + +Execute(ale#lsp#SendResponse() should send a JSON-RPC response message): + " Give the connection a job_id so s:SendMessageData routes to ale#job#SendRaw + let g:conn = ale#lsp#GetConnections()[g:conn_id] + let g:conn.job_id = 1 + + call ale#lsp#SendResponse(g:conn_id, 42, ['result_value']) + + AssertEqual 1, len(g:sent_data) + let [g:remainder, g:messages] = ale#lsp#ReadMessageData(g:sent_data[0]) + AssertEqual '', g:remainder + AssertEqual [{'jsonrpc': '2.0', 'id': 42, 'result': ['result_value']}], g:messages