Compare commits

...

3 Commits

Author SHA1 Message Date
Sofus cae5e5079f
Merge 0831043ffa into 4281228da3 2025-07-24 14:36:52 -03:00
Valtteri Koskivuori 4281228da3
fix(queries): Fix filesystem permissions for snakemake (#14061) 2025-07-24 13:09:40 -04:00
Sofus Addington 0831043ffa
Pull diagnostics 2025-07-23 10:09:07 +02:00
15 changed files with 361 additions and 4 deletions

View File

@ -268,6 +268,7 @@ pub enum LanguageServerFeature {
WorkspaceSymbols, WorkspaceSymbols,
// Symbols, use bitflags, see above? // Symbols, use bitflags, see above?
Diagnostics, Diagnostics,
PullDiagnostics,
RenameSymbol, RenameSymbol,
InlayHints, InlayHints,
DocumentColors, DocumentColors,
@ -292,6 +293,7 @@ impl Display for LanguageServerFeature {
DocumentSymbols => "document-symbols", DocumentSymbols => "document-symbols",
WorkspaceSymbols => "workspace-symbols", WorkspaceSymbols => "workspace-symbols",
Diagnostics => "diagnostics", Diagnostics => "diagnostics",
PullDiagnostics => "pull-diagnostics",
RenameSymbol => "rename-symbol", RenameSymbol => "rename-symbol",
InlayHints => "inlay-hints", InlayHints => "inlay-hints",
DocumentColors => "document-colors", DocumentColors => "document-colors",

View File

@ -372,6 +372,7 @@ impl Client {
Some(OneOf::Left(true) | OneOf::Right(_)) Some(OneOf::Left(true) | OneOf::Right(_))
), ),
LanguageServerFeature::Diagnostics => true, // there's no extra server capability LanguageServerFeature::Diagnostics => true, // there's no extra server capability
LanguageServerFeature::PullDiagnostics => capabilities.diagnostic_provider.is_some(),
LanguageServerFeature::RenameSymbol => matches!( LanguageServerFeature::RenameSymbol => matches!(
capabilities.rename_provider, capabilities.rename_provider,
Some(OneOf::Left(true)) | Some(OneOf::Right(_)) Some(OneOf::Left(true)) | Some(OneOf::Right(_))
@ -602,6 +603,9 @@ impl Client {
did_rename: Some(true), did_rename: Some(true),
..Default::default() ..Default::default()
}), }),
diagnostic: Some(lsp::DiagnosticWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
..Default::default() ..Default::default()
}), }),
text_document: Some(lsp::TextDocumentClientCapabilities { text_document: Some(lsp::TextDocumentClientCapabilities {
@ -679,6 +683,10 @@ impl Client {
}), }),
..Default::default() ..Default::default()
}), }),
diagnostic: Some(lsp::DiagnosticClientCapabilities {
dynamic_registration: Some(false),
related_document_support: Some(true),
}),
publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities { publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
version_support: Some(true), version_support: Some(true),
tag_support: Some(lsp::TagSupport { tag_support: Some(lsp::TagSupport {
@ -1229,6 +1237,32 @@ impl Client {
Some(self.call::<lsp::request::RangeFormatting>(params)) Some(self.call::<lsp::request::RangeFormatting>(params))
} }
pub fn text_document_diagnostic(
&self,
text_document: lsp::TextDocumentIdentifier,
previous_result_id: Option<String>,
) -> Option<impl Future<Output = Result<lsp::DocumentDiagnosticReportResult>>> {
let capabilities = self.capabilities();
// 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 params = lsp::DocumentDiagnosticParams {
text_document,
identifier,
previous_result_id,
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
partial_result_params: lsp::PartialResultParams::default(),
};
Some(self.call::<lsp::request::DocumentDiagnosticRequest>(params))
}
pub fn text_document_document_highlight( pub fn text_document_document_highlight(
&self, &self,
text_document: lsp::TextDocumentIdentifier, text_document: lsp::TextDocumentIdentifier,

View File

@ -463,6 +463,7 @@ pub enum MethodCall {
RegisterCapability(lsp::RegistrationParams), RegisterCapability(lsp::RegistrationParams),
UnregisterCapability(lsp::UnregistrationParams), UnregisterCapability(lsp::UnregistrationParams),
ShowDocument(lsp::ShowDocumentParams), ShowDocument(lsp::ShowDocumentParams),
WorkspaceDiagnosticRefresh,
} }
impl MethodCall { impl MethodCall {
@ -494,6 +495,7 @@ impl MethodCall {
let params: lsp::ShowDocumentParams = params.parse()?; let params: lsp::ShowDocumentParams = params.parse()?;
Self::ShowDocument(params) Self::ShowDocument(params)
} }
lsp::request::WorkspaceDiagnosticRefresh::METHOD => Self::WorkspaceDiagnosticRefresh,
_ => { _ => {
return Err(Error::Unhandled); return Err(Error::Unhandled);
} }

View File

@ -1021,6 +1021,27 @@ impl Application {
let result = self.handle_show_document(params, offset_encoding); let result = self.handle_show_document(params, offset_encoding);
Ok(json!(result)) Ok(json!(result))
} }
Ok(MethodCall::WorkspaceDiagnosticRefresh) => {
let language_server = language_server!().id();
let documents: Vec<_> = self
.editor
.documents
.values()
.filter(|x| x.supports_language_server(language_server))
.map(|x| x.id())
.collect();
for document in documents {
handlers::diagnostics::request_document_diagnostics(
&mut self.editor,
document,
false,
);
}
Ok(serde_json::Value::Null)
}
}; };
let language_server = language_server!(); let language_server = language_server!();

View File

@ -1,11 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use diagnostics::PullAllDocumentsDiagnosticHandler;
use helix_event::AsyncHook; use helix_event::AsyncHook;
use crate::config::Config; use crate::config::Config;
use crate::events; use crate::events;
use crate::handlers::auto_save::AutoSaveHandler; use crate::handlers::auto_save::AutoSaveHandler;
use crate::handlers::diagnostics::PullDiagnosticsHandler;
use crate::handlers::signature_help::SignatureHelpHandler; use crate::handlers::signature_help::SignatureHelpHandler;
pub use helix_view::handlers::{word_index, Handlers}; pub use helix_view::handlers::{word_index, Handlers};
@ -14,7 +16,7 @@ use self::document_colors::DocumentColorsHandler;
mod auto_save; mod auto_save;
pub mod completion; pub mod completion;
mod diagnostics; pub mod diagnostics;
mod document_colors; mod document_colors;
mod signature_help; mod signature_help;
mod snippet; mod snippet;
@ -27,6 +29,8 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
let auto_save = AutoSaveHandler::new().spawn(); let auto_save = AutoSaveHandler::new().spawn();
let document_colors = DocumentColorsHandler::default().spawn(); let document_colors = DocumentColorsHandler::default().spawn();
let word_index = word_index::Handler::spawn(); let word_index = word_index::Handler::spawn();
let pull_diagnostics = PullDiagnosticsHandler::new().spawn();
let pull_all_documents_diagnostics = PullAllDocumentsDiagnosticHandler::new().spawn();
let handlers = Handlers { let handlers = Handlers {
completions: helix_view::handlers::completion::CompletionHandler::new(event_tx), completions: helix_view::handlers::completion::CompletionHandler::new(event_tx),
@ -34,6 +38,8 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
auto_save, auto_save,
document_colors, document_colors,
word_index, word_index,
pull_diagnostics,
pull_all_documents_diagnostics,
}; };
helix_view::handlers::register_hooks(&handlers); helix_view::handlers::register_hooks(&handlers);

View File

@ -1,12 +1,27 @@
use helix_event::{register_hook, send_blocking}; use futures_util::stream::FuturesOrdered;
use std::collections::HashSet;
use std::time::Duration;
use tokio::time::Instant;
use tokio_stream::StreamExt;
use helix_core::diagnostic::DiagnosticProvider;
use helix_core::syntax::config::LanguageServerFeature;
use helix_core::Uri;
use helix_event::{cancelable_future, register_hook, send_blocking};
use helix_lsp::lsp;
use helix_view::document::Mode; use helix_view::document::Mode;
use helix_view::events::DiagnosticsDidChange; use helix_view::events::{
DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen, LanguageServerInitialized,
};
use helix_view::handlers::diagnostics::DiagnosticEvent; use helix_view::handlers::diagnostics::DiagnosticEvent;
use helix_view::handlers::lsp::{PullAllDocumentsDiagnosticsEvent, PullDiagnosticsEvent};
use helix_view::handlers::Handlers; use helix_view::handlers::Handlers;
use helix_view::{DocumentId, Editor};
use crate::events::OnModeSwitch; use crate::events::OnModeSwitch;
use crate::job;
pub(super) fn register_hooks(_handlers: &Handlers) { pub(super) fn register_hooks(handlers: &Handlers) {
register_hook!(move |event: &mut DiagnosticsDidChange<'_>| { register_hook!(move |event: &mut DiagnosticsDidChange<'_>| {
if event.editor.mode != Mode::Insert { if event.editor.mode != Mode::Insert {
for (view, _) in event.editor.tree.views_mut() { for (view, _) in event.editor.tree.views_mut() {
@ -21,4 +36,264 @@ pub(super) fn register_hooks(_handlers: &Handlers) {
} }
Ok(()) Ok(())
}); });
let tx = handlers.pull_diagnostics.clone();
let tx_all_documents = handlers.pull_all_documents_diagnostics.clone();
register_hook!(move |event: &mut DocumentDidChange<'_>| {
if event
.doc
.has_language_server_with_feature(LanguageServerFeature::PullDiagnostics)
&& !event.ghost_transaction
{
// Cancel the ongoing request, if present.
event.doc.pull_diagnostic_controller.cancel();
let document_id = event.doc.id();
send_blocking(&tx, PullDiagnosticsEvent { document_id });
send_blocking(&tx_all_documents, PullAllDocumentsDiagnosticsEvent {});
}
Ok(())
});
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
request_document_diagnostics(event.editor, event.doc, false);
Ok(())
});
register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
let doc_ids: Vec<_> = event.editor.documents().map(|doc| doc.id()).collect();
for doc_id in doc_ids {
request_document_diagnostics(event.editor, doc_id, false);
}
Ok(())
});
}
#[derive(Debug)]
pub(super) struct PullDiagnosticsHandler {
document_ids: HashSet<DocumentId>,
}
impl PullDiagnosticsHandler {
pub fn new() -> Self {
PullDiagnosticsHandler {
document_ids: Default::default(),
}
}
}
impl helix_event::AsyncHook for PullDiagnosticsHandler {
type Event = PullDiagnosticsEvent;
fn handle_event(
&mut self,
event: Self::Event,
_timeout: Option<tokio::time::Instant>,
) -> Option<tokio::time::Instant> {
self.document_ids.insert(event.document_id);
Some(Instant::now() + Duration::from_millis(250))
}
fn finish_debounce(&mut self) {
let document_ids = self.document_ids.clone();
job::dispatch_blocking(move |editor, _| {
for document_id in document_ids {
request_document_diagnostics(editor, document_id, false);
}
})
}
}
#[derive(Debug)]
pub(super) struct PullAllDocumentsDiagnosticHandler {}
impl PullAllDocumentsDiagnosticHandler {
pub fn new() -> Self {
PullAllDocumentsDiagnosticHandler {}
}
}
impl helix_event::AsyncHook for PullAllDocumentsDiagnosticHandler {
type Event = PullAllDocumentsDiagnosticsEvent;
fn handle_event(
&mut self,
_event: Self::Event,
_timeout: Option<tokio::time::Instant>,
) -> Option<tokio::time::Instant> {
Some(Instant::now() + Duration::from_secs(1))
}
fn finish_debounce(&mut self) {
job::dispatch_blocking(move |editor, _| {
let documents: Vec<_> = editor.documents.values().map(|doc| doc.id()).collect();
for document in documents {
request_document_diagnostics(editor, document, true);
}
})
}
}
pub fn request_document_diagnostics(
editor: &mut Editor,
doc_id: DocumentId,
only_providers_with_inter_file_dependencies: bool,
) {
let Some(doc) = editor.document_mut(doc_id) else {
return;
};
let mut seen_language_servers = HashSet::new();
let mut futures: FuturesOrdered<_> = doc
.language_servers_with_feature(LanguageServerFeature::PullDiagnostics)
.filter(|ls| seen_language_servers.insert(ls.id()))
.filter_map(|language_server| {
if only_providers_with_inter_file_dependencies
&& !language_server
.capabilities()
.diagnostic_provider
.as_ref()
.is_some_and(|diagnostic_provider| match diagnostic_provider {
lsp::DiagnosticServerCapabilities::Options(options) => {
options.inter_file_dependencies
}
lsp::DiagnosticServerCapabilities::RegistrationOptions(options) => {
options.diagnostic_options.inter_file_dependencies
}
})
{
return None;
}
let future = language_server
.text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())?;
let identifier = language_server
.capabilities()
.diagnostic_provider
.as_ref()
.and_then(|diagnostic_provider| match diagnostic_provider {
lsp::DiagnosticServerCapabilities::Options(options) => {
options.identifier.clone()
}
lsp::DiagnosticServerCapabilities::RegistrationOptions(options) => {
options.diagnostic_options.identifier.clone()
}
});
let language_server_id = language_server.id();
let provider = DiagnosticProvider::Lsp {
server_id: language_server_id,
identifier,
};
let uri = doc.uri()?;
Some(async move {
let result = future.await;
(result, provider, uri)
})
})
.collect();
if futures.is_empty() {
return;
}
job::dispatch_blocking(move |editor, _| {
let Some(doc) = editor.document_mut(doc_id) else {
return;
};
let cancel = doc.pull_diagnostic_controller.restart();
tokio::spawn(async move {
loop {
match cancelable_future(futures.next(), &cancel).await {
Some(Some(future_result)) => match future_result.0 {
Ok(result) => {
job::dispatch(move |editor, _| {
handle_pull_diagnostics_response(
editor,
result,
future_result.1,
future_result.2,
doc_id,
)
})
.await
}
Err(err) => {
let parsed_cancellation_data = if let helix_lsp::Error::Rpc(error) = err
{
error.data.and_then(|data| {
serde_json::from_value::<lsp::DiagnosticServerCancellationData>(
data,
)
.ok()
})
} else {
log::error!("Pull diagnostic request failed: {err}");
return;
};
if let Some(parsed_cancellation_data) = parsed_cancellation_data {
if parsed_cancellation_data.retrigger_request {
tokio::time::sleep(Duration::from_millis(500)).await;
job::dispatch(move |editor, _| {
request_document_diagnostics(
editor,
doc_id,
only_providers_with_inter_file_dependencies,
);
})
.await;
}
}
}
},
Some(None) => break,
// The request was cancelled.
None => return,
}
}
});
});
}
fn handle_pull_diagnostics_response(
editor: &mut Editor,
result: lsp::DocumentDiagnosticReportResult,
provider: DiagnosticProvider,
uri: Uri,
document_id: DocumentId,
) {
match result {
lsp::DocumentDiagnosticReportResult::Report(report) => {
let result_id = match report {
lsp::DocumentDiagnosticReport::Full(report) => {
editor.handle_lsp_diagnostics(
&provider,
uri,
None,
report.full_document_diagnostic_report.items,
);
report.full_document_diagnostic_report.result_id
}
lsp::DocumentDiagnosticReport::Unchanged(report) => {
Some(report.unchanged_document_diagnostic_report.result_id)
}
};
if let Some(doc) = editor.document_mut(document_id) {
doc.previous_diagnostic_id = result_id;
};
}
lsp::DocumentDiagnosticReportResult::Partial(_) => {}
};
} }

