When working with Dart LSP, it populates all diagnostics on startup. However, with the current lspce behavior, flymake only shows diagnostics per open buffer — meaning you either pay the overhead of opening the whole project in buffers, or you query the Rust server directly for the current project diagnostics state.
As I understand it, flymake is philosophically buffer-based, so there's no way around this limitation from that side.
Digging around, I found that the current lspce Rust implementation already collects all diagnostics in a per-project internal state. So the idea is to extend the client with a public API to query that state and return all diagnostics for a specified project. Users could then use familiar tools to build a UI for conveniently navigating this state.
I've implemented this extension along with a consult-based UI presenter.
I'm interested in your opinion, do you see this extension as a desirable way to provide such functionality?
extension patch:
diff --git a/src/lib.rs b/src/lib.rs
index 8ccf1cb..d92f3d7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -978,6 +978,31 @@ fn read_file_diagnostics(
Ok(None)
}
+#[defun]
+fn read_all_diagnostics(env: &Env, root_uri: String, file_type: String) -> Result<Option<String>> {
+ let projects = projects().lock().unwrap();
+ if let Some(p) = projects.get(&root_uri) {
+ if let Some(server) = p.servers.get(&file_type) {
+ let server_data = server.server_data.lock().unwrap();
+ let map: HashMap<&String, &Vec<lsp_types::Diagnostic>> = server_data
+ .file_infos
+ .iter()
+ .filter(|(_, fi)| !fi.diagnostics.is_empty())
+ .map(|(uri, fi)| (uri, &fi.diagnostics))
+ .collect();
+ if map.is_empty() {
+ return Ok(None);
+ }
+ return Ok(Some(serde_json::to_string(&map)?));
+ } else {
+ env.message(&format!("No server for {}", &file_type));
+ }
+ } else {
+ env.message(&format!("No project for {} {}", &root_uri, &file_type));
+ }
+ Ok(None)
+}
+
#[defun]
fn read_latest_response_id(
env: &Env,
as an example, diagnostics viewer based on consult:
Details
;;; lspce-consult-diagnostics.el --- consult picker for lspce project diagnostics
(require 'consult)
(defface lspce-diag-error-face
'((t :foreground "#ff5f5f" :weight bold))
"Face for error diagnostics.")
(defface lspce-diag-warn-face
'((t :foreground "#e5c07b" :weight bold))
"Face for warning diagnostics.")
(defface lspce-diag-info-face
'((t :foreground "#61afef"))
"Face for info diagnostics.")
(defface lspce-diag-hint-face
'((t :foreground "#98c379"))
"Face for hint diagnostics.")
(defun lspce--diag-severity-label (sev)
(pcase sev
(1 (propertize "ERR " 'face 'lspce-diag-error-face))
(2 (propertize "WARN" 'face 'lspce-diag-warn-face))
(3 (propertize "INFO" 'face 'lspce-diag-info-face))
(_ (propertize "HINT" 'face 'lspce-diag-hint-face))))
(defun lspce--diag-severity-order (candidate)
(pcase (get-text-property 0 'lspce-severity candidate)
(1 0) (2 1) (3 2) (_ 3)))
(defun lspce--diag-candidates ()
"Build a flat candidate list from project diagnostics.
Each string carries text properties: lspce-uri, lspce-line, lspce-col, lspce-severity."
(let ((table (lspce-get-project-diagnostics))
candidates)
(when table
(maphash
(lambda (uri diags)
(seq-do
(lambda (d)
(let* ((range (gethash "range" d))
(start (gethash "start" range))
(line (gethash "line" start))
(col (gethash "character" start))
(severity (gethash "severity" d))
(msg (gethash "message" d))
(path (string-remove-prefix "file://" uri))
(display-path
(if (and lspce--root-uri
(string-prefix-p
(string-remove-prefix "file://" lspce--root-uri)
path))
(file-relative-name
path
(string-remove-prefix "file://" lspce--root-uri))
path))
(candidate
(format "%s %-40s %4d:%-4d %s"
(lspce--diag-severity-label severity)
(propertize display-path 'face 'font-lock-keyword-face)
(1+ line)
(1+ col)
(propertize msg 'face 'font-lock-comment-face))))
(add-text-properties 0 1
(list 'lspce-uri uri
'lspce-line line
'lspce-col col
'lspce-severity severity)
candidate)
(push candidate candidates)))
diags))
table))
;; Sort: errors first, then warn, info, hint
(sort (nreverse candidates)
(lambda (a b)
(< (lspce--diag-severity-order a)
(lspce--diag-severity-order b))))))
(defun lspce--diag-jump (candidate)
"Open file and jump to the location encoded in CANDIDATE."
(when candidate
(let* ((uri (get-text-property 0 'lspce-uri candidate))
(line (get-text-property 0 'lspce-line candidate))
(col (get-text-property 0 'lspce-col candidate))
(path (string-remove-prefix "file://" uri)))
(find-file path)
(goto-char (point-min))
(forward-line line)
(move-to-column col))))
;;;###autoload
(defun lspce-consult-diagnostics ()
"Browse project-wide LSP diagnostics with consult.
Errors are shown first, then warnings, info, hints.
Selecting a candidate opens the file at the diagnostic location."
(interactive)
(unless lspce-mode
(user-error "lspce-mode is not active in this buffer"))
(let ((candidates (lspce--diag-candidates)))
(unless candidates
(user-error "lspce: no project diagnostics"))
(let ((selected
(consult--read
candidates
:prompt "LSP diagnostics: "
:require-match t
:sort nil
:category 'lspce-diagnostic
:lookup (lambda (selected candidates _input _narrow)
(car (member selected candidates)))
:state (lambda (action candidate)
(when (and candidate (eq action 'preview))
(lspce--diag-jump candidate))))))
(lspce--diag-jump selected))))
(provide 'lspce-consult-diagnostics)
When working with Dart LSP, it populates all diagnostics on startup. However, with the current lspce behavior, flymake only shows diagnostics per open buffer — meaning you either pay the overhead of opening the whole project in buffers, or you query the Rust server directly for the current project diagnostics state.
As I understand it, flymake is philosophically buffer-based, so there's no way around this limitation from that side.
Digging around, I found that the current lspce Rust implementation already collects all diagnostics in a per-project internal state. So the idea is to extend the client with a public API to query that state and return all diagnostics for a specified project. Users could then use familiar tools to build a UI for conveniently navigating this state.
I've implemented this extension along with a consult-based UI presenter.
I'm interested in your opinion, do you see this extension as a desirable way to provide such functionality?
extension patch:
as an example, diagnostics viewer based on consult:
Details