From 0750b766961582707549abf4a02ec565f34982e9 Mon Sep 17 00:00:00 2001 From: MinusGix Date: Wed, 22 Jun 2022 20:49:38 -0500 Subject: [PATCH] Impl searching for symbols in workspace --- defaults/keymaps-macos.toml | 4 + defaults/keymaps-nonmacos.toml | 4 + lapce-data/src/command.rs | 3 + lapce-data/src/data.rs | 7 ++ lapce-data/src/palette.rs | 132 +++++++++++++++++++++++++++++++-- lapce-data/src/proxy.rs | 16 ++++ lapce-proxy/src/dispatch.rs | 5 ++ lapce-proxy/src/lsp.rs | 32 ++++++++ lapce-rpc/src/proxy.rs | 6 ++ lapce-ui/src/palette.rs | 59 ++++++++++++++- 10 files changed, 262 insertions(+), 6 deletions(-) diff --git a/defaults/keymaps-macos.toml b/defaults/keymaps-macos.toml index 1e3a59cf..e51e69be 100644 --- a/defaults/keymaps-macos.toml +++ b/defaults/keymaps-macos.toml @@ -226,6 +226,10 @@ command = "document_end" key = "meta+O" command = "palette.symbol" +[[keymaps]] +key = "meta+t" +command = "palette.workspace_symbol" + [[keymaps]] key = "ctrl+g" command = "palette.line" diff --git a/defaults/keymaps-nonmacos.toml b/defaults/keymaps-nonmacos.toml index a291b90f..d0fd4940 100644 --- a/defaults/keymaps-nonmacos.toml +++ b/defaults/keymaps-nonmacos.toml @@ -221,6 +221,10 @@ command = "document_end" key = "ctrl+O" command = "palette.symbol" +[[keymaps]] +key = "ctrl+t" +command = "palette.workspace_symbol" + [[keymaps]] key = "ctrl+g" command = "palette.line" diff --git a/lapce-data/src/command.rs b/lapce-data/src/command.rs index 562b0435..3c4394a0 100644 --- a/lapce-data/src/command.rs +++ b/lapce-data/src/command.rs @@ -271,6 +271,9 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "palette.symbol")] PaletteSymbol, + #[strum(serialize = "palette.workspace_symbol")] + PaletteWorkspaceSymbol, + #[strum(message = "Command Palette")] #[strum(serialize = "palette.command")] PaletteCommand, diff --git a/lapce-data/src/data.rs b/lapce-data/src/data.rs index b1d3491f..a16de169 100644 --- a/lapce-data/src/data.rs +++ b/lapce-data/src/data.rs @@ -1100,6 +1100,13 @@ pub fn run_workbench_command( Target::Widget(self.palette.widget_id), )); } + LapceWorkbenchCommand::PaletteWorkspaceSymbol => { + ctx.submit_command(Command::new( + LAPCE_UI_COMMAND, + LapceUICommand::RunPalette(Some(PaletteType::WorkspaceSymbol)), + Target::Widget(self.palette.widget_id), + )); + } LapceWorkbenchCommand::PaletteCommand => { ctx.submit_command(Command::new( LAPCE_UI_COMMAND, diff --git a/lapce-data/src/palette.rs b/lapce-data/src/palette.rs index dd4544f4..6be4823b 100644 --- a/lapce-data/src/palette.rs +++ b/lapce-data/src/palette.rs @@ -9,7 +9,7 @@ use lapce_core::command::{EditCommand, FocusCommand}; use lapce_core::mode::Mode; use lapce_core::movement::Movement; -use lsp_types::{DocumentSymbolResponse, Range, SymbolKind}; +use lsp_types::{DocumentSymbolResponse, Range, SymbolInformation, SymbolKind}; use serde_json; use std::cmp::Ordering; use std::collections::HashSet; @@ -21,6 +21,7 @@ use crate::data::{LapceWorkspace, LapceWorkspaceType}; use crate::document::BufferContent; use crate::editor::EditorLocation; +use crate::proxy::path_from_url; use crate::{ command::LAPCE_UI_COMMAND, command::{CommandExecuted, LAPCE_COMMAND}, @@ -39,6 +40,7 @@ pub enum PaletteType { Line, GlobalSearch, DocumentSymbol, + WorkspaceSymbol, Workspace, Command, Reference, @@ -52,6 +54,7 @@ fn string(&self) -> String { PaletteType::File => "".to_string(), PaletteType::Line => "/".to_string(), PaletteType::DocumentSymbol => "@".to_string(), + PaletteType::WorkspaceSymbol => "#".to_string(), PaletteType::GlobalSearch => "?".to_string(), PaletteType::Workspace => ">".to_string(), PaletteType::Command => ":".to_string(), @@ -66,6 +69,7 @@ pub fn has_preview(&self) -> bool { self, PaletteType::Line | PaletteType::DocumentSymbol + | PaletteType::WorkspaceSymbol | PaletteType::GlobalSearch | PaletteType::Reference ) @@ -97,6 +101,13 @@ pub enum PaletteItemContent { range: Range, container_name: Option, }, + WorkspaceSymbol { + // TODO: Include what language it is from? + kind: SymbolKind, + name: String, + container_name: Option, + location: EditorLocation, + }, ReferenceLocation(PathBuf, EditorLocation), Workspace(LapceWorkspace), SshHost(String, String), @@ -133,6 +144,18 @@ fn select( Target::Auto, )); } + PaletteItemContent::WorkspaceSymbol { location, .. } => { + let editor_id = if preview { + Some(preview_editor_id) + } else { + None + }; + ctx.submit_command(Command::new( + LAPCE_UI_COMMAND, + LapceUICommand::JumpToLocation(editor_id, location.clone()), + Target::Auto, + )); + } PaletteItemContent::Line(line, _) => { let editor_id = if preview { Some(preview_editor_id) @@ -430,6 +453,7 @@ pub fn get_input(&self) -> &str { PaletteType::SshHost => &self.input, PaletteType::Line => &self.input[1..], PaletteType::DocumentSymbol => &self.input[1..], + PaletteType::WorkspaceSymbol => &self.input[1..], PaletteType::Workspace => &self.input[1..], PaletteType::Command => &self.input[1..], PaletteType::GlobalSearch => &self.input[1..], @@ -461,7 +485,7 @@ pub fn run_references( ctx: &mut EventCtx, locations: &[EditorLocation], ) { - self.run(ctx, Some(PaletteType::Reference)); + self.run(ctx, Some(PaletteType::Reference), None); let items: Vec = locations .iter() .map(|l| { @@ -487,11 +511,16 @@ pub fn run_references( palette.preview(ctx); } - pub fn run(&mut self, ctx: &mut EventCtx, palette_type: Option) { + pub fn run( + &mut self, + ctx: &mut EventCtx, + palette_type: Option, + input: Option, + ) { let palette = Arc::make_mut(&mut self.palette); palette.status = PaletteStatus::Started; palette.palette_type = palette_type.unwrap_or(PaletteType::File); - palette.input = palette.palette_type.string(); + palette.input = input.unwrap_or_else(|| palette.palette_type.string()); ctx.submit_command(Command::new( LAPCE_UI_COMMAND, LapceUICommand::InitPaletteInput(palette.input.clone()), @@ -526,6 +555,9 @@ pub fn run(&mut self, ctx: &mut EventCtx, palette_type: Option) { PaletteType::DocumentSymbol => { self.get_document_symbols(ctx); } + PaletteType::WorkspaceSymbol => { + self.get_workspace_symbols(ctx); + } PaletteType::Workspace => { self.get_workspaces(ctx); } @@ -570,6 +602,7 @@ pub fn delete_to_beginning_of_line(&mut self, ctx: &mut EventCtx) { PaletteType::SshHost => 0, PaletteType::Line => 1, PaletteType::DocumentSymbol => 1, + PaletteType::WorkspaceSymbol => 1, PaletteType::Workspace => 1, PaletteType::Command => 1, PaletteType::GlobalSearch => 1, @@ -643,7 +676,17 @@ pub fn select(&mut self, ctx: &mut EventCtx) { pub fn update_input(&mut self, ctx: &mut EventCtx, input: String) { let palette = Arc::make_mut(&mut self.palette); + + // WorkspaceSymbol requires sending the query to the lsp, so we refresh it when the input changes + if input != palette.input + && matches!(palette.palette_type, PaletteType::WorkspaceSymbol) + { + self.run(ctx, Some(PaletteType::WorkspaceSymbol), Some(input)); + return; + } + palette.input = input; + self.update_palette(ctx) } @@ -652,9 +695,10 @@ pub fn update_palette(&mut self, ctx: &mut EventCtx) { palette.index = 0; let palette_type = self.get_palette_type(); if self.palette.palette_type != palette_type { - self.run(ctx, Some(palette_type)); + self.run(ctx, Some(palette_type), None); return; } + if self.palette.get_input() != "" { let _ = self.palette.sender.send(( self.palette.run_id.clone(), @@ -679,6 +723,7 @@ fn get_palette_type(&self) -> PaletteType { match self.palette.input { _ if self.palette.input.starts_with('/') => PaletteType::Line, _ if self.palette.input.starts_with('@') => PaletteType::DocumentSymbol, + _ if self.palette.input.starts_with('#') => PaletteType::WorkspaceSymbol, _ if self.palette.input.starts_with('>') => PaletteType::Workspace, _ if self.palette.input.starts_with(':') => PaletteType::Command, _ => PaletteType::File, @@ -980,6 +1025,83 @@ fn get_document_symbols(&mut self, ctx: &mut EventCtx) { } } + fn get_workspace_symbols(&mut self, ctx: &mut EventCtx) { + let editor = self.main_split.active_editor(); + let editor = match editor { + Some(editor) => editor, + None => return, + }; + + let widget_id = self.palette.widget_id; + + // TODO: We'd like to be able to request symbols even when not in an editor. + if let BufferContent::File(path) = &editor.content { + let buffer_id = self.main_split.open_docs.get(path).unwrap().id(); + let run_id = self.palette.run_id.clone(); + let event_sink = ctx.get_external_handle(); + + let query = self.palette.get_input(); + + self.palette.proxy.get_workspace_symbols( + buffer_id, + query, + Box::new(move |result| { + if let Ok(res) = result { + let resp: Result< + Option>, + serde_json::Error, + > = serde_json::from_value(res); + if let Ok(resp) = resp { + let items: Vec = match resp { + Some(symbols) => symbols + .iter() + .map(|s| { + // TODO: Should we be using filter text? + let mut filter_text = s.name.clone(); + if let Some(container_name) = + s.container_name.as_ref() + { + filter_text += container_name; + } + PaletteItem { + content: + PaletteItemContent::WorkspaceSymbol { + kind: s.kind, + name: s.name.clone(), + location: EditorLocation { + path: path_from_url( + &s.location.uri, + ), + position: Some( + s.location.range.start, + ), + scroll_offset: None, + history: None, + }, + container_name: s + .container_name + .clone(), + }, + filter_text, + score: 0, + indices: Vec::new(), + } + }) + .collect(), + None => Vec::new(), + }; + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::UpdatePaletteItems(run_id, items), + Target::Widget(widget_id), + ); + } + } + }), + ); + } + } + pub fn update_process( receiver: Receiver<(String, String, Vec)>, widget_id: WidgetId, diff --git a/lapce-data/src/proxy.rs b/lapce-data/src/proxy.rs index e1355a81..f1aea689 100644 --- a/lapce-data/src/proxy.rs +++ b/lapce-data/src/proxy.rs @@ -611,6 +611,22 @@ pub fn get_document_symbols(&self, buffer_id: BufferId, f: Box) { ); } + pub fn get_workspace_symbols( + &self, + buffer_id: BufferId, + query: &str, + f: Box, + ) { + self.rpc.send_rpc_request_async( + "get_workspace_symbols", + &json!({ + "buffer_id": buffer_id, + "query": query, + }), + f, + ); + } + pub fn get_code_actions( &self, buffer_id: BufferId, diff --git a/lapce-proxy/src/dispatch.rs b/lapce-proxy/src/dispatch.rs index 2d0f0f3e..e30f0cd7 100644 --- a/lapce-proxy/src/dispatch.rs +++ b/lapce-proxy/src/dispatch.rs @@ -525,6 +525,11 @@ fn handle_request(&self, id: RequestId, rpc: ProxyRequest) { let buffer = buffers.get(&buffer_id).unwrap(); self.lsp.lock().get_document_symbols(id, buffer); } + GetWorkspaceSymbols { query, buffer_id } => { + let buffers = self.buffers.lock(); + let buffer = buffers.get(&buffer_id).unwrap(); + self.lsp.lock().get_workspace_symbols(id, buffer, query); + } GetDocumentFormatting { buffer_id } => { let buffers = self.buffers.lock(); let buffer = buffers.get(&buffer_id).unwrap(); diff --git a/lapce-proxy/src/lsp.rs b/lapce-proxy/src/lsp.rs index 41f74f7a..7bfe0c01 100644 --- a/lapce-proxy/src/lsp.rs +++ b/lapce-proxy/src/lsp.rs @@ -162,6 +162,20 @@ pub fn get_document_symbols(&self, id: RequestId, buffer: &Buffer) { } } + pub fn get_workspace_symbols( + &self, + id: RequestId, + buffer: &Buffer, + query: String, + ) { + // TODO: We could collate workspace symbols from all the lsps? + if let Some(client) = self.clients.get(&buffer.language_id) { + client.request_workspace_symbols(query, move |lsp_client, result| { + lsp_client.dispatcher.respond(id, result); + }); + } + } + pub fn get_document_formatting(&self, id: RequestId, buffer: &Buffer) { if let Some(client) = self.clients.get(&buffer.language_id) { let uri = client.get_uri(buffer); @@ -707,6 +721,12 @@ pub fn send_initialize(&self, root_uri: Option, on_init: CB) }), ..Default::default() }), + workspace: Some(WorkspaceClientCapabilities { + symbol: Some(WorkspaceSymbolClientCapabilities { + ..Default::default() + }), + ..Default::default() + }), experimental: Some(json!({ "serverStatusNotification": true, })), @@ -743,6 +763,18 @@ pub fn request_document_symbols(&self, document_uri: Url, cb: CB) self.send_request("textDocument/documentSymbol", params, Box::new(cb)); } + pub fn request_workspace_symbols(&self, query: String, cb: CB) + where + CB: 'static + Send + FnOnce(&LspClient, Result), + { + let params = WorkspaceSymbolParams { + query, + ..Default::default() + }; + let params = Params::from(serde_json::to_value(params).unwrap()); + self.send_request("workspace/symbol", params, Box::new(cb)); + } + pub fn request_document_formatting(&self, document_uri: Url, cb: CB) where CB: 'static + Send + FnOnce(&LspClient, Result), diff --git a/lapce-rpc/src/proxy.rs b/lapce-rpc/src/proxy.rs index 65ca13bd..0f2230a5 100644 --- a/lapce-rpc/src/proxy.rs +++ b/lapce-rpc/src/proxy.rs @@ -100,6 +100,12 @@ pub enum ProxyRequest { GetDocumentSymbols { buffer_id: BufferId, }, + GetWorkspaceSymbols { + /// The search query + query: String, + /// THe id of the buffer it was used in, which tells us what LSP to query + buffer_id: BufferId, + }, GetDocumentFormatting { buffer_id: BufferId, }, diff --git a/lapce-ui/src/palette.rs b/lapce-ui/src/palette.rs index dfde8119..5ae095a2 100644 --- a/lapce-ui/src/palette.rs +++ b/lapce-ui/src/palette.rs @@ -22,6 +22,7 @@ keypress::KeyPressFocus, palette::{PaletteStatus, PaletteType, PaletteViewData, PaletteViewLens}, }; +use lsp_types::SymbolKind; use crate::{ editor::view::LapceEditorView, @@ -102,7 +103,7 @@ fn event( LapceUICommand::RunPalette(palette_type) => { ctx.set_handled(); let mut palette_data = data.palette_view_data(); - palette_data.run(ctx, palette_type.to_owned()); + palette_data.run(ctx, palette_type.to_owned(), None); data.palette = palette_data.palette.clone(); data.keypress = palette_data.keypress.clone(); data.workspace = palette_data.workspace.clone(); @@ -546,6 +547,7 @@ pub fn new() -> Self { fn paint_palette_item( palette_item_content: &PaletteItemContent, ctx: &mut PaintCtx, + workspace_path: Option<&Path>, line: usize, indices: &[usize], line_height: f64, @@ -589,6 +591,18 @@ fn paint_palette_item( .collect(); (symbol_svg(kind), text, text_indices, hint, hint_indices) } + PaletteItemContent::WorkspaceSymbol { + kind, + name, + location, + .. + } => Self::file_paint_symbols( + &location.path, + indices, + workspace_path, + name.as_str(), + *kind, + ), PaletteItemContent::Line(_, text) => { (None, text.clone(), indices.to_vec(), "".to_string(), vec![]) } @@ -735,6 +749,47 @@ fn paint_palette_item( ctx.draw_text(&text_layout, point); } + fn file_paint_symbols( + path: &Path, + indices: &[usize], + workspace_path: Option<&Path>, + name: &str, + kind: SymbolKind, + ) -> (Option, String, Vec, String, Vec) { + let text = name.to_string(); + let hint = path.to_string_lossy(); + // Remove the workspace prefix from the path + let hint = workspace_path + .and_then(Path::to_str) + .and_then(|x| hint.strip_prefix(x)) + .map(|x| x.strip_prefix('/').unwrap_or(x)) + .map(ToString::to_string) + .unwrap_or_else(|| hint.to_string()); + let text_indices = indices + .iter() + .filter_map(|i| { + let i = *i; + if i < text.len() { + Some(i) + } else { + None + } + }) + .collect(); + let hint_indices = indices + .iter() + .filter_map(|i| { + let i = *i; + if i >= text.len() { + Some(i - text.len()) + } else { + None + } + }) + .collect(); + (symbol_svg(&kind), text, text_indices, hint, hint_indices) + } + fn file_paint_items( path: &Path, indices: &[usize], @@ -856,6 +911,7 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &PaletteViewData, _env: &Env) { let start_line = (rect.y0 / self.line_height).floor() as usize; let end_line = (rect.y1 / self.line_height).ceil() as usize; + let workspace_path = data.workspace.path.as_deref(); for line in start_line..end_line { if line >= items.len() { break; @@ -874,6 +930,7 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &PaletteViewData, _env: &Env) { Self::paint_palette_item( &item.content, ctx, + workspace_path, line, &item.indices, self.line_height,