Skip to content

Support textDocument/diagnostic specification (Pull diagnostics) #11315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions helix-core/src/syntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ pub enum LanguageServerFeature {
WorkspaceSymbols,
// Symbols, use bitflags, see above?
Diagnostics,
PullDiagnostics,
RenameSymbol,
InlayHints,
DocumentColors,
Expand All @@ -289,6 +290,7 @@ impl Display for LanguageServerFeature {
DocumentSymbols => "document-symbols",
WorkspaceSymbols => "workspace-symbols",
Diagnostics => "diagnostics",
PullDiagnostics => "pull-diagnostics",
RenameSymbol => "rename-symbol",
InlayHints => "inlay-hints",
DocumentColors => "document-colors",
Expand Down
79 changes: 79 additions & 0 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub struct Client {
initialize_notify: Arc<Notify>,
/// workspace folders added while the server is still initializing
req_timeout: u64,
ongoing_work: Mutex<Vec<(lsp::TextDocumentIdentifier, lsp::ProgressToken)>>,
}

impl Client {
Expand Down Expand Up @@ -258,6 +259,7 @@ impl Client {
root_uri,
workspace_folders: Mutex::new(workspace_folders),
initialize_notify: initialize_notify.clone(),
ongoing_work: Mutex::new(Default::default()),
};

Ok((client, server_rx, initialize_notify))
Expand Down Expand Up @@ -372,6 +374,7 @@ impl Client {
Some(OneOf::Left(true) | OneOf::Right(_))
),
LanguageServerFeature::Diagnostics => true, // there's no extra server capability
LanguageServerFeature::PullDiagnostics => capabilities.diagnostic_provider.is_some(),
LanguageServerFeature::RenameSymbol => matches!(
capabilities.rename_provider,
Some(OneOf::Left(true)) | Some(OneOf::Right(_))
Expand Down Expand Up @@ -602,6 +605,9 @@ impl Client {
did_rename: Some(true),
..Default::default()
}),
diagnostic: Some(lsp::DiagnosticWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
..Default::default()
}),
text_document: Some(lsp::TextDocumentClientCapabilities {
Expand Down Expand Up @@ -679,6 +685,10 @@ impl Client {
}),
..Default::default()
}),
diagnostic: Some(lsp::DiagnosticClientCapabilities {
dynamic_registration: Some(false),
related_document_support: Some(true),
}),
publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
version_support: Some(true),
tag_support: Some(lsp::TagSupport {
Expand Down Expand Up @@ -1229,6 +1239,75 @@ impl Client {
Some(self.call::<lsp::request::RangeFormatting>(params))
}

pub fn mark_work_as_done(&self, id: lsp::ProgressToken) {
self.ongoing_work.lock().retain(|x| x.1 != id);
}

fn cancel_ongoing_work(&self, id: lsp::ProgressToken) {
self.notify::<lsp::notification::Cancel>(lsp::CancelParams { id: id.clone() });
self.mark_work_as_done(id);
}

pub fn text_document_diagnostic(
&self,
text_document: lsp::TextDocumentIdentifier,
previous_result_id: Option<String>,
) -> Option<(
impl Future<Output = Result<lsp::DocumentDiagnosticReportResult>>,
lsp::ProgressToken,
)> {
let capabilities = self.capabilities();

let ongoing_work = {
let ongoing_work_lock = self.ongoing_work.lock();

ongoing_work_lock
.iter()
.filter(|x| x.0 == text_document)
.cloned()
.collect::<Vec<_>>()
};

if !ongoing_work.is_empty() {
for id in ongoing_work.into_iter().map(|x| x.1) {
self.cancel_ongoing_work(id.clone());
}
}

// Return early if the server does not support pull diagnostic.
let identifier = match capabilities.diagnostic_provider.as_ref()? {
lsp::DiagnosticServerCapabilities::Options(cap) => cap.identifier.clone(),
lsp::DiagnosticServerCapabilities::RegistrationOptions(cap) => {
cap.diagnostic_options.identifier.clone()
}
};

let request_id = match self.next_request_id() {
jsonrpc::Id::Null => lsp::ProgressToken::Number(1),
jsonrpc::Id::Num(num) => lsp::ProgressToken::Number(num as i32),
jsonrpc::Id::Str(str) => lsp::ProgressToken::String(str),
};

let params = lsp::DocumentDiagnosticParams {
text_document: text_document.clone(),
identifier,
previous_result_id,
work_done_progress_params: lsp::WorkDoneProgressParams {
work_done_token: Some(request_id.clone()),
},
partial_result_params: lsp::PartialResultParams::default(),
};

self.ongoing_work
.lock()
.push((text_document, request_id.clone()));

Some((
self.call::<lsp::request::DocumentDiagnosticRequest>(params),
request_id,
))
}

pub fn text_document_document_highlight(
&self,
text_document: lsp::TextDocumentIdentifier,
Expand Down
2 changes: 2 additions & 0 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ pub enum MethodCall {
RegisterCapability(lsp::RegistrationParams),
UnregisterCapability(lsp::UnregistrationParams),
ShowDocument(lsp::ShowDocumentParams),
WorkspaceDiagnosticRefresh,
}

impl MethodCall {
Expand Down Expand Up @@ -494,6 +495,7 @@ impl MethodCall {
let params: lsp::ShowDocumentParams = params.parse()?;
Self::ShowDocument(params)
}
lsp::request::WorkspaceDiagnosticRefresh::METHOD => Self::WorkspaceDiagnosticRefresh,
_ => {
return Err(Error::Unhandled);
}
Expand Down
15 changes: 15 additions & 0 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,21 @@ impl Application {
let result = self.handle_show_document(params, offset_encoding);
Ok(json!(result))
}
Ok(MethodCall::WorkspaceDiagnosticRefresh) => {
for document in self.editor.documents() {
let language_server = language_server!();
if language_server.supports_feature(
syntax::config::LanguageServerFeature::PullDiagnostics,
) && document.supports_language_server(language_server.id())
{
handlers::diagnostics::pull_diagnostics_for_document(
document,
language_server,
);
}
}
Ok(serde_json::Value::Null)
}
};

let language_server = language_server!();
Expand Down
8 changes: 7 additions & 1 deletion helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::sync::Arc;

use arc_swap::ArcSwap;
use diagnostics::PullAllDocumentsDiagnosticHandler;
use helix_event::AsyncHook;

use crate::config::Config;
use crate::events;
use crate::handlers::auto_save::AutoSaveHandler;
use crate::handlers::diagnostics::PullDiagnosticsHandler;
use crate::handlers::signature_help::SignatureHelpHandler;

pub use helix_view::handlers::Handlers;
Expand All @@ -14,7 +16,7 @@ use self::document_colors::DocumentColorsHandler;

mod auto_save;
pub mod completion;
mod diagnostics;
pub mod diagnostics;
mod document_colors;
mod signature_help;
mod snippet;
Expand All @@ -26,12 +28,16 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
let signature_hints = SignatureHelpHandler::new().spawn();
let auto_save = AutoSaveHandler::new().spawn();
let document_colors = DocumentColorsHandler::default().spawn();
let pull_diagnostics = PullDiagnosticsHandler::new().spawn();
let pull_all_documents_diagnostics = PullAllDocumentsDiagnosticHandler::new().spawn();

let handlers = Handlers {
completions: helix_view::handlers::completion::CompletionHandler::new(event_tx),
signature_hints,
auto_save,
document_colors,
pull_diagnostics,
pull_all_documents_diagnostics,
};

helix_view::handlers::register_hooks(&handlers);
Expand Down
Loading