Merge pull request #666 from MinusGix/workspace-symbol-search

Impl searching for symbols in workspace
This commit is contained in:
Dongdong Zhou 2022-06-23 09:12:44 +01:00 committed by GitHub
commit fc9eabdd1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 262 additions and 6 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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<String>,
},
WorkspaceSymbol {
// TODO: Include what language it is from?
kind: SymbolKind,
name: String,
container_name: Option<String>,
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<PaletteItem> = 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<PaletteType>) {
pub fn run(
&mut self,
ctx: &mut EventCtx,
palette_type: Option<PaletteType>,
input: Option<String>,
) {
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>) {
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<Vec<SymbolInformation>>,
serde_json::Error,
> = serde_json::from_value(res);
if let Ok(resp) = resp {
let items: Vec<PaletteItem> = 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<PaletteItem>)>,
widget_id: WidgetId,

View File

@ -661,6 +661,22 @@ pub fn get_document_symbols(&self, buffer_id: BufferId, f: Box<dyn Callback>) {
);
}
pub fn get_workspace_symbols(
&self,
buffer_id: BufferId,
query: &str,
f: Box<dyn Callback>,
) {
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,

View File

@ -533,6 +533,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();

View File

@ -191,6 +191,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);
@ -790,6 +804,12 @@ pub fn send_initialize<CB>(&self, root_uri: Option<Url>, on_init: CB)
}),
..Default::default()
}),
workspace: Some(WorkspaceClientCapabilities {
symbol: Some(WorkspaceSymbolClientCapabilities {
..Default::default()
}),
..Default::default()
}),
experimental: Some(json!({
"serverStatusNotification": true,
})),
@ -826,6 +846,18 @@ pub fn request_document_symbols<CB>(&self, document_uri: Url, cb: CB)
self.send_request("textDocument/documentSymbol", params, Box::new(cb));
}
pub fn request_workspace_symbols<CB>(&self, query: String, cb: CB)
where
CB: 'static + Send + FnOnce(&LspClient, Result<Value>),
{
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<CB>(&self, document_uri: Url, cb: CB)
where
CB: 'static + Send + FnOnce(&LspClient, Result<Value>),

View File

@ -101,6 +101,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,
},

View File

@ -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<Svg>, String, Vec<usize>, String, Vec<usize>) {
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,