mirror of https://github.com/helix-editor/helix
Merge 43cbbcd586
into 362e97e927
commit
f996fbe2da
|
@ -265,6 +265,7 @@ pub enum LanguageServerFeature {
|
|||
WorkspaceSymbols,
|
||||
// Symbols, use bitflags, see above?
|
||||
Diagnostics,
|
||||
PullDiagnostics,
|
||||
RenameSymbol,
|
||||
InlayHints,
|
||||
DocumentColors,
|
||||
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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(_))
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
|
|
@ -463,6 +463,7 @@ pub enum MethodCall {
|
|||
RegisterCapability(lsp::RegistrationParams),
|
||||
UnregisterCapability(lsp::UnregistrationParams),
|
||||
ShowDocument(lsp::ShowDocumentParams),
|
||||
WorkspaceDiagnosticRefresh,
|
||||
}
|
||||
|
||||
impl MethodCall {
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1009,6 +1009,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!();
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use helix_core::diagnostic::DiagnosticProvider;
|
||||
use helix_core::syntax::config::LanguageServerFeature;
|
||||
use helix_core::Uri;
|
||||
use helix_event::{register_hook, send_blocking};
|
||||
use helix_lsp::lsp;
|
||||
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::lsp::{PullAllDocumentsDiagnosticsEvent, PullDiagnosticsEvent};
|
||||
use helix_view::handlers::Handlers;
|
||||
use helix_view::{DocumentId, Editor};
|
||||
|
||||
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<'_>| {
|
||||
if event.editor.mode != Mode::Insert {
|
||||
for (view, _) in event.editor.tree.views_mut() {
|
||||
|
@ -21,4 +34,249 @@ pub(super) fn register_hooks(_handlers: &Handlers) {
|
|||
}
|
||||
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)
|
||||
{
|
||||
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<'_>| {
|
||||
let doc = doc!(event.editor, &event.doc);
|
||||
for language_server in
|
||||
doc.language_servers_with_feature(LanguageServerFeature::PullDiagnostics)
|
||||
{
|
||||
pull_diagnostics_for_document(doc, language_server);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
|
||||
let language_server = event.editor.language_server_by_id(event.server_id).unwrap();
|
||||
if language_server.supports_feature(LanguageServerFeature::PullDiagnostics) {
|
||||
for doc in event
|
||||
.editor
|
||||
.documents()
|
||||
.filter(|doc| doc.supports_language_server(event.server_id))
|
||||
{
|
||||
pull_diagnostics_for_document(doc, language_server);
|
||||
}
|
||||
}
|
||||
|
||||
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(125))
|
||||
}
|
||||
|
||||
fn finish_debounce(&mut self) {
|
||||
let document_ids = self.document_ids.clone();
|
||||
job::dispatch_blocking(move |editor, _| {
|
||||
for document_id in document_ids {
|
||||
let document = editor.document(document_id);
|
||||
|
||||
let Some(document) = document else {
|
||||
return;
|
||||
};
|
||||
|
||||
let language_servers = document
|
||||
.language_servers_with_feature(LanguageServerFeature::PullDiagnostics)
|
||||
.filter(|ls| ls.is_initialized());
|
||||
|
||||
for language_server in language_servers {
|
||||
pull_diagnostics_for_document(document, language_server);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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_millis(500))
|
||||
}
|
||||
|
||||
fn finish_debounce(&mut self) {
|
||||
job::dispatch_blocking(move |editor, _| {
|
||||
for document in editor.documents.values() {
|
||||
let language_servers = document
|
||||
.language_servers_with_feature(LanguageServerFeature::PullDiagnostics)
|
||||
.filter(|ls| ls.is_initialized())
|
||||
.filter(|ls| {
|
||||
ls.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
|
||||
}
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
for language_server in language_servers {
|
||||
pull_diagnostics_for_document(document, language_server);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pull_diagnostics_for_document(
|
||||
doc: &helix_view::Document,
|
||||
language_server: &helix_lsp::Client,
|
||||
) {
|
||||
let Some(future) = language_server
|
||||
.text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(uri) = doc.uri() else {
|
||||
return;
|
||||
};
|
||||
|
||||
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 document_id = doc.id();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match future.0.await {
|
||||
Ok(result) => {
|
||||
job::dispatch(move |editor, _| {
|
||||
if let Some(language_server) = editor.language_server_by_id(language_server_id)
|
||||
{
|
||||
language_server.mark_work_as_done(future.1);
|
||||
};
|
||||
|
||||
handle_pull_diagnostics_response(editor, result, provider, uri, document_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, _| {
|
||||
if let (Some(doc), Some(language_server)) = (
|
||||
editor.document(document_id),
|
||||
editor.language_server_by_id(language_server_id),
|
||||
) {
|
||||
language_server.mark_work_as_done(future.1);
|
||||
if doc.supports_language_server(language_server_id) {
|
||||
pull_diagnostics_for_document(doc, language_server);
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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(_) => {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -204,6 +204,8 @@ pub struct Document {
|
|||
|
||||
pub readonly: bool,
|
||||
|
||||
pub previous_diagnostic_id: Option<String>,
|
||||
|
||||
/// Annotations for LSP document color swatches
|
||||
pub color_swatches: Option<DocumentColorSwatches>,
|
||||
// NOTE: ideally this would live on the handler for color swatches. This is blocked on a
|
||||
|
@ -728,6 +730,7 @@ impl Document {
|
|||
color_swatches: None,
|
||||
color_swatch_controller: TaskController::new(),
|
||||
syn_loader,
|
||||
previous_diagnostic_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2277,6 +2280,10 @@ impl Document {
|
|||
pub fn reset_all_inlay_hints(&mut self) {
|
||||
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)]
|
||||
|
|
|
@ -22,6 +22,8 @@ pub struct Handlers {
|
|||
pub signature_hints: Sender<lsp::SignatureHelpEvent>,
|
||||
pub auto_save: Sender<AutoSaveEvent>,
|
||||
pub document_colors: Sender<lsp::DocumentColorsEvent>,
|
||||
pub pull_diagnostics: Sender<lsp::PullDiagnosticsEvent>,
|
||||
pub pull_all_documents_diagnostics: Sender<lsp::PullAllDocumentsDiagnosticsEvent>,
|
||||
}
|
||||
|
||||
impl Handlers {
|
||||
|
|
|
@ -30,6 +30,12 @@ pub enum SignatureHelpEvent {
|
|||
RequestComplete { open: bool },
|
||||
}
|
||||
|
||||
pub struct PullDiagnosticsEvent {
|
||||
pub document_id: DocumentId,
|
||||
}
|
||||
|
||||
pub struct PullAllDocumentsDiagnosticsEvent {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApplyEditError {
|
||||
pub kind: ApplyEditErrorKind,
|
||||
|
|
Loading…
Reference in New Issue