mirror of https://github.com/lapce/lapce.git
feat(lsp): implement syntax aware selection (#1353)
This commit is contained in:
parent
7213ae1dc8
commit
42a23afd35
|
@ -138,6 +138,14 @@ key = "esc"
|
|||
command = "clear_search"
|
||||
when = "search_focus"
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+shift+up"
|
||||
command = "select_next_syntax_item"
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+shift+down"
|
||||
command = "select_previous_syntax_item"
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+m"
|
||||
command = "list.select"
|
||||
|
|
|
@ -304,6 +304,10 @@ pub enum FocusCommand {
|
|||
Rename,
|
||||
#[strum(serialize = "confirm_rename")]
|
||||
ConfirmRename,
|
||||
#[strum(serialize = "select_next_syntax_item")]
|
||||
SelectNextSyntaxItem,
|
||||
#[strum(serialize = "select_previous_syntax_item")]
|
||||
SelectPreviousSyntaxItem,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
|
|
@ -274,6 +274,23 @@ pub fn yank(&self, buffer: &Buffer) -> RegisterData {
|
|||
RegisterData { content, mode }
|
||||
}
|
||||
|
||||
/// Return the current selection start and end position for a
|
||||
/// Single cursor selection
|
||||
pub fn get_selection(&self) -> Option<(usize, usize)> {
|
||||
match &self.mode {
|
||||
CursorMode::Visual {
|
||||
start,
|
||||
end,
|
||||
mode: _,
|
||||
} => Some((*start, *end)),
|
||||
CursorMode::Insert(selection) => selection
|
||||
.regions()
|
||||
.first()
|
||||
.map(|region| (region.start, region.end)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_offset(&mut self, offset: usize, modify: bool, new_cursor: bool) {
|
||||
match &self.mode {
|
||||
CursorMode::Normal(old_offset) => {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
use lsp_types::{
|
||||
CodeActionOrCommand, CodeActionResponse, CompletionItem, CompletionResponse,
|
||||
InlayHint, Location, Position, ProgressParams, PublishDiagnosticsParams,
|
||||
TextEdit, WorkspaceEdit,
|
||||
SelectionRange, TextEdit, WorkspaceEdit,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use strum::{self, EnumMessage, IntoEnumIterator};
|
||||
|
@ -31,6 +31,7 @@
|
|||
use crate::editor::{EditorPosition, Line, LineCol};
|
||||
use crate::menu::MenuKind;
|
||||
use crate::rich_text::RichText;
|
||||
use crate::selection_range::SelectionRangeDirection;
|
||||
use crate::update::ReleaseInfo;
|
||||
use crate::{
|
||||
data::{EditorTabChild, SplitContent},
|
||||
|
@ -722,6 +723,18 @@ pub enum LapceUICommand {
|
|||
CopyPath(PathBuf),
|
||||
CopyRelativePath(PathBuf),
|
||||
SetLanguage(String),
|
||||
ApplySelectionRange {
|
||||
buffer_id: BufferId,
|
||||
rev: u64,
|
||||
direction: SelectionRangeDirection,
|
||||
},
|
||||
StoreSelectionRangeAndApply {
|
||||
buffer_id: BufferId,
|
||||
rev: u64,
|
||||
current_selection: Option<(usize, usize)>,
|
||||
ranges: Vec<SelectionRange>,
|
||||
direction: SelectionRangeDirection,
|
||||
},
|
||||
|
||||
/// An item in a list was chosen
|
||||
/// This is typically targeted at the widget which contains the list
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
Interval, Rope, RopeDelta, Transformer,
|
||||
};
|
||||
|
||||
use crate::selection_range::SelectionRangeDirection;
|
||||
use crate::{
|
||||
command::{InitBufferContentCb, LapceUICommand, LAPCE_UI_COMMAND},
|
||||
config::{Config, LapceTheme},
|
||||
|
@ -52,6 +53,7 @@
|
|||
find::{Find, FindProgress},
|
||||
history::DocumentHistory,
|
||||
proxy::LapceProxy,
|
||||
selection_range::SyntaxSelectionRanges,
|
||||
settings::SettingsValueKind,
|
||||
};
|
||||
|
||||
|
@ -416,6 +418,7 @@ pub struct Document {
|
|||
pub code_actions: im::HashMap<usize, CodeActionResponse>,
|
||||
pub inlay_hints: Option<Spans<InlayHint>>,
|
||||
pub diagnostics: Option<Arc<Vec<EditorDiagnostic>>>,
|
||||
pub syntax_selection_range: Option<SyntaxSelectionRanges>,
|
||||
pub find: Rc<RefCell<Find>>,
|
||||
find_progress: Rc<RefCell<FindProgress>>,
|
||||
pub event_sink: ExtEventSink,
|
||||
|
@ -462,6 +465,7 @@ pub fn new(
|
|||
find_progress: Rc::new(RefCell::new(FindProgress::Ready)),
|
||||
event_sink,
|
||||
proxy,
|
||||
syntax_selection_range: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2677,4 +2681,24 @@ pub fn sticky_headers(&self, line: usize) -> Option<Vec<usize>> {
|
|||
self.sticky_headers.borrow_mut().insert(line, lines.clone());
|
||||
lines
|
||||
}
|
||||
|
||||
pub fn change_syntax_selection(
|
||||
&mut self,
|
||||
direction: SelectionRangeDirection,
|
||||
) -> Option<Selection> {
|
||||
if let Some(selections) = self.syntax_selection_range.as_mut() {
|
||||
match direction {
|
||||
SelectionRangeDirection::Next => selections.next_range(),
|
||||
SelectionRangeDirection::Previous => selections.previous_range(),
|
||||
}
|
||||
.map(|range| {
|
||||
let start = self.buffer.offset_of_position(&range.start);
|
||||
let end = self.buffer.offset_of_position(&range.end);
|
||||
selections.last_known_selection = Some((start, end));
|
||||
Selection::region(start, end)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
use crate::palette::PaletteData;
|
||||
use crate::proxy::path_from_url;
|
||||
use crate::rename::RenameData;
|
||||
use crate::selection_range::SelectionRangeDirection;
|
||||
use crate::{
|
||||
command::{
|
||||
EnsureVisiblePosition, InitBufferContent, LapceUICommand, LAPCE_UI_COMMAND,
|
||||
|
@ -125,6 +126,7 @@ fn init_buffer_content_cmd(
|
|||
/// (If you want to jump to the very first character then use `LineCol` with column set to 0)
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Line(pub usize);
|
||||
|
||||
impl EditorPosition for Line {
|
||||
fn to_utf8_offset(&self, buffer: &Buffer) -> usize {
|
||||
buffer.first_non_blank_character_on_line(self.0.saturating_sub(1))
|
||||
|
@ -153,6 +155,7 @@ pub struct LineCol {
|
|||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl EditorPosition for LineCol {
|
||||
fn to_utf8_offset(&self, buffer: &Buffer) -> usize {
|
||||
buffer.offset_of_line_col(self.line, self.column)
|
||||
|
@ -2222,11 +2225,77 @@ fn run_focus_command(
|
|||
Target::Widget(self.rename.view_id),
|
||||
));
|
||||
}
|
||||
SelectNextSyntaxItem => {
|
||||
self.run_selection_range_command(ctx, SelectionRangeDirection::Next)
|
||||
}
|
||||
SelectPreviousSyntaxItem => self
|
||||
.run_selection_range_command(ctx, SelectionRangeDirection::Previous),
|
||||
_ => return CommandExecuted::No,
|
||||
}
|
||||
CommandExecuted::Yes
|
||||
}
|
||||
|
||||
fn run_selection_range_command(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: SelectionRangeDirection,
|
||||
) {
|
||||
let offset = self.editor.cursor.offset();
|
||||
if let BufferContent::File(path) = self.doc.content() {
|
||||
let rev = self.doc.buffer().rev();
|
||||
let buffer_id = self.doc.id();
|
||||
let event_sink = ctx.get_external_handle();
|
||||
let current_selection = self.editor.cursor.get_selection();
|
||||
|
||||
match &self.doc.syntax_selection_range {
|
||||
// If the cached selection range match current revision, no need to call the
|
||||
// LSP server, we ca apply it right now
|
||||
Some(selection_range)
|
||||
if selection_range.match_request(
|
||||
buffer_id,
|
||||
rev,
|
||||
current_selection,
|
||||
) =>
|
||||
{
|
||||
let _ = event_sink.submit_command(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ApplySelectionRange {
|
||||
rev,
|
||||
buffer_id,
|
||||
direction,
|
||||
},
|
||||
Target::Auto,
|
||||
);
|
||||
}
|
||||
// Otherwise, ask the LSP server for `textDocument/selectionRange`
|
||||
_ => {
|
||||
let position = self.doc.buffer().offset_to_position(offset);
|
||||
self.proxy.proxy_rpc.get_selection_range(
|
||||
path.to_owned(),
|
||||
vec![position],
|
||||
move |result| {
|
||||
if let Ok(ProxyResponse::GetSelectionRange { ranges }) =
|
||||
result
|
||||
{
|
||||
let _ = event_sink.submit_command(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::StoreSelectionRangeAndApply {
|
||||
ranges,
|
||||
rev,
|
||||
buffer_id,
|
||||
direction,
|
||||
current_selection,
|
||||
},
|
||||
Target::Auto,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_motion_mode_command(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
pub mod rename;
|
||||
pub mod rich_text;
|
||||
pub mod search;
|
||||
pub mod selection_range;
|
||||
pub mod settings;
|
||||
pub mod signature;
|
||||
pub mod source_control;
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
use lapce_rpc::buffer::BufferId;
|
||||
use lsp_types::{Range, SelectionRange};
|
||||
|
||||
/// Lsp [selectionRange](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#selectionRange)
|
||||
/// are used to do "smart" syntax selection. A buffer id, buffer revision and cursor position are
|
||||
/// stored along side [`lsp_type::SelectionRange`] data to ensure the current selection still apply
|
||||
/// to the current buffer.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SyntaxSelectionRanges {
|
||||
pub buffer_id: BufferId,
|
||||
pub rev: u64,
|
||||
pub last_known_selection: Option<(usize, usize)>,
|
||||
pub ranges: SelectionRange,
|
||||
pub current_selection: Option<usize>,
|
||||
}
|
||||
|
||||
/// Helper to request either the next or previous [`SyntaxSelectionRanges`],
|
||||
/// see: [`crate::editor::LapceUiCommand::ApplySelectionRange`]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum SelectionRangeDirection {
|
||||
Next,
|
||||
Previous,
|
||||
}
|
||||
|
||||
impl SyntaxSelectionRanges {
|
||||
/// Ensure the editor state match this selection range, if not
|
||||
/// a new SelectionRanges should be requested
|
||||
pub fn match_request(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
rev: u64,
|
||||
current_selection: Option<(usize, usize)>,
|
||||
) -> bool {
|
||||
if self.last_known_selection.is_some() {
|
||||
buffer_id == self.buffer_id
|
||||
&& rev == self.rev
|
||||
&& current_selection == self.last_known_selection
|
||||
} else {
|
||||
buffer_id == self.buffer_id && rev == self.rev
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next [`lsp_types::Range'] at `current_selection` depth
|
||||
pub fn next_range(&mut self) -> Option<Range> {
|
||||
match self.current_selection {
|
||||
None => self.current_selection = Some(0),
|
||||
Some(index) => {
|
||||
if index < self.count() - 1 {
|
||||
self.current_selection = Some(index + 1)
|
||||
}
|
||||
}
|
||||
};
|
||||
self.get()
|
||||
}
|
||||
|
||||
/// Get the previous [`lsp_types::Range'] at `current_selection` depth
|
||||
pub fn previous_range(&mut self) -> Option<Range> {
|
||||
if let Some(index) = self.current_selection {
|
||||
if index > 0 {
|
||||
self.current_selection = Some(index - 1)
|
||||
}
|
||||
}
|
||||
self.get()
|
||||
}
|
||||
|
||||
fn get(&self) -> Option<Range> {
|
||||
self.current_selection.and_then(|index| {
|
||||
if index == 0 {
|
||||
Some(self.ranges.range)
|
||||
} else {
|
||||
let mut current = self.ranges.parent.as_ref();
|
||||
|
||||
for _ in 1..index {
|
||||
current = current.and_then(|c| c.parent.as_ref());
|
||||
}
|
||||
|
||||
current.map(|c| c.range)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
let mut count = 1;
|
||||
let mut range = &self.ranges;
|
||||
while let Some(parent) = &range.parent {
|
||||
count += 1;
|
||||
range = parent;
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::CommandExecuted::No;
|
||||
use crate::selection_range::SyntaxSelectionRanges;
|
||||
use lapce_rpc::buffer::BufferId;
|
||||
use lsp_types::{Position, Range, SelectionRange};
|
||||
|
||||
#[test]
|
||||
fn should_get_next_selection_range() {
|
||||
let range_zero = Range {
|
||||
start: Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
end: Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
};
|
||||
let range_one = Range {
|
||||
start: Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
end: Position {
|
||||
line: 0,
|
||||
character: 2,
|
||||
},
|
||||
};
|
||||
let range_two = Range {
|
||||
start: Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
end: Position {
|
||||
line: 0,
|
||||
character: 4,
|
||||
},
|
||||
};
|
||||
|
||||
let mut syntax_selection = SyntaxSelectionRanges {
|
||||
buffer_id: BufferId(0),
|
||||
rev: 0,
|
||||
last_known_selection: None,
|
||||
ranges: SelectionRange {
|
||||
range: range_zero.clone(),
|
||||
parent: Some(Box::new(SelectionRange {
|
||||
range: range_one.clone(),
|
||||
parent: Some(Box::new(SelectionRange {
|
||||
range: range_two.clone(),
|
||||
parent: None,
|
||||
})),
|
||||
})),
|
||||
},
|
||||
current_selection: None,
|
||||
};
|
||||
|
||||
let range = syntax_selection.next_range();
|
||||
assert_eq!(range, Some(range_zero));
|
||||
assert_eq!(syntax_selection.current_selection, Some(0));
|
||||
|
||||
let range = syntax_selection.next_range();
|
||||
assert_eq!(range, Some(range_one));
|
||||
assert_eq!(syntax_selection.current_selection, Some(1));
|
||||
|
||||
let range = syntax_selection.next_range();
|
||||
assert_eq!(range, Some(range_two));
|
||||
assert_eq!(syntax_selection.current_selection, Some(2));
|
||||
|
||||
// Ensure we are not going out of bound
|
||||
let range = syntax_selection.next_range();
|
||||
assert_eq!(range, Some(range_two));
|
||||
assert_eq!(syntax_selection.current_selection, Some(2));
|
||||
|
||||
// Going backward now
|
||||
let range = syntax_selection.previous_range();
|
||||
assert_eq!(range, Some(range_one));
|
||||
assert_eq!(syntax_selection.current_selection, Some(1));
|
||||
|
||||
let range = syntax_selection.previous_range();
|
||||
assert_eq!(range, Some(range_zero));
|
||||
assert_eq!(syntax_selection.current_selection, Some(0));
|
||||
|
||||
// Ensure we are not going below zero
|
||||
let range = syntax_selection.previous_range();
|
||||
assert_eq!(range, Some(range_zero));
|
||||
assert_eq!(syntax_selection.current_selection, Some(0));
|
||||
}
|
||||
}
|
|
@ -708,6 +708,19 @@ fn handle_request(&mut self, id: RequestId, rpc: ProxyRequest) {
|
|||
};
|
||||
self.respond_rpc(id, result);
|
||||
}
|
||||
GetSelectionRange { positions, path } => {
|
||||
let proxy_rpc = self.proxy_rpc.clone();
|
||||
self.catalog_rpc.get_selection_range(
|
||||
path.as_path(),
|
||||
positions,
|
||||
move |_, result| {
|
||||
let result = result.map(|ranges| {
|
||||
ProxyResponse::GetSelectionRange { ranges }
|
||||
});
|
||||
proxy_rpc.handle_response(id, result);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
use lsp_types::request::{
|
||||
CodeActionRequest, Completion, DocumentSymbolRequest, Formatting,
|
||||
GotoDefinition, GotoTypeDefinition, GotoTypeDefinitionParams,
|
||||
GotoTypeDefinitionResponse, HoverRequest, InlayHintRequest,
|
||||
PrepareRenameRequest, References, Rename, Request, ResolveCompletionItem,
|
||||
GotoTypeDefinitionResponse, InlayHintRequest, PrepareRenameRequest, References,
|
||||
Rename, Request, ResolveCompletionItem, SelectionRangeRequest,
|
||||
SemanticTokensFullRequest, WorkspaceSymbol,
|
||||
};
|
||||
use lsp_types::{
|
||||
|
@ -24,12 +24,12 @@
|
|||
CompletionParams, CompletionResponse, DidOpenTextDocumentParams,
|
||||
DocumentFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
|
||||
FormattingOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover,
|
||||
HoverParams, InlayHint, InlayHintParams, Location, PartialResultParams,
|
||||
Position, PrepareRenameResponse, Range, ReferenceContext, ReferenceParams,
|
||||
RenameParams, SemanticTokens, SemanticTokensParams, SymbolInformation,
|
||||
TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, TextEdit,
|
||||
Url, VersionedTextDocumentIdentifier, WorkDoneProgressParams, WorkspaceEdit,
|
||||
WorkspaceSymbolParams,
|
||||
InlayHint, InlayHintParams, Location, PartialResultParams, Position,
|
||||
PrepareRenameResponse, Range, ReferenceContext, ReferenceParams, RenameParams,
|
||||
SelectionRange, SelectionRangeParams, SemanticTokens, SemanticTokensParams,
|
||||
SymbolInformation, TextDocumentIdentifier, TextDocumentItem,
|
||||
TextDocumentPositionParams, TextEdit, Url, VersionedTextDocumentIdentifier,
|
||||
WorkDoneProgressParams, WorkspaceEdit, WorkspaceSymbolParams,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
@ -683,6 +683,34 @@ pub fn get_semantic_tokens(
|
|||
);
|
||||
}
|
||||
|
||||
pub fn get_selection_range(
|
||||
&self,
|
||||
path: &Path,
|
||||
positions: Vec<Position>,
|
||||
cb: impl FnOnce(PluginId, Result<Vec<SelectionRange>, RpcError>)
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
let uri = Url::from_file_path(path).unwrap();
|
||||
let method = SelectionRangeRequest::METHOD;
|
||||
let params = SelectionRangeParams {
|
||||
text_document: TextDocumentIdentifier { uri },
|
||||
positions,
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
partial_result_params: Default::default(),
|
||||
};
|
||||
let language_id =
|
||||
Some(language_id_from_path(path).unwrap_or("").to_string());
|
||||
self.send_request_to_all_plugins(
|
||||
method,
|
||||
params,
|
||||
language_id,
|
||||
Some(path.to_path_buf()),
|
||||
cb,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn hover(
|
||||
&self,
|
||||
path: &Path,
|
||||
|
@ -690,16 +718,16 @@ pub fn hover(
|
|||
cb: impl FnOnce(PluginId, Result<Hover, RpcError>) + Clone + Send + 'static,
|
||||
) {
|
||||
let uri = Url::from_file_path(path).unwrap();
|
||||
let method = HoverRequest::METHOD;
|
||||
let params = HoverParams {
|
||||
text_document_position_params: TextDocumentPositionParams {
|
||||
text_document: TextDocumentIdentifier { uri },
|
||||
position,
|
||||
},
|
||||
let method = SelectionRangeRequest::METHOD;
|
||||
let params = SelectionRangeParams {
|
||||
text_document: TextDocumentIdentifier { uri },
|
||||
positions: vec![position],
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
partial_result_params: Default::default(),
|
||||
};
|
||||
let language_id =
|
||||
Some(language_id_from_path(path).unwrap_or("").to_string());
|
||||
|
||||
self.send_request_to_all_plugins(
|
||||
method,
|
||||
params,
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
CodeActionRequest, Completion, DocumentSymbolRequest, Formatting,
|
||||
GotoDefinition, GotoTypeDefinition, HoverRequest, Initialize,
|
||||
InlayHintRequest, PrepareRenameRequest, References, RegisterCapability,
|
||||
Rename, ResolveCompletionItem, SemanticTokensFullRequest,
|
||||
WorkDoneProgressCreate, WorkspaceSymbol,
|
||||
Rename, ResolveCompletionItem, SelectionRangeRequest,
|
||||
SemanticTokensFullRequest, WorkDoneProgressCreate, WorkspaceSymbol,
|
||||
},
|
||||
CodeActionProviderCapability, DidChangeTextDocumentParams,
|
||||
DidSaveTextDocumentParams, DocumentSelector, HoverProviderCapability, OneOf,
|
||||
|
@ -692,6 +692,9 @@ pub fn method_registered(&mut self, method: &'static str) -> bool {
|
|||
self.server_capabilities.rename_provider.is_some()
|
||||
}
|
||||
Rename::METHOD => self.server_capabilities.rename_provider.is_some(),
|
||||
SelectionRangeRequest::METHOD => {
|
||||
self.server_capabilities.selection_range_provider.is_some()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
use lsp_types::{
|
||||
request::GotoTypeDefinitionResponse, CodeActionResponse, CompletionItem,
|
||||
DocumentSymbolResponse, GotoDefinitionResponse, Hover, InlayHint, Location,
|
||||
Position, PrepareRenameResponse, SymbolInformation, TextDocumentItem, TextEdit,
|
||||
WorkspaceEdit,
|
||||
Position, PrepareRenameResponse, SelectionRange, SymbolInformation,
|
||||
TextDocumentItem, TextEdit, WorkspaceEdit,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -61,6 +61,10 @@ pub enum ProxyRequest {
|
|||
buffer_id: BufferId,
|
||||
position: Position,
|
||||
},
|
||||
GetSelectionRange {
|
||||
path: PathBuf,
|
||||
positions: Vec<Position>,
|
||||
},
|
||||
GetReferences {
|
||||
path: PathBuf,
|
||||
position: Position,
|
||||
|
@ -255,6 +259,9 @@ pub enum ProxyResponse {
|
|||
GetWorkspaceSymbols {
|
||||
symbols: Vec<SymbolInformation>,
|
||||
},
|
||||
GetSelectionRange {
|
||||
ranges: Vec<SelectionRange>,
|
||||
},
|
||||
GetInlayHints {
|
||||
hints: Vec<InlayHint>,
|
||||
},
|
||||
|
@ -732,6 +739,15 @@ pub fn git_discard_files_changes(&self, files: Vec<PathBuf>) {
|
|||
pub fn git_discard_workspace_changes(&self) {
|
||||
self.notification(ProxyNotification::GitDiscardWorkspaceChanges {});
|
||||
}
|
||||
|
||||
pub fn get_selection_range(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
positions: Vec<Position>,
|
||||
f: impl ProxyCallback + 'static,
|
||||
) {
|
||||
self.request_async(ProxyRequest::GetSelectionRange { path, positions }, f);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProxyRpcHandler {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
use lapce_data::menu::MenuKind;
|
||||
use lapce_data::palette::PaletteStatus;
|
||||
use lapce_data::panel::{PanelData, PanelKind};
|
||||
use lapce_data::selection_range::SyntaxSelectionRanges;
|
||||
use lapce_data::{
|
||||
command::{
|
||||
LapceCommand, LapceUICommand, LapceWorkbenchCommand, LAPCE_UI_COMMAND,
|
||||
|
@ -1911,62 +1912,159 @@ fn event(
|
|||
}
|
||||
}
|
||||
Event::Command(cmd) if cmd.is(LAPCE_UI_COMMAND) => {
|
||||
let cmd = cmd.get_unchecked(LAPCE_UI_COMMAND);
|
||||
if let LapceUICommand::ShowCodeActions(point) = cmd {
|
||||
let editor_data = data.editor_view_content(self.view_id);
|
||||
if let Some(actions) = editor_data.current_code_actions() {
|
||||
if !actions.is_empty() {
|
||||
let mut menu = druid::Menu::new("");
|
||||
match cmd.get_unchecked(LAPCE_UI_COMMAND) {
|
||||
LapceUICommand::ShowCodeActions(point) => {
|
||||
let editor_data = data.editor_view_content(self.view_id);
|
||||
if let Some(actions) = editor_data.current_code_actions() {
|
||||
if !actions.is_empty() {
|
||||
let mut menu = druid::Menu::new("");
|
||||
|
||||
for action in actions.iter() {
|
||||
let title = match action {
|
||||
CodeActionOrCommand::Command(c) => {
|
||||
c.title.clone()
|
||||
}
|
||||
CodeActionOrCommand::CodeAction(a) => {
|
||||
a.title.clone()
|
||||
}
|
||||
};
|
||||
let mut item = druid::MenuItem::new(title);
|
||||
item = item.command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::RunCodeAction(action.clone()),
|
||||
Target::Widget(editor_data.view_id),
|
||||
));
|
||||
menu = menu.entry(item);
|
||||
for action in actions.iter() {
|
||||
let title = match action {
|
||||
CodeActionOrCommand::Command(c) => {
|
||||
c.title.clone()
|
||||
}
|
||||
CodeActionOrCommand::CodeAction(a) => {
|
||||
a.title.clone()
|
||||
}
|
||||
};
|
||||
let mut item = druid::MenuItem::new(title);
|
||||
item = item.command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::RunCodeAction(
|
||||
action.clone(),
|
||||
),
|
||||
Target::Widget(editor_data.view_id),
|
||||
));
|
||||
menu = menu.entry(item);
|
||||
}
|
||||
|
||||
let point = point.unwrap_or_else(|| {
|
||||
let offset = editor_data.editor.cursor.offset();
|
||||
let (line, col) = editor_data
|
||||
.doc
|
||||
.buffer()
|
||||
.offset_to_line_col(offset);
|
||||
|
||||
let phantom_text = editor_data
|
||||
.doc
|
||||
.line_phantom_text(&data.config, line);
|
||||
|
||||
let col = phantom_text.col_at(col);
|
||||
|
||||
let x = editor_data
|
||||
.doc
|
||||
.line_point_of_line_col(
|
||||
ctx.text(),
|
||||
line,
|
||||
col,
|
||||
editor_data.config.editor.font_size,
|
||||
&editor_data.config,
|
||||
)
|
||||
.x;
|
||||
let y = editor_data.config.editor.line_height()
|
||||
as f64
|
||||
* (line + 1) as f64;
|
||||
ctx.to_window(Point::new(x, y))
|
||||
});
|
||||
ctx.show_context_menu::<LapceData>(menu, point);
|
||||
}
|
||||
|
||||
let point = point.unwrap_or_else(|| {
|
||||
let offset = editor_data.editor.cursor.offset();
|
||||
let (line, col) = editor_data
|
||||
.doc
|
||||
.buffer()
|
||||
.offset_to_line_col(offset);
|
||||
|
||||
let phantom_text = editor_data
|
||||
.doc
|
||||
.line_phantom_text(&data.config, line);
|
||||
|
||||
let col = phantom_text.col_at(col);
|
||||
|
||||
let x = editor_data
|
||||
.doc
|
||||
.line_point_of_line_col(
|
||||
ctx.text(),
|
||||
line,
|
||||
col,
|
||||
editor_data.config.editor.font_size,
|
||||
&editor_data.config,
|
||||
)
|
||||
.x;
|
||||
let y = editor_data.config.editor.line_height()
|
||||
as f64
|
||||
* (line + 1) as f64;
|
||||
ctx.to_window(Point::new(x, y))
|
||||
});
|
||||
ctx.show_context_menu::<LapceData>(menu, point);
|
||||
}
|
||||
}
|
||||
LapceUICommand::ApplySelectionRange {
|
||||
buffer_id,
|
||||
rev,
|
||||
direction,
|
||||
} => {
|
||||
if let Some(editor) = data
|
||||
.main_split
|
||||
.active
|
||||
.and_then(|active| data.main_split.editors.get(&active))
|
||||
.cloned()
|
||||
{
|
||||
let mut editor_data =
|
||||
data.editor_view_content(editor.view_id);
|
||||
|
||||
let orig_doc =
|
||||
data.main_split.editor_doc(editor.view_id);
|
||||
|
||||
if orig_doc.id() != *buffer_id || orig_doc.rev() != *rev
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = Arc::make_mut(&mut editor_data.doc);
|
||||
|
||||
if let Some(selection) =
|
||||
doc.change_syntax_selection(*direction)
|
||||
{
|
||||
Arc::make_mut(&mut editor_data.editor)
|
||||
.cursor
|
||||
.update_selection(orig_doc.buffer(), selection);
|
||||
data.update_from_editor_buffer_data(
|
||||
editor_data,
|
||||
&editor,
|
||||
&orig_doc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
LapceUICommand::StoreSelectionRangeAndApply {
|
||||
rev,
|
||||
buffer_id,
|
||||
current_selection,
|
||||
ranges,
|
||||
direction,
|
||||
} => {
|
||||
if let Some(editor) = data
|
||||
.main_split
|
||||
.active
|
||||
.and_then(|active| data.main_split.editors.get(&active))
|
||||
.cloned()
|
||||
{
|
||||
let mut editor_data =
|
||||
data.editor_view_content(editor.view_id);
|
||||
let orig_doc =
|
||||
data.main_split.editor_doc(editor.view_id);
|
||||
|
||||
if orig_doc.id() != *buffer_id || orig_doc.rev() != *rev
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut doc = Arc::make_mut(&mut editor_data.doc);
|
||||
if let (_, Some(ranges)) = (
|
||||
&doc.syntax_selection_range,
|
||||
ranges.first().cloned(),
|
||||
) {
|
||||
doc.syntax_selection_range =
|
||||
Some(SyntaxSelectionRanges {
|
||||
buffer_id: *buffer_id,
|
||||
rev: *rev,
|
||||
last_known_selection: *current_selection,
|
||||
ranges,
|
||||
current_selection: None,
|
||||
});
|
||||
};
|
||||
|
||||
data.update_from_editor_buffer_data(
|
||||
editor_data,
|
||||
&editor,
|
||||
&orig_doc,
|
||||
);
|
||||
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ApplySelectionRange {
|
||||
buffer_id: *buffer_id,
|
||||
rev: *rev,
|
||||
direction: *direction,
|
||||
},
|
||||
Target::Auto,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
Loading…
Reference in New Issue