Skip to content
1 change: 1 addition & 0 deletions book/src/generated/static-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
| `add_newline_below` | Add newline below | normal: `` ]<space> ``, select: `` ]<space> `` |
| `goto_type_definition` | Goto type definition | normal: `` gy ``, select: `` gy `` |
| `goto_implementation` | Goto implementation | normal: `` gi ``, select: `` gi `` |
| `show_code_lenses_under_cursor` | Show code lenses under cursor | normal: `` <space>l ``, select: `` <space>l `` |
| `goto_file_start` | Goto line number `<n>` else file start | normal: `` gg `` |
| `goto_file_end` | Goto file end | |
| `extend_to_file_start` | Extend to line number `<n>` else file start | select: `` gg `` |
Expand Down
2 changes: 2 additions & 0 deletions helix-core/src/syntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ pub enum LanguageServerFeature {
InlayHints,
DocumentColors,
CallHierarchy,
CodeLens,
}

impl Display for LanguageServerFeature {
Expand Down Expand Up @@ -348,6 +349,7 @@ impl Display for LanguageServerFeature {
InlayHints => "inlay-hints",
DocumentColors => "document-colors",
CallHierarchy => "call-hierarchy",
CodeLens => "code-lens",
};
write!(f, "{feature}",)
}
Expand Down
35 changes: 35 additions & 0 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ impl Client {
| CallHierarchyServerCapability::Options(_)
)
),
LanguageServerFeature::CodeLens => capabilities.code_lens_provider.is_some(),
}
}

