diff --git a/book/src/keymap.md b/book/src/keymap.md index cba89896f..a4af5ce85 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -166,5 +166,6 @@ This layer is a kludge of mappings I had under leader key in neovim. |-----|-----------| | f | Open file picker | | b | Open buffer picker | +| s | Open symbol picker (current document)| | w | Enter window mode | | space | Keep primary selection TODO: it's here because space mode replaced it | diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 070f90d15..9ca708a75 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -660,4 +660,17 @@ impl Client { self.call::(params) } + + pub fn document_symbols( + &self, + text_document: lsp::TextDocumentIdentifier, + ) -> impl Future> { + let params = lsp::DocumentSymbolParams { + text_document, + work_done_progress_params: lsp::WorkDoneProgressParams::default(), + partial_result_params: lsp::PartialResultParams::default(), + }; + + self.call::(params) + } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index e16ad765b..cff624928 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -138,6 +138,17 @@ pub mod util { lsp::Range::new(start, end) } + pub fn lsp_range_to_range( + doc: &Rope, + range: lsp::Range, + offset_encoding: OffsetEncoding, + ) -> Option { + let start = lsp_pos_to_pos(doc, range.start, offset_encoding)?; + let end = lsp_pos_to_pos(doc, range.end, offset_encoding)?; + + Some(Range::new(start, end)) + } + pub fn generate_transaction_from_edits( doc: &Rope, edits: Vec, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 0ae2186b1..e7823fe12 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -16,7 +16,7 @@ use helix_view::{ use helix_lsp::{ lsp, - util::{lsp_pos_to_pos, pos_to_lsp_pos, range_to_lsp_range}, + util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, OffsetEncoding, }; use movement::Movement; @@ -1194,6 +1194,76 @@ pub fn buffer_picker(cx: &mut Context) { cx.push_layer(Box::new(picker)); } +pub fn symbol_picker(cx: &mut Context) { + fn nested_to_flat( + list: &mut Vec, + file: &lsp::TextDocumentIdentifier, + symbol: lsp::DocumentSymbol, + ) { + #[allow(deprecated)] + list.push(lsp::SymbolInformation { + name: symbol.name, + kind: symbol.kind, + tags: symbol.tags, + deprecated: symbol.deprecated, + location: lsp::Location::new(file.uri.clone(), symbol.selection_range), + container_name: None, + }); + for child in symbol.children.into_iter().flatten() { + nested_to_flat(list, file, child); + } + } + let (view, doc) = cx.current(); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + let offset_encoding = language_server.offset_encoding(); + + let future = language_server.document_symbols(doc.identifier()); + + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + response: Option| { + if let Some(symbols) = response { + // lsp has two ways to represent symbols (flat/nested) + // convert the nested variant to flat, so that we have a homogeneous list + let symbols = match symbols { + lsp::DocumentSymbolResponse::Flat(symbols) => symbols, + lsp::DocumentSymbolResponse::Nested(symbols) => { + let (_view, doc) = editor.current(); + let mut flat_symbols = Vec::new(); + for symbol in symbols { + nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol) + } + flat_symbols + } + }; + + let picker = Picker::new( + symbols, + |symbol| (&symbol.name).into(), + move |editor: &mut Editor, symbol, _action| { + push_jump(editor); + let (view, doc) = editor.current(); + + if let Some(range) = + lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding) + { + doc.set_selection(view.id, Selection::single(range.to(), range.from())); + align_view(doc, view, Align::Center); + } + }, + ); + compositor.push(Box::new(picker)) + } + }, + ) +} + // I inserts at the first nonwhitespace character of each line with a selection pub fn prepend_to_line(cx: &mut Context) { move_first_nonwhitespace(cx); @@ -2548,6 +2618,7 @@ pub fn space_mode(cx: &mut Context) { match ch { 'f' => file_picker(cx), 'b' => buffer_picker(cx), + 's' => symbol_picker(cx), 'w' => window_mode(cx), // ' ' => toggle_alternate_buffer(cx), // TODO: temporary since space mode took its old key