View File

@ -204,11 +204,14 @@ pub struct Document {
pub readonly: bool, pub readonly: bool,
pub previous_diagnostic_id: Option<String>,
/// Annotations for LSP document color swatches /// Annotations for LSP document color swatches
pub color_swatches: Option<DocumentColorSwatches>, pub color_swatches: Option<DocumentColorSwatches>,
// NOTE: ideally this would live on the handler for color swatches. This is blocked on a // NOTE: ideally this would live on the handler for color swatches. This is blocked on a
// large refactor that would make `&mut Editor` available on the `DocumentDidChange` event. // large refactor that would make `&mut Editor` available on the `DocumentDidChange` event.
pub color_swatch_controller: TaskController, pub color_swatch_controller: TaskController,
pub pull_diagnostic_controller: TaskController,
// NOTE: this field should eventually go away - we should use the Editor's syn_loader instead // NOTE: this field should eventually go away - we should use the Editor's syn_loader instead
// of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the // of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the
@ -728,6 +731,8 @@ impl Document {
color_swatches: None, color_swatches: None,
color_swatch_controller: TaskController::new(), color_swatch_controller: TaskController::new(),
syn_loader, syn_loader,
previous_diagnostic_id: None,
pull_diagnostic_controller: TaskController::new(),
} }
} }
@ -2284,6 +2289,10 @@ impl Document {
pub fn reset_all_inlay_hints(&mut self) { pub fn reset_all_inlay_hints(&mut self) {
self.inlay_hints = Default::default(); self.inlay_hints = Default::default();
} }
pub fn has_language_server_with_feature(&self, feature: LanguageServerFeature) -> bool {
self.language_servers_with_feature(feature).next().is_some()
}
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]

View File

@ -24,6 +24,8 @@ pub struct Handlers {
pub auto_save: Sender<AutoSaveEvent>, pub auto_save: Sender<AutoSaveEvent>,
pub document_colors: Sender<lsp::DocumentColorsEvent>, pub document_colors: Sender<lsp::DocumentColorsEvent>,
pub word_index: word_index::Handler, pub word_index: word_index::Handler,
pub pull_diagnostics: Sender<lsp::PullDiagnosticsEvent>,
pub pull_all_documents_diagnostics: Sender<lsp::PullAllDocumentsDiagnosticsEvent>,
} }
impl Handlers { impl Handlers {

View File

@ -30,6 +30,12 @@ pub enum SignatureHelpEvent {
RequestComplete { open: bool }, RequestComplete { open: bool },
} }
pub struct PullDiagnosticsEvent {
pub document_id: DocumentId,
}
pub struct PullAllDocumentsDiagnosticsEvent {}
#[derive(Debug)] #[derive(Debug)]
pub struct ApplyEditError { pub struct ApplyEditError {
pub kind: ApplyEditErrorKind, pub kind: ApplyEditErrorKind,

0
runtime/queries/snakemake/LICENSE 100755 → 100644
View File

View File

View File

View File

View File

View File