Skip to content

Commit 26149d1

Browse files
committed
feat: code lenses handler
Request code lenses on newly opened documents, when a language server is started, etc.
1 parent b661ac6 commit 26149d1

6 files changed

Lines changed: 160 additions & 3 deletions

File tree

helix-term/src/commands/lsp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use super::{align_view, push_jump, Align, Context, Editor};
1515

1616
use helix_core::{
1717
diagnostic::DiagnosticProvider, syntax::config::LanguageServerFeature,
18-
text_annotations::InlineAnnotation, CodeLens, Selection, Uri,
18+
text_annotations::InlineAnnotation, Selection, Uri,
1919
};
2020
use helix_stdx::path;
2121
use helix_view::{

helix-term/src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub use helix_view::handlers::{word_index, Handlers};
1515
use self::document_colors::DocumentColorsHandler;
1616

1717
mod auto_save;
18+
mod code_lenses;
1819
pub mod completion;
1920
pub mod diagnostics;
2021
mod document_colors;
@@ -50,6 +51,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
5051
diagnostics::register_hooks(&handlers);
5152
snippet::register_hooks(&handlers);
5253
document_colors::register_hooks(&handlers);
54+
code_lenses::register_hooks(&handlers);
5355
prompt::register_hooks(&handlers);
5456
handlers
5557
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use std::{collections::HashSet, time::Duration};
2+
3+
use futures_util::{stream::FuturesOrdered, StreamExt};
4+
use helix_core::syntax::config::LanguageServerFeature;
5+
use helix_event::{cancelable_future, register_hook};
6+
use helix_view::{
7+
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
8+
handlers::{lsp::CodeLensesEvent, Handlers},
9+
DocumentId, Editor,
10+
};
11+
use tokio::time::Instant;
12+
13+
use crate::job;
14+
15+
#[derive(Default)]
16+
pub(super) struct DocumentCodeLensesHandler {
17+
docs: HashSet<DocumentId>,
18+
}
19+
20+
const DOCUMENT_CHANGE_DEBOUNCE: Duration = Duration::from_millis(250);
21+
22+
impl helix_event::AsyncHook for DocumentCodeLensesHandler {
23+
type Event = CodeLensesEvent;
24+
25+
fn handle_event(&mut self, event: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
26+
let CodeLensesEvent(doc_id) = event;
27+
self.docs.insert(doc_id);
28+
Some(Instant::now() + DOCUMENT_CHANGE_DEBOUNCE)
29+
}
30+
31+
fn finish_debounce(&mut self) {
32+
let docs = std::mem::take(&mut self.docs);
33+
34+
job::dispatch_blocking(move |editor, _compositor| {
35+
for doc in docs {
36+
request_document_code_lenses(editor, doc);
37+
}
38+
});
39+
}
40+
}
41+
42+
fn request_document_code_lenses(editor: &mut Editor, doc_id: DocumentId) {
43+
if !editor.config().lsp.display_code_lenses {
44+
return;
45+
}
46+
47+
let Some(doc) = editor.document_mut(doc_id) else {
48+
return;
49+
};
50+
51+
let cancel = doc.color_swatch_controller.restart();
52+
53+
let mut seen_language_servers = HashSet::new();
54+
let mut futures: FuturesOrdered<_> = doc
55+
.language_servers_with_feature(LanguageServerFeature::CodeLens)
56+
.filter(|ls| seen_language_servers.insert(ls.id()))
57+
.map(|language_server| {
58+
let text = doc.text().clone();
59+
let offset_encoding = language_server.offset_encoding();
60+
let future = language_server.code_lens(doc.identifier()).unwrap();
61+
62+
async move {
63+
let lenses: Vec<_> = future
64+
.await?
65+
.unwrap_or_default()
66+
.into_iter()
67+
.filter_map(|lens| {
68+
let _pos = helix_lsp::util::lsp_pos_to_pos(
69+
&text,
70+
lens.range.start,
71+
offset_encoding,
72+
)?;
73+
Some(lens)
74+
})
75+
.collect();
76+
anyhow::Ok(lenses)
77+
}
78+
})
79+
.collect();
80+
81+
if futures.is_empty() {
82+
return;
83+
}
84+
85+
tokio::spawn(async move {
86+
let mut code_lenses = Vec::new();
87+
loop {
88+
match cancelable_future(futures.next(), &cancel).await {
89+
Some(Some(Ok(items))) => code_lenses.extend(items),
90+
Some(Some(Err(err))) => log::error!("document color request failed: {err}"),
91+
Some(None) => break,
92+
// The request was cancelled.
93+
None => return,
94+
}
95+
}
96+
job::dispatch(move |editor, _| {
97+
let Some(doc) = editor.documents.get_mut(&doc_id) else {
98+
return;
99+
};
100+
101+
if code_lenses.is_empty() {
102+
doc.code_lenses.clear();
103+
return;
104+
}
105+
106+
doc.code_lenses = code_lenses;
107+
})
108+
.await;
109+
});
110+
}
111+
112+
pub(super) fn register_hooks(_handlers: &Handlers) {
113+
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
114+
// when a document is initially opened, request colors for it
115+
request_document_code_lenses(event.editor, event.doc);
116+
117+
Ok(())
118+
});
119+
120+
register_hook!(move |_event: &mut DocumentDidChange<'_>| {
121+
// TODO: update code lenses positions, same as with e.g. document colors
122+
Ok(())
123+
});
124+
125+
register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
126+
let doc_ids: Vec<_> = event.editor.documents().map(|doc| doc.id()).collect();
127+
128+
for doc_id in doc_ids {
129+
request_document_code_lenses(event.editor, doc_id);
130+
}
131+
132+
Ok(())
133+
});
134+
135+
register_hook!(move |event: &mut LanguageServerExited<'_>| {
136+
// Clear and re-request all color swatches when a server exits.
137+
for doc in event.editor.documents_mut() {
138+
if doc.supports_language_server(event.server_id) {
139+
doc.code_lenses.clear();
140+
}
141+
}
142+
143+
let doc_ids: Vec<_> = event.editor.documents().map(|doc| doc.id()).collect();
144+
145+
for doc_id in doc_ids {
146+
request_document_code_lenses(event.editor, doc_id);
147+
}
148+
149+
Ok(())
150+
});
151+
}

helix-view/src/document.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ use helix_core::{
4040
indent::{auto_detect_indent_style, IndentStyle},
4141
line_ending::auto_detect_line_ending,
4242
syntax::{self, config::LanguageConfiguration},
43-
ChangeSet, CodeLens, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax,
44-
Transaction,
43+
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
4544
};
4645

4746
use crate::{

helix-view/src/editor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ pub struct LspConfig {
527527
pub inlay_hints_length_limit: Option<NonZeroU8>,
528528
/// Display document color swatches
529529
pub display_color_swatches: bool,
530+
/// Display document color swatches
531+
pub display_code_lenses: bool,
530532
/// Whether to enable snippet support
531533
pub snippets: bool,
532534
/// Whether to include declaration in the goto reference query
@@ -546,6 +548,7 @@ impl Default for LspConfig {
546548
snippets: true,
547549
goto_reference_include_declaration: true,
548550
display_color_swatches: true,
551+
display_code_lenses: true,
549552
}
550553
}
551554
}

helix-view/src/handlers/lsp.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use super::Handlers;
1717

1818
pub struct DocumentColorsEvent(pub DocumentId);
1919

20+
pub struct CodeLensesEvent(pub DocumentId);
21+
2022
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2123
pub enum SignatureHelpInvoked {
2224
Automatic,

0 commit comments

Comments
 (0)