Expand Down Expand Up @@ -620,6 +621,9 @@ impl Client {
diagnostic: Some(lsp::DiagnosticWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
code_lens: Some(lsp::CodeLensWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
..Default::default()
}),
text_document: Some(lsp::TextDocumentClientCapabilities {
Expand Down Expand Up @@ -723,6 +727,9 @@ impl Client {
call_hierarchy: Some(lsp::DynamicRegistrationClientCapabilities {
dynamic_registration: Some(false),
}),
code_lens: Some(lsp::CodeLensClientCapabilities {
..Default::default()
}),
..Default::default()
}),
window: Some(lsp::WindowClientCapabilities {
Expand Down Expand Up @@ -1713,4 +1720,32 @@ impl Client {
changes,
})
}

pub fn code_lens(
&self,
text_document: lsp::TextDocumentIdentifier,
) -> Option<impl Future<Output = Result<Option<Vec<lsp::CodeLens>>>>> {
if !self.supports_feature(LanguageServerFeature::CodeLens) {
return None;
}

let params = lsp::CodeLensParams {
text_document,
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
partial_result_params: lsp::PartialResultParams::default(),
};

Some(self.call::<lsp::request::CodeLensRequest>(params))
}

pub fn resolve_code_lens(
&self,
params: lsp::CodeLens,
) -> Option<impl Future<Output = Result<lsp::CodeLens>>> {
if !self.supports_feature(LanguageServerFeature::CodeLens) {
return None;
}

Some(self.call::<lsp::request::CodeLensResolve>(params))
}
}
2 changes: 2 additions & 0 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ pub enum MethodCall {
ShowDocument(lsp::ShowDocumentParams),
WorkspaceDiagnosticRefresh,
ShowMessageRequest(lsp::ShowMessageRequestParams),
CodeLensRefresh,
}

impl MethodCall {
Expand Down Expand Up @@ -515,6 +516,7 @@ impl MethodCall {
let params: lsp::ShowMessageRequestParams = params.parse()?;
Self::ShowMessageRequest(params)
}
lsp::request::CodeLensRefresh::METHOD => Self::CodeLensRefresh,
_ => {
return Err(Error::Unhandled);
}
Expand Down
24 changes: 22 additions & 2 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,8 +1113,8 @@ impl Application {
.editor
.documents
.values()
.filter(|x| x.supports_language_server(language_server))
.map(|x| x.id())
.filter(|doc| doc.supports_language_server(language_server))
.map(|doc| doc.id())
.collect();

for document in documents {
Expand Down Expand Up @@ -1162,6 +1162,26 @@ impl Application {
Ok(serde_json::Value::Null)
}
}
Ok(MethodCall::CodeLensRefresh) => {
let language_server = language_server!().id();

let documents: Vec<_> = self
.editor
.documents
.values()
.filter(|doc| doc.supports_language_server(language_server))
.map(|doc| doc.id())
.collect();

for document in documents {
handlers::code_lenses::request_document_code_lenses(
&mut self.editor,
document,
);
}

Ok(serde_json::Value::Null)
}
};

let language_server = language_server!();
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ impl MappableCommand {
add_newline_below, "Add newline below",
goto_type_definition, "Goto type definition",
goto_implementation, "Goto implementation",
show_code_lenses_under_cursor, "Show code lenses under cursor",
goto_file_start, "Goto line number `<n>` else file start",
goto_file_end, "Goto file end",
extend_to_file_start, "Extend to line number `<n>` else file start",
Expand Down
96 changes: 95 additions & 1 deletion helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use futures_util::{stream::FuturesUnordered, FutureExt};
use futures_util::{
future::{ready, Either},
stream::FuturesUnordered,
FutureExt,
};
use helix_lsp::{
block_on,
lsp::{
Expand Down Expand Up @@ -1456,3 +1460,93 @@ fn compute_inlay_hints_for_view(

Some(callback)
}

impl ui::menu::Item for lsp::CodeLens {
type Data = ();
fn format(&self, _data: &Self::Data) -> Row<'_> {
// Safety: assumes that only resolved code lenses are passed to the menu
self.command.as_ref().unwrap().title.as_str().into()
}
}

pub fn show_code_lenses_under_cursor(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
let doc_id = doc.id();
let language_server =
language_server_with_feature!(cx.editor, doc, LanguageServerFeature::CodeLens);
let language_server_id = language_server.id();
let offset_encoding = language_server.offset_encoding();
let pos = doc.position(view.id, offset_encoding);

if doc
.language_servers_with_feature(LanguageServerFeature::CodeLens)
.count()
== 0
{
cx.editor
.set_error("No configured language server supports hover");
return;
}

// TODO(matoous): this should eventually support multiple language servers
let mut futures: FuturesUnordered<_> = doc
.code_lenses()
.iter()
.filter(|c| c.line == pos.line as usize)
.filter_map(|c| {
if c.lens.command.is_some() {
Some(Either::Left(ready(Ok(c.lens.clone()))))
} else {
// Safety: the command is empty only if the code lens is unresolved and we assume
// that if the code lens is unresolved the language server support resolution.
language_server
.resolve_code_lens(c.lens.clone())
.map(Either::Right)
}
})
.collect();

cx.jobs.callback(async move {
let mut lenses = Vec::new();
while let Some(response) = futures.next().await {
match response {
Ok(item) => lenses.push(item),
Err(err) => log::error!("Error requesting document symbols: {err}"),
}
}

let call = move |editor: &mut Editor, compositor: &mut Compositor| {
if lenses.is_empty() {
editor.set_error("No code lenses available");
return;
}

if let Some(doc) = editor.documents.get_mut(&doc_id) {
doc.update_code_lenses(lenses.clone());
}

let mut picker = ui::Menu::new(lenses, (), move |editor, lens, event| {
if event != PromptEvent::Validate {
return;
}

// always present here
let lens = lens.unwrap();

if let Some(cmd) = lens.command.clone() {
log::debug!("code lens command: {:?}", cmd);
editor.execute_lsp_command(cmd, language_server_id);
};
});
picker.move_down(); // pre-select the first item

let popup = Popup::new("code-lens", picker)
.with_scrollbar(false)
.auto_close(true);

compositor.replace_or_push("code-lens", popup);
};

Ok(Callback::EditorCompositor(Box::new(call)))
});
}
5 changes: 5 additions & 0 deletions helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

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

Expand All @@ -16,6 +17,7 @@ use self::document_colors::DocumentColorsHandler;
use self::document_links::DocumentLinksHandler;

mod auto_save;
pub mod code_lenses;
pub mod completion;
pub mod diagnostics;
mod document_colors;
Expand All @@ -34,6 +36,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
let auto_save = AutoSaveHandler::new().spawn();
let document_colors = DocumentColorsHandler::default().spawn();
let document_links = DocumentLinksHandler::default().spawn();
let code_lenses = DocumentCodeLensesHandler::default().spawn();
let word_index = word_index::Handler::spawn();
let pull_diagnostics = PullDiagnosticsHandler::default().spawn();
let pull_all_documents_diagnostics = PullAllDocumentsDiagnosticHandler::default().spawn();
Expand All @@ -44,6 +47,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
auto_save,
document_colors,
document_links,
code_lenses,
word_index,
pull_diagnostics,
pull_all_documents_diagnostics,
Expand All @@ -58,6 +62,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
snippet::register_hooks(&handlers);
document_colors::register_hooks(&handlers);
document_links::register_hooks(&handlers);
code_lenses::register_hooks(&handlers);
prompt::register_hooks(&handlers);
workspace_trust::register_hooks(&handlers);
handlers
Expand Down
Loading