--- /dev/null
+" DiagnosticSeverity
+let s:ERROR = 1
+let s:WARN = 2
+let s:INFO = 3
+let s:HINT = 4
+
+let s:Dispose = v:null
+
+function! s:severity_threshold() abort
+ let s = g:lsp_ale_diagnostics_severity
+ if s ==? 'error'
+ return s:ERROR
+ elseif s ==? 'warning' || s ==? 'warn'
+ return s:WARN
+ elseif s ==? 'information' || s ==? 'info'
+ return s:INFO
+ elseif s ==? 'hint'
+ return s:HINT
+ else
+ throw 'vim-lsp-ale: Unexpected severity "' . s . '". Severity must be one of "error", "warning", "information", "hint"'
+ endif
+endfunction
+
+function! s:get_loc_type(severity) abort
+ if a:severity == s:ERROR
+ return 'E'
+ elseif a:severity == s:WARN
+ return 'W'
+ elseif a:severity == s:INFO
+ return 'I'
+ elseif a:severity == s:HINT
+ return 'H'
+ else
+ throw 'vim-lsp-ale: Unexpected severity: ' . a:severity
+ endif
+endfunction
+
+let s:prev_num_diags = {}
+function! lsp#ale#_reset_prev_num_diags() abort
+ let s:prev_num_diags = {}
+endfunction
+
+function! s:can_skip_diags(server, uri, diags) abort
+ if !has_key(s:prev_num_diags, a:server)
+ let s:prev_num_diags[a:server] = {}
+ endif
+ let prev = s:prev_num_diags[a:server]
+
+ let num_diags = len(a:diags)
+ if num_diags == 0 && get(prev, a:uri, -1) == 0
+ " Some language servers send diagnostics notifications even if the
+ " results are not changed from previous. It's hard to check the
+ " notifications are perfectly the same as previous. Here only checks
+ " emptiness and skip if both previous ones and current ones are
+ " empty.
+ " I believe programmers usually try to keep no lint errors in the
+ " source code they are writing :)
+ return v:true
+ endif
+
+ let prev[a:uri] = num_diags
+ return v:false
+endfunction
+
+function! s:can_skip_all_diags(uri, all_diags) abort
+ for [server, diags] in items(a:all_diags)
+ if !s:can_skip_diags(server, a:uri, diags.params.diagnostics)
+ return v:false
+ endif
+ endfor
+ return v:true
+endfunction
+
+function! s:is_active_linter() abort
+ if g:lsp_ale_auto_enable_linter
+ return v:true
+ endif
+ let active_linters = get(b:, 'ale_linters', get(g:ale_linters, &filetype, []))
+ return index(active_linters, 'vim-lsp') >= 0
+endf
+
+function! lsp#ale#on_ale_want_results(bufnr) abort
+ " Note: Checking lsp#internal#diagnostics#state#_is_enabled_for_buffer here. If previous lint
+ " errors remain in a buffer, they won't be updated when vim-lsp is disabled for the buffer.
+ if s:Dispose is v:null || !lsp#internal#diagnostics#state#_is_enabled_for_buffer(a:bufnr)
+ return
+ endif
+
+ let uri = lsp#utils#get_buffer_uri(a:bufnr)
+ let all_diags = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(uri)
+ if empty(all_diags) || s:can_skip_all_diags(uri, all_diags)
+ " Do nothing when no diagnostics results
+ return
+ endif
+
+ if s:is_active_linter()
+ call ale#other_source#StartChecking(a:bufnr, 'vim-lsp')
+ " Avoid the issue that sign and highlight are not set
+ " https://github.com/dense-analysis/ale/issues/3690
+ call timer_start(0, {-> s:notify_diag_to_ale(a:bufnr, all_diags) })
+ endif
+endfunction
+
+function! s:notify_diag_to_ale(bufnr, diags) abort
+ try
+ let threshold = s:severity_threshold()
+ let results = []
+ for [server, diag] in items(a:diags)
+ " Note: Do not filter `diag` destructively since the object is also used by vim-lsp
+ let locs = lsp#ui#vim#utils#diagnostics_to_loc_list({'response': diag})
+ let idx = 0
+ for loc in locs
+ let severity = get(diag.params.diagnostics[idx], 'severity', s:ERROR)
+ if severity > threshold
+ continue
+ endif
+ let loc.text = '[' . server . '] ' . loc.text
+ let loc.type = s:get_loc_type(severity)
+ let results += [loc]
+ let idx += 1
+ endfor
+ endfor
+ catch
+ " Since ale#other_source#StartChecking() was already called, ale#other_source#ShowResults()
+ " needs to be called to notify ALE that checking was done.
+ call ale#other_source#ShowResults(a:bufnr, 'vim-lsp', [])
+ let msg = v:exception . ' at ' . v:throwpoint
+ if msg !~# '^vim-lsp-ale: '
+ " Avoid E608 on rethrowing exceptions from Vim script runtime
+ let msg = 'vim-lsp-ale: Error while notifying results to ALE: ' . msg
+ endif
+ throw msg
+ endtry
+ call ale#other_source#ShowResults(a:bufnr, 'vim-lsp', results)
+endfunction
+
+function! s:notify_diag_to_ale_for_buf(bufnr) abort
+ if !s:is_active_linter()
+ return
+ endif
+
+ let uri = lsp#utils#get_buffer_uri(a:bufnr)
+ let diags = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(uri)
+ call s:notify_diag_to_ale(a:bufnr, diags)
+endfunction
+
+function! s:on_diagnostics(res) abort
+ let uri = a:res.response.params.uri
+ if s:can_skip_diags(a:res.server, uri, a:res.response.params.diagnostics)
+ return
+ endif
+
+ let path = lsp#utils#uri_to_path(uri)
+ let bufnr = bufnr('^' . path . '$')
+ if bufnr == -1
+ " This branch is reachable when vim-lsp receives some notifications
+ " but the buffer for them was already deleted. This can happen since
+ " notifications are asynchronous
+ return
+ endif
+
+ call ale#other_source#StartChecking(bufnr, 'vim-lsp')
+ " Use timer_start to ensure calling s:notify_diag_to_ale after all
+ " subscribers handled the publishDiagnostics event.
+ " lsp_setup is hooked before vim-lsp sets various internal hooks. So this
+ " function is called before the response is not handled by vim-lsp yet.
+ call timer_start(0, {-> s:notify_diag_to_ale_for_buf(bufnr) })
+endfunction
+
+function! s:is_diagnostics_response(item) abort
+ if !has_key(a:item, 'server') || !has_key(a:item, 'response')
+ return v:false
+ endif
+ let res = a:item.response
+ if !has_key(res, 'method')
+ return v:false
+ endif
+ return res.method ==# 'textDocument/publishDiagnostics'
+endfunction
+
+function! lsp#ale#enable() abort
+ if s:Dispose isnot v:null
+ return
+ endif
+
+ let s:Dispose = lsp#callbag#pipe(
+ \ lsp#stream(),
+ \ lsp#callbag#filter(funcref('s:is_diagnostics_response')),
+ \ lsp#callbag#subscribe({ 'next': funcref('s:on_diagnostics') }),
+ \ )
+endfunction
+
+function! lsp#ale#disable() abort
+ if s:Dispose is v:null
+ return
+ endif
+ call s:Dispose()
+ let s:Dispose = v:null
+endfunction
+
+function! lsp#ale#enabled() abort
+ return s:Dispose isnot v:null
+endfunction