Merge pull request #1237 from zed-industries/jump-to-definition
Mouse jump to definition
This commit is contained in:
@@ -1980,13 +1980,13 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(definitions_1.len(), 1);
|
||||
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
|
||||
let target_buffer = definitions_1[0].buffer.read(cx);
|
||||
let target_buffer = definitions_1[0].target.buffer.read(cx);
|
||||
assert_eq!(
|
||||
target_buffer.text(),
|
||||
"const TWO: usize = 2;\nconst THREE: usize = 3;"
|
||||
);
|
||||
assert_eq!(
|
||||
definitions_1[0].range.to_point(target_buffer),
|
||||
definitions_1[0].target.range.to_point(target_buffer),
|
||||
Point::new(0, 6)..Point::new(0, 9)
|
||||
);
|
||||
});
|
||||
@@ -2009,17 +2009,20 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(definitions_2.len(), 1);
|
||||
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
|
||||
let target_buffer = definitions_2[0].buffer.read(cx);
|
||||
let target_buffer = definitions_2[0].target.buffer.read(cx);
|
||||
assert_eq!(
|
||||
target_buffer.text(),
|
||||
"const TWO: usize = 2;\nconst THREE: usize = 3;"
|
||||
);
|
||||
assert_eq!(
|
||||
definitions_2[0].range.to_point(target_buffer),
|
||||
definitions_2[0].target.range.to_point(target_buffer),
|
||||
Point::new(1, 6)..Point::new(1, 11)
|
||||
);
|
||||
});
|
||||
assert_eq!(definitions_1[0].buffer, definitions_2[0].buffer);
|
||||
assert_eq!(
|
||||
definitions_1[0].target.buffer,
|
||||
definitions_2[0].target.buffer
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
@@ -2554,7 +2557,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
let buffer_b2 = buffer_b2.await.unwrap();
|
||||
let definitions = definitions.await.unwrap();
|
||||
assert_eq!(definitions.len(), 1);
|
||||
assert_eq!(definitions[0].buffer, buffer_b2);
|
||||
assert_eq!(definitions[0].target.buffer, buffer_b2);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
@@ -5593,9 +5596,9 @@ impl TestClient {
|
||||
log::info!("{}: detaching definitions request", guest_username);
|
||||
cx.update(|cx| definitions.detach_and_log_err(cx));
|
||||
} else {
|
||||
client
|
||||
.buffers
|
||||
.extend(definitions.await?.into_iter().map(|loc| loc.buffer));
|
||||
client.buffers.extend(
|
||||
definitions.await?.into_iter().map(|loc| loc.target.buffer),
|
||||
);
|
||||
}
|
||||
}
|
||||
50..=54 => {
|
||||
|
||||
@@ -474,6 +474,14 @@ impl DisplaySnapshot {
|
||||
pub fn longest_row(&self) -> u32 {
|
||||
self.blocks_snapshot.longest_row()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn highlight_ranges<Tag: ?Sized + 'static>(
|
||||
&self,
|
||||
) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
|
||||
let type_id = TypeId::of::<Tag>();
|
||||
self.text_highlights.get(&Some(type_id)).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
|
||||
+36
-19
@@ -2,6 +2,7 @@ pub mod display_map;
|
||||
mod element;
|
||||
mod hover_popover;
|
||||
pub mod items;
|
||||
mod link_go_to_definition;
|
||||
pub mod movement;
|
||||
mod multi_buffer;
|
||||
pub mod selections_collection;
|
||||
@@ -37,13 +38,14 @@ use language::{
|
||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal,
|
||||
TransactionId,
|
||||
};
|
||||
use link_go_to_definition::LinkGoToDefinitionState;
|
||||
use multi_buffer::MultiBufferChunks;
|
||||
pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
||||
ToPoint,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{Project, ProjectPath, ProjectTransaction};
|
||||
use project::{LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
@@ -314,6 +316,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_async_action(Editor::find_all_references);
|
||||
|
||||
hover_popover::init(cx);
|
||||
link_go_to_definition::init(cx);
|
||||
|
||||
workspace::register_project_item::<Editor>(cx);
|
||||
workspace::register_followable_item::<Editor>(cx);
|
||||
@@ -432,6 +435,7 @@ pub struct Editor {
|
||||
input_enabled: bool,
|
||||
leader_replica_id: Option<u16>,
|
||||
hover_state: HoverState,
|
||||
link_go_to_definition_state: LinkGoToDefinitionState,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
@@ -1021,6 +1025,7 @@ impl Editor {
|
||||
input_enabled: true,
|
||||
leader_replica_id: None,
|
||||
hover_state: Default::default(),
|
||||
link_go_to_definition_state: Default::default(),
|
||||
};
|
||||
this.end_selection(cx);
|
||||
|
||||
@@ -4597,24 +4602,7 @@ impl Editor {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let definitions = definitions.await?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let nav_history = workspace.active_pane().read(cx).nav_history().clone();
|
||||
for definition in definitions {
|
||||
let range = definition.range.to_offset(definition.buffer.read(cx));
|
||||
|
||||
let target_editor_handle = workspace.open_project_item(definition.buffer, cx);
|
||||
target_editor_handle.update(cx, |target_editor, cx| {
|
||||
// When selecting a definition in a different buffer, disable the nav history
|
||||
// to avoid creating a history entry at the previous cursor location.
|
||||
if editor_handle != target_editor_handle {
|
||||
nav_history.borrow_mut().disable();
|
||||
}
|
||||
target_editor.change_selections(Some(Autoscroll::Center), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
|
||||
nav_history.borrow_mut().enable();
|
||||
});
|
||||
}
|
||||
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||
});
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
@@ -4622,6 +4610,35 @@ impl Editor {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn navigate_to_definitions(
|
||||
workspace: &mut Workspace,
|
||||
editor_handle: ViewHandle<Editor>,
|
||||
definitions: Vec<LocationLink>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let nav_history = workspace.active_pane().read(cx).nav_history().clone();
|
||||
for definition in definitions {
|
||||
let range = definition
|
||||
.target
|
||||
.range
|
||||
.to_offset(definition.target.buffer.read(cx));
|
||||
|
||||
let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx);
|
||||
target_editor_handle.update(cx, |target_editor, cx| {
|
||||
// When selecting a definition in a different buffer, disable the nav history
|
||||
// to avoid creating a history entry at the previous cursor location.
|
||||
if editor_handle != target_editor_handle {
|
||||
nav_history.borrow_mut().disable();
|
||||
}
|
||||
target_editor.change_selections(Some(Autoscroll::Center), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
|
||||
nav_history.borrow_mut().enable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_all_references(
|
||||
workspace: &mut Workspace,
|
||||
_: &FindAllReferences,
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
||||
hover_popover::HoverAt,
|
||||
link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink},
|
||||
EditorStyle,
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
@@ -104,7 +105,7 @@ impl EditorElement {
|
||||
fn mouse_down(
|
||||
&self,
|
||||
position: Vector2F,
|
||||
_: bool,
|
||||
cmd: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
mut click_count: usize,
|
||||
@@ -112,6 +113,14 @@ impl EditorElement {
|
||||
paint: &mut PaintState,
|
||||
cx: &mut EventContext,
|
||||
) -> bool {
|
||||
if cmd && paint.text_bounds.contains_point(position) {
|
||||
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
|
||||
if overshoot.is_zero() {
|
||||
cx.dispatch_action(GoToFetchedDefinition { point });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if paint.gutter_bounds.contains_point(position) {
|
||||
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
||||
} else if !paint.text_bounds.contains_point(position) {
|
||||
@@ -203,6 +212,52 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_moved(
|
||||
&self,
|
||||
position: Vector2F,
|
||||
cmd: bool,
|
||||
layout: &LayoutState,
|
||||
paint: &PaintState,
|
||||
cx: &mut EventContext,
|
||||
) -> bool {
|
||||
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
|
||||
// Don't trigger hover popover if mouse is hovering over context menu
|
||||
let point = if paint.text_bounds.contains_point(position) {
|
||||
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
|
||||
if overshoot.is_zero() {
|
||||
Some(point)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.dispatch_action(UpdateGoToDefinitionLink {
|
||||
point,
|
||||
cmd_held: cmd,
|
||||
});
|
||||
|
||||
if paint
|
||||
.context_menu_bounds
|
||||
.map_or(false, |context_menu_bounds| {
|
||||
context_menu_bounds.contains_point(position)
|
||||
})
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if paint
|
||||
.hover_bounds
|
||||
.map_or(false, |hover_bounds| hover_bounds.contains_point(position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cx.dispatch_action(HoverAt { point });
|
||||
true
|
||||
}
|
||||
|
||||
fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
|
||||
let view = self.view.upgrade(cx.app).unwrap();
|
||||
|
||||
@@ -218,6 +273,11 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
|
||||
cx.dispatch_action(CmdChanged { cmd_down: cmd });
|
||||
false
|
||||
}
|
||||
|
||||
fn scroll(
|
||||
&self,
|
||||
position: Vector2F,
|
||||
@@ -367,9 +427,14 @@ impl EditorElement {
|
||||
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
|
||||
|
||||
cx.scene.push_layer(Some(bounds));
|
||||
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
bounds,
|
||||
style: CursorStyle::IBeam,
|
||||
style: if !view.link_go_to_definition_state.definitions.is_empty() {
|
||||
CursorStyle::PointingHand
|
||||
} else {
|
||||
CursorStyle::IBeam
|
||||
},
|
||||
});
|
||||
|
||||
for (range, color) in &layout.highlighted_ranges {
|
||||
@@ -1417,7 +1482,7 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
),
|
||||
Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx),
|
||||
Event::LeftMouseDragged { position } => {
|
||||
Event::LeftMouseDragged { position, .. } => {
|
||||
self.mouse_dragged(*position, layout, paint, cx)
|
||||
}
|
||||
Event::ScrollWheel {
|
||||
@@ -1426,40 +1491,11 @@ impl Element for EditorElement {
|
||||
precise,
|
||||
} => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
||||
Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
|
||||
Event::MouseMoved { position, .. } => {
|
||||
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
|
||||
// Don't trigger hover popover if mouse is hovering over context menu
|
||||
if paint
|
||||
.context_menu_bounds
|
||||
.map_or(false, |context_menu_bounds| {
|
||||
context_menu_bounds.contains_point(*position)
|
||||
})
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if paint
|
||||
.hover_bounds
|
||||
.map_or(false, |hover_bounds| hover_bounds.contains_point(*position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let point = if paint.text_bounds.contains_point(*position) {
|
||||
let (point, overshoot) =
|
||||
paint.point_for_position(&self.snapshot(cx), layout, *position);
|
||||
if overshoot.is_zero() {
|
||||
Some(point)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.dispatch_action(HoverAt { point });
|
||||
true
|
||||
Event::ModifiersChanged { cmd, .. } => self.modifiers_changed(*cmd, cx),
|
||||
Event::MouseMoved { position, cmd, .. } => {
|
||||
self.mouse_moved(*position, *cmd, layout, paint, cx)
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
use std::{
|
||||
ops::Range,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{Flex, MouseEventHandler, Padding, Text},
|
||||
@@ -12,6 +7,7 @@ use gpui::{
|
||||
};
|
||||
use language::Bias;
|
||||
use project::{HoverBlock, Project};
|
||||
use std::{ops::Range, time::Duration};
|
||||
use util::TryFutureExt;
|
||||
|
||||
use crate::{
|
||||
@@ -60,7 +56,6 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
|
||||
// only notify the context once
|
||||
if editor.hover_state.popover.is_some() {
|
||||
editor.hover_state.popover = None;
|
||||
editor.hover_state.hidden_at = Some(cx.background().now());
|
||||
did_hide = true;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -242,7 +237,6 @@ fn show_hover(
|
||||
#[derive(Default)]
|
||||
pub struct HoverState {
|
||||
pub popover: Option<HoverPopover>,
|
||||
pub hidden_at: Option<Instant>,
|
||||
pub triggered_from: Option<Anchor>,
|
||||
pub symbol_range: Option<Range<Anchor>>,
|
||||
pub task: Option<Task<Option<()>>>,
|
||||
|
||||
@@ -1,58 +1,611 @@
|
||||
use std::{
|
||||
ops::Range,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{Flex, MouseEventHandler, Padding, Text},
|
||||
impl_internal_actions,
|
||||
platform::CursorStyle,
|
||||
Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext,
|
||||
};
|
||||
use language::Bias;
|
||||
use project::{HoverBlock, Project};
|
||||
use gpui::{impl_internal_actions, MutableAppContext, Task, ViewContext};
|
||||
use language::{Bias, ToOffset};
|
||||
use project::LocationLink;
|
||||
use settings::Settings;
|
||||
use util::TryFutureExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
|
||||
EditorStyle,
|
||||
};
|
||||
use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, Select, SelectPhase};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct FetchDefinition {
|
||||
pub struct UpdateGoToDefinitionLink {
|
||||
pub point: Option<DisplayPoint>,
|
||||
pub cmd_held: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CmdChanged {
|
||||
pub cmd_down: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct GoToFetchedDefinition {
|
||||
pub point: Option<DisplayPoint>,
|
||||
pub point: DisplayPoint,
|
||||
}
|
||||
|
||||
impl_internal_actions!(edtior, [FetchDefinition, GoToFetchedDefinition]);
|
||||
impl_internal_actions!(
|
||||
editor,
|
||||
[UpdateGoToDefinitionLink, CmdChanged, GoToFetchedDefinition]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(fetch_definition);
|
||||
cx.add_action(update_go_to_definition_link);
|
||||
cx.add_action(cmd_changed);
|
||||
cx.add_action(go_to_fetched_definition);
|
||||
}
|
||||
|
||||
pub fn fetch_definition(
|
||||
editor: &mut Editor,
|
||||
FetchDefinition { point }: &FetchDefinition,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn go_to_fetched_definition(
|
||||
editor: &mut Editor,
|
||||
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LinkGoToDefinitionState {
|
||||
pub triggered_from
|
||||
pub last_mouse_location: Option<Anchor>,
|
||||
pub symbol_range: Option<Range<Anchor>>,
|
||||
pub definitions: Vec<LocationLink>,
|
||||
pub task: Option<Task<Option<()>>>,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_go_to_definition_link(
|
||||
editor: &mut Editor,
|
||||
&UpdateGoToDefinitionLink { point, cmd_held }: &UpdateGoToDefinitionLink,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
// Store new mouse point as an anchor
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let point = point.map(|point| {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left))
|
||||
});
|
||||
|
||||
// If the new point is the same as the previously stored one, return early
|
||||
if let (Some(a), Some(b)) = (
|
||||
&point,
|
||||
&editor.link_go_to_definition_state.last_mouse_location,
|
||||
) {
|
||||
if a.cmp(&b, &snapshot.buffer_snapshot).is_eq() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
editor.link_go_to_definition_state.last_mouse_location = point.clone();
|
||||
if cmd_held {
|
||||
if let Some(point) = point {
|
||||
show_link_definition(editor, point, snapshot, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hide_link_definition(editor, cx);
|
||||
}
|
||||
|
||||
pub fn cmd_changed(
|
||||
editor: &mut Editor,
|
||||
&CmdChanged { cmd_down }: &CmdChanged,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if let Some(point) = editor
|
||||
.link_go_to_definition_state
|
||||
.last_mouse_location
|
||||
.clone()
|
||||
{
|
||||
if cmd_down {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
show_link_definition(editor, point.clone(), snapshot, cx);
|
||||
} else {
|
||||
hide_link_definition(editor, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_link_definition(
|
||||
editor: &mut Editor,
|
||||
trigger_point: Anchor,
|
||||
snapshot: EditorSnapshot,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if editor.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (buffer, buffer_position) = if let Some(output) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(trigger_point.clone(), cx)
|
||||
{
|
||||
output
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let excerpt_id = if let Some((excerpt_id, _, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(trigger_point.clone(), cx)
|
||||
{
|
||||
excerpt_id
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = if let Some(project) = editor.project.clone() {
|
||||
project
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Don't request again if the location is within the symbol region of a previous request
|
||||
if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
|
||||
if symbol_range
|
||||
.start
|
||||
.cmp(&trigger_point, &snapshot.buffer_snapshot)
|
||||
.is_le()
|
||||
&& symbol_range
|
||||
.end
|
||||
.cmp(&trigger_point, &snapshot.buffer_snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let task = cx.spawn_weak(|this, mut cx| {
|
||||
async move {
|
||||
// query the LSP for definition info
|
||||
let definition_request = cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.definition(&buffer, buffer_position.clone(), cx)
|
||||
})
|
||||
});
|
||||
|
||||
let result = definition_request.await.ok().map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().map(|origin| {
|
||||
let start = snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
|
||||
let end = snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
|
||||
|
||||
start..end
|
||||
})
|
||||
}),
|
||||
definition_result,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
// Clear any existing highlights
|
||||
this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
||||
this.link_go_to_definition_state.symbol_range = result
|
||||
.as_ref()
|
||||
.and_then(|(symbol_range, _)| symbol_range.clone());
|
||||
|
||||
if let Some((symbol_range, definitions)) = result {
|
||||
this.link_go_to_definition_state.definitions = definitions.clone();
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
// Only show highlight if there exists a definition to jump to that doesn't contain
|
||||
// the current location.
|
||||
if definitions.iter().any(|definition| {
|
||||
let target = &definition.target;
|
||||
if target.buffer == buffer {
|
||||
let range = &target.range;
|
||||
// Expand range by one character as lsp definition ranges include positions adjacent
|
||||
// but not contained by the symbol range
|
||||
let start = buffer_snapshot.clip_offset(
|
||||
range.start.to_offset(&buffer_snapshot).saturating_sub(1),
|
||||
Bias::Left,
|
||||
);
|
||||
let end = buffer_snapshot.clip_offset(
|
||||
range.end.to_offset(&buffer_snapshot) + 1,
|
||||
Bias::Right,
|
||||
);
|
||||
let offset = buffer_position.to_offset(&buffer_snapshot);
|
||||
!(start <= offset && end >= offset)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}) {
|
||||
// If no symbol range returned from language server, use the surrounding word.
|
||||
let highlight_range = symbol_range.unwrap_or_else(|| {
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
let (offset_range, _) = snapshot.surrounding_word(trigger_point);
|
||||
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end)
|
||||
});
|
||||
|
||||
// Highlight symbol using theme link definition highlight style
|
||||
let style = cx.global::<Settings>().theme.editor.link_definition;
|
||||
this.highlight_text::<LinkGoToDefinitionState>(
|
||||
vec![highlight_range],
|
||||
style,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
hide_link_definition(this, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
.log_err()
|
||||
});
|
||||
|
||||
editor.link_go_to_definition_state.task = Some(task);
|
||||
}
|
||||
|
||||
pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
if editor.link_go_to_definition_state.symbol_range.is_some()
|
||||
|| !editor.link_go_to_definition_state.definitions.is_empty()
|
||||
{
|
||||
editor.link_go_to_definition_state.symbol_range.take();
|
||||
editor.link_go_to_definition_state.definitions.clear();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
editor.link_go_to_definition_state.task = None;
|
||||
|
||||
editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
||||
}
|
||||
|
||||
pub fn go_to_fetched_definition(
|
||||
workspace: &mut Workspace,
|
||||
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let active_item = workspace.active_item(cx);
|
||||
let editor_handle = if let Some(editor) = active_item
|
||||
.as_ref()
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
{
|
||||
editor
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let definitions = editor_handle.update(cx, |editor, cx| {
|
||||
let definitions = editor.link_go_to_definition_state.definitions.clone();
|
||||
hide_link_definition(editor, cx);
|
||||
definitions
|
||||
});
|
||||
|
||||
if !definitions.is_empty() {
|
||||
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||
} else {
|
||||
editor_handle.update(cx, |editor, cx| {
|
||||
editor.select(
|
||||
&Select(SelectPhase::Begin {
|
||||
position: point.clone(),
|
||||
add: false,
|
||||
click_count: 1,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
Editor::go_to_definition(workspace, &GoToDefinition, cx);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::StreamExt;
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::test::EditorLspTestContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn |test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Basic hold cmd, expect highlight in region if response contains definition
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
fn test()
|
||||
do_w|ork();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn [do_work]()
|
||||
test();"});
|
||||
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Unpress cmd causes highlight to go away
|
||||
cx.update_editor(|editor, cx| {
|
||||
cmd_changed(editor, &CmdChanged { cmd_down: false }, cx);
|
||||
});
|
||||
// Assert no link highlights
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Response without source range still highlights word
|
||||
cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
// No origin range
|
||||
origin_selection_range: None,
|
||||
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
println!("tag");
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Moving mouse to location with no response dismisses highlight
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
f|n test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
// No definitions returned
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// Assert no link highlights
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Move mouse without cmd and then pressing cmd triggers highlight
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
te|st();"});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// Assert no link highlights
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn [test]()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
cmd_changed(editor, &CmdChanged { cmd_down: true }, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
|
||||
// Moving within symbol range doesn't re-request
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
tes|t();"});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
|
||||
// Cmd click with existing definition doesn't re-request and dismisses highlight
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
|
||||
});
|
||||
// Assert selection moved to to definition
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
// Empty definition response to make sure we aren't hitting the lsp and using
|
||||
// the cached location instead
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn [test}()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
// Assert no link highlights after jump
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
// Cmd click without existing definition requests and jumps
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
fn test()
|
||||
do_w|ork();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn [do_work]()
|
||||
test();"});
|
||||
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn [do_work}()
|
||||
test();"});
|
||||
}
|
||||
}
|
||||
|
||||
+66
-23
@@ -7,19 +7,20 @@ use futures::StreamExt;
|
||||
use indoc::indoc;
|
||||
|
||||
use collections::BTreeMap;
|
||||
use gpui::{keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
|
||||
use gpui::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
|
||||
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection};
|
||||
use project::{FakeFs, Project};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use util::{
|
||||
set_eq,
|
||||
assert_set_eq, set_eq,
|
||||
test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
|
||||
};
|
||||
use workspace::{pane, AppState, Workspace, WorkspaceHandle};
|
||||
|
||||
use crate::{
|
||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
multi_buffer::ToPointUtf16,
|
||||
Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
|
||||
AnchorRangeExt, Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -215,6 +216,24 @@ impl<'a> EditorTestContext<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
|
||||
let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
|
||||
assert_eq!(unmarked, self.buffer_text());
|
||||
|
||||
let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
|
||||
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
let actual_ranges: Vec<Range<usize>> = snapshot
|
||||
.display_snapshot
|
||||
.highlight_ranges::<Tag>()
|
||||
.map(|ranges| ranges.as_ref().clone().1)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||
.collect();
|
||||
|
||||
assert_set_eq!(asserted_ranges, actual_ranges);
|
||||
}
|
||||
|
||||
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
|
||||
let mut empty_selections = Vec::new();
|
||||
let mut reverse_selections = Vec::new();
|
||||
@@ -390,6 +409,7 @@ impl<'a> DerefMut for EditorTestContext<'a> {
|
||||
pub struct EditorLspTestContext<'a> {
|
||||
pub cx: EditorTestContext<'a>,
|
||||
pub lsp: lsp::FakeLanguageServer,
|
||||
pub workspace: ViewHandle<Workspace>,
|
||||
}
|
||||
|
||||
impl<'a> EditorLspTestContext<'a> {
|
||||
@@ -398,8 +418,17 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &'a mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext<'a> {
|
||||
use json::json;
|
||||
|
||||
cx.update(|cx| {
|
||||
crate::init(cx);
|
||||
pane::init(cx);
|
||||
});
|
||||
|
||||
let params = cx.update(AppState::test);
|
||||
|
||||
let file_name = format!(
|
||||
"/file.{}",
|
||||
"file.{}",
|
||||
language
|
||||
.path_suffixes()
|
||||
.first()
|
||||
@@ -411,30 +440,36 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.background().clone());
|
||||
fs.insert_file(file_name.clone(), "".to_string()).await;
|
||||
|
||||
let project = Project::test(fs, [file_name.as_ref()], cx).await;
|
||||
let project = Project::test(params.fs.clone(), [], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer(file_name, cx))
|
||||
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "dir": { file_name: "" }}))
|
||||
.await;
|
||||
|
||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/root", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let (window_id, editor) = cx.update(|cx| {
|
||||
cx.set_global(Settings::test(cx));
|
||||
crate::init(cx);
|
||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let item = workspace
|
||||
.update(cx, |workspace, cx| workspace.open_path(file, true, cx))
|
||||
.await
|
||||
.expect("Could not open test file");
|
||||
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
|
||||
});
|
||||
|
||||
editor.update(cx, |_, cx| cx.focus_self());
|
||||
|
||||
(window_id, editor)
|
||||
let editor = cx.update(|cx| {
|
||||
item.act_as::<Editor>(cx)
|
||||
.expect("Opened test file wasn't an editor")
|
||||
});
|
||||
editor.update(cx, |_, cx| cx.focus_self());
|
||||
|
||||
let lsp = fake_servers.next().await.unwrap();
|
||||
|
||||
@@ -445,6 +480,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
editor,
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +529,13 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
lsp::Range { start, end }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_workspace<F, T>(&mut self, update: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
||||
{
|
||||
self.workspace.update(self.cx.cx, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EditorLspTestContext<'a> {
|
||||
|
||||
@@ -13,6 +13,16 @@ pub enum Event {
|
||||
input: Option<String>,
|
||||
is_held: bool,
|
||||
},
|
||||
KeyUp {
|
||||
keystroke: Keystroke,
|
||||
input: Option<String>,
|
||||
},
|
||||
ModifiersChanged {
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
cmd: bool,
|
||||
},
|
||||
ScrollWheel {
|
||||
position: Vector2F,
|
||||
delta: Vector2F,
|
||||
@@ -32,6 +42,10 @@ pub enum Event {
|
||||
},
|
||||
LeftMouseDragged {
|
||||
position: Vector2F,
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
cmd: bool,
|
||||
},
|
||||
RightMouseDown {
|
||||
position: Vector2F,
|
||||
@@ -61,6 +75,10 @@ pub enum Event {
|
||||
MouseMoved {
|
||||
position: Vector2F,
|
||||
left_mouse_down: bool,
|
||||
ctrl: bool,
|
||||
cmd: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -68,10 +86,12 @@ impl Event {
|
||||
pub fn position(&self) -> Option<Vector2F> {
|
||||
match self {
|
||||
Event::KeyDown { .. } => None,
|
||||
Event::KeyUp { .. } => None,
|
||||
Event::ModifiersChanged { .. } => None,
|
||||
Event::ScrollWheel { position, .. }
|
||||
| Event::LeftMouseDown { position, .. }
|
||||
| Event::LeftMouseUp { position, .. }
|
||||
| Event::LeftMouseDragged { position }
|
||||
| Event::LeftMouseDragged { position, .. }
|
||||
| Event::RightMouseDown { position, .. }
|
||||
| Event::RightMouseUp { position, .. }
|
||||
| Event::NavigateMouseDown { position, .. }
|
||||
|
||||
@@ -52,6 +52,20 @@ impl Event {
|
||||
}
|
||||
|
||||
match event_type {
|
||||
NSEventType::NSFlagsChanged => {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
|
||||
Some(Self::ModifiersChanged {
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
cmd,
|
||||
})
|
||||
}
|
||||
NSEventType::NSKeyDown => {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
@@ -60,71 +74,7 @@ impl Event {
|
||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||
|
||||
let unmodified_chars = CStr::from_ptr(
|
||||
native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char,
|
||||
)
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
let mut input = None;
|
||||
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
|
||||
use cocoa::appkit::*;
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const ENTER_KEY: u16 = 0x0d;
|
||||
const ESCAPE_KEY: u16 = 0x1b;
|
||||
const TAB_KEY: u16 = 0x09;
|
||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||
const SPACE_KEY: u16 = b' ' as u16;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
match first_char as u16 {
|
||||
SPACE_KEY => {
|
||||
input = Some(" ".to_string());
|
||||
"space"
|
||||
}
|
||||
BACKSPACE_KEY => "backspace",
|
||||
ENTER_KEY => "enter",
|
||||
ESCAPE_KEY => "escape",
|
||||
TAB_KEY => "tab",
|
||||
SHIFT_TAB_KEY => "tab",
|
||||
|
||||
NSUpArrowFunctionKey => "up",
|
||||
NSDownArrowFunctionKey => "down",
|
||||
NSLeftArrowFunctionKey => "left",
|
||||
NSRightArrowFunctionKey => "right",
|
||||
NSPageUpFunctionKey => "pageup",
|
||||
NSPageDownFunctionKey => "pagedown",
|
||||
NSDeleteFunctionKey => "delete",
|
||||
NSF1FunctionKey => "f1",
|
||||
NSF2FunctionKey => "f2",
|
||||
NSF3FunctionKey => "f3",
|
||||
NSF4FunctionKey => "f4",
|
||||
NSF5FunctionKey => "f5",
|
||||
NSF6FunctionKey => "f6",
|
||||
NSF7FunctionKey => "f7",
|
||||
NSF8FunctionKey => "f8",
|
||||
NSF9FunctionKey => "f9",
|
||||
NSF10FunctionKey => "f10",
|
||||
NSF11FunctionKey => "f11",
|
||||
NSF12FunctionKey => "f12",
|
||||
|
||||
_ => {
|
||||
if !cmd && !ctrl && !function {
|
||||
input = Some(
|
||||
CStr::from_ptr(
|
||||
native_event.characters().UTF8String() as *mut c_char
|
||||
)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
unmodified_chars
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
||||
|
||||
Some(Self::KeyDown {
|
||||
keystroke: Keystroke {
|
||||
@@ -138,6 +88,27 @@ impl Event {
|
||||
is_held: native_event.isARepeat() == YES,
|
||||
})
|
||||
}
|
||||
NSEventType::NSKeyUp => {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||
|
||||
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
||||
|
||||
Some(Self::KeyUp {
|
||||
keystroke: Keystroke {
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
cmd,
|
||||
key: unmodified_chars.into(),
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
NSEventType::NSLeftMouseDown => {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
window_height.map(|window_height| Self::LeftMouseDown {
|
||||
@@ -218,14 +189,19 @@ impl Event {
|
||||
direction,
|
||||
})
|
||||
}
|
||||
NSEventType::NSLeftMouseDragged => {
|
||||
window_height.map(|window_height| Self::LeftMouseDragged {
|
||||
NSEventType::NSLeftMouseDragged => window_height.map(|window_height| {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
Self::LeftMouseDragged {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
),
|
||||
})
|
||||
}
|
||||
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
}
|
||||
}),
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
@@ -237,14 +213,90 @@ impl Event {
|
||||
),
|
||||
precise: native_event.hasPreciseScrollingDeltas() == YES,
|
||||
}),
|
||||
NSEventType::NSMouseMoved => window_height.map(|window_height| Self::MouseMoved {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
),
|
||||
left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0,
|
||||
NSEventType::NSMouseMoved => window_height.map(|window_height| {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
Self::MouseMoved {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
),
|
||||
left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0,
|
||||
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
}
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_key_text(
|
||||
native_event: id,
|
||||
cmd: bool,
|
||||
ctrl: bool,
|
||||
function: bool,
|
||||
) -> Option<(&'static str, Option<String>)> {
|
||||
let unmodified_chars =
|
||||
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
let mut input = None;
|
||||
let first_char = unmodified_chars.chars().next()?;
|
||||
use cocoa::appkit::*;
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const ENTER_KEY: u16 = 0x0d;
|
||||
const ESCAPE_KEY: u16 = 0x1b;
|
||||
const TAB_KEY: u16 = 0x09;
|
||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||
const SPACE_KEY: u16 = b' ' as u16;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
let unmodified_chars = match first_char as u16 {
|
||||
SPACE_KEY => {
|
||||
input = Some(" ".to_string());
|
||||
"space"
|
||||
}
|
||||
BACKSPACE_KEY => "backspace",
|
||||
ENTER_KEY => "enter",
|
||||
ESCAPE_KEY => "escape",
|
||||
TAB_KEY => "tab",
|
||||
SHIFT_TAB_KEY => "tab",
|
||||
|
||||
NSUpArrowFunctionKey => "up",
|
||||
NSDownArrowFunctionKey => "down",
|
||||
NSLeftArrowFunctionKey => "left",
|
||||
NSRightArrowFunctionKey => "right",
|
||||
NSPageUpFunctionKey => "pageup",
|
||||
NSPageDownFunctionKey => "pagedown",
|
||||
NSDeleteFunctionKey => "delete",
|
||||
NSF1FunctionKey => "f1",
|
||||
NSF2FunctionKey => "f2",
|
||||
NSF3FunctionKey => "f3",
|
||||
NSF4FunctionKey => "f4",
|
||||
NSF5FunctionKey => "f5",
|
||||
NSF6FunctionKey => "f6",
|
||||
NSF7FunctionKey => "f7",
|
||||
NSF8FunctionKey => "f8",
|
||||
NSF9FunctionKey => "f9",
|
||||
NSF10FunctionKey => "f10",
|
||||
NSF11FunctionKey => "f11",
|
||||
NSF12FunctionKey => "f12",
|
||||
|
||||
_ => {
|
||||
if !cmd && !ctrl && !function {
|
||||
input = Some(
|
||||
CStr::from_ptr(native_event.characters().UTF8String() as *mut c_char)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
unmodified_chars
|
||||
}
|
||||
};
|
||||
|
||||
Some((unmodified_chars, input))
|
||||
}
|
||||
|
||||
@@ -135,6 +135,10 @@ unsafe fn build_classes() {
|
||||
sel!(scrollWheel:),
|
||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(flagsChanged:),
|
||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(cancelOperation:),
|
||||
cancel_operation as extern "C" fn(&Object, Sel, id),
|
||||
@@ -181,6 +185,7 @@ struct WindowState {
|
||||
last_fresh_keydown: Option<(Keystroke, Option<String>)>,
|
||||
layer: id,
|
||||
traffic_light_position: Option<Vector2F>,
|
||||
previous_modifiers_changed_event: Option<Event>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -263,6 +268,7 @@ impl Window {
|
||||
last_fresh_keydown: None,
|
||||
layer,
|
||||
traffic_light_position: options.traffic_light_position,
|
||||
previous_modifiers_changed_event: None,
|
||||
})));
|
||||
|
||||
(*native_window).set_ivar(
|
||||
@@ -597,7 +603,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
|
||||
if let Some(event) = event {
|
||||
match &event {
|
||||
Event::LeftMouseDragged { position } => {
|
||||
Event::LeftMouseDragged { position, .. } => {
|
||||
window_state_borrow.synthetic_drag_counter += 1;
|
||||
window_state_borrow
|
||||
.executor
|
||||
@@ -611,6 +617,31 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
Event::LeftMouseUp { .. } => {
|
||||
window_state_borrow.synthetic_drag_counter += 1;
|
||||
}
|
||||
Event::ModifiersChanged {
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
cmd,
|
||||
} => {
|
||||
// Only raise modifiers changed event when they have actually changed
|
||||
if let Some(Event::ModifiersChanged {
|
||||
ctrl: prev_ctrl,
|
||||
alt: prev_alt,
|
||||
shift: prev_shift,
|
||||
cmd: prev_cmd,
|
||||
}) = &window_state_borrow.previous_modifiers_changed_event
|
||||
{
|
||||
if prev_ctrl == ctrl
|
||||
&& prev_alt == alt
|
||||
&& prev_shift == shift
|
||||
&& prev_cmd == cmd
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -805,7 +836,14 @@ async fn synthetic_drag(
|
||||
if window_state_borrow.synthetic_drag_counter == drag_id {
|
||||
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
||||
drop(window_state_borrow);
|
||||
callback(Event::LeftMouseDragged { position });
|
||||
callback(Event::LeftMouseDragged {
|
||||
// TODO: Make sure empty modifiers is correct for this
|
||||
position,
|
||||
shift: false,
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
cmd: false,
|
||||
});
|
||||
window_state.borrow_mut().event_callback = Some(callback);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -294,7 +294,13 @@ impl Presenter {
|
||||
Event::MouseMoved { .. } => {
|
||||
self.last_mouse_moved_event = Some(event.clone());
|
||||
}
|
||||
Event::LeftMouseDragged { position } => {
|
||||
Event::LeftMouseDragged {
|
||||
position,
|
||||
shift,
|
||||
ctrl,
|
||||
alt,
|
||||
cmd,
|
||||
} => {
|
||||
if let Some((clicked_region, prev_drag_position)) = self
|
||||
.clicked_region
|
||||
.as_ref()
|
||||
@@ -308,6 +314,10 @@ impl Presenter {
|
||||
self.last_mouse_moved_event = Some(Event::MouseMoved {
|
||||
position,
|
||||
left_mouse_down: true,
|
||||
shift,
|
||||
ctrl,
|
||||
alt,
|
||||
cmd,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@@ -403,6 +413,7 @@ impl Presenter {
|
||||
if let Event::MouseMoved {
|
||||
position,
|
||||
left_mouse_down,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
if !left_mouse_down {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{DocumentHighlight, Hover, HoverBlock, Location, Project, ProjectTransaction};
|
||||
use crate::{
|
||||
DocumentHighlight, Hover, HoverBlock, Location, LocationLink, Project, ProjectTransaction,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use client::{proto, PeerId};
|
||||
@@ -328,7 +330,7 @@ impl LspCommand for PerformRename {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetDefinition {
|
||||
type Response = Vec<Location>;
|
||||
type Response = Vec<LocationLink>;
|
||||
type LspRequest = lsp::request::GotoDefinition;
|
||||
type ProtoRequest = proto::GetDefinition;
|
||||
|
||||
@@ -351,7 +353,7 @@ impl LspCommand for GetDefinition {
|
||||
project: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Location>> {
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let mut definitions = Vec::new();
|
||||
let (lsp_adapter, language_server) = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
@@ -362,24 +364,26 @@ impl LspCommand for GetDefinition {
|
||||
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
|
||||
|
||||
if let Some(message) = message {
|
||||
let mut unresolved_locations = Vec::new();
|
||||
let mut unresolved_links = Vec::new();
|
||||
match message {
|
||||
lsp::GotoDefinitionResponse::Scalar(loc) => {
|
||||
unresolved_locations.push((loc.uri, loc.range));
|
||||
unresolved_links.push((None, loc.uri, loc.range));
|
||||
}
|
||||
lsp::GotoDefinitionResponse::Array(locs) => {
|
||||
unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
|
||||
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
|
||||
}
|
||||
lsp::GotoDefinitionResponse::Link(links) => {
|
||||
unresolved_locations.extend(
|
||||
links
|
||||
.into_iter()
|
||||
.map(|l| (l.target_uri, l.target_selection_range)),
|
||||
);
|
||||
unresolved_links.extend(links.into_iter().map(|l| {
|
||||
(
|
||||
l.origin_selection_range,
|
||||
l.target_uri,
|
||||
l.target_selection_range,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for (target_uri, target_range) in unresolved_locations {
|
||||
for (origin_range, target_uri, target_range) in unresolved_links {
|
||||
let target_buffer_handle = project
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_local_buffer_via_lsp(
|
||||
@@ -392,16 +396,34 @@ impl LspCommand for GetDefinition {
|
||||
.await?;
|
||||
|
||||
cx.read(|cx| {
|
||||
let origin_location = origin_range.map(|origin_range| {
|
||||
let origin_buffer = buffer.read(cx);
|
||||
let origin_start = origin_buffer
|
||||
.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
|
||||
let origin_end = origin_buffer
|
||||
.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
|
||||
Location {
|
||||
buffer: buffer.clone(),
|
||||
range: origin_buffer.anchor_after(origin_start)
|
||||
..origin_buffer.anchor_before(origin_end),
|
||||
}
|
||||
});
|
||||
|
||||
let target_buffer = target_buffer_handle.read(cx);
|
||||
let target_start = target_buffer
|
||||
.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
|
||||
let target_end = target_buffer
|
||||
.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
|
||||
definitions.push(Location {
|
||||
let target_location = Location {
|
||||
buffer: target_buffer_handle,
|
||||
range: target_buffer.anchor_after(target_start)
|
||||
..target_buffer.anchor_before(target_end),
|
||||
});
|
||||
};
|
||||
|
||||
definitions.push(LocationLink {
|
||||
origin: origin_location,
|
||||
target: target_location,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -441,24 +463,39 @@ impl LspCommand for GetDefinition {
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Vec<Location>,
|
||||
response: Vec<LocationLink>,
|
||||
project: &mut Project,
|
||||
peer_id: PeerId,
|
||||
_: &clock::Global,
|
||||
cx: &AppContext,
|
||||
) -> proto::GetDefinitionResponse {
|
||||
let locations = response
|
||||
let links = response
|
||||
.into_iter()
|
||||
.map(|definition| {
|
||||
let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&definition.range.start)),
|
||||
end: Some(serialize_anchor(&definition.range.end)),
|
||||
let origin = definition.origin.map(|origin| {
|
||||
let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&origin.range.start)),
|
||||
end: Some(serialize_anchor(&origin.range.end)),
|
||||
buffer: Some(buffer),
|
||||
}
|
||||
});
|
||||
|
||||
let buffer =
|
||||
project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
|
||||
let target = proto::Location {
|
||||
start: Some(serialize_anchor(&definition.target.range.start)),
|
||||
end: Some(serialize_anchor(&definition.target.range.end)),
|
||||
buffer: Some(buffer),
|
||||
};
|
||||
|
||||
proto::LocationLink {
|
||||
origin,
|
||||
target: Some(target),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
proto::GetDefinitionResponse { locations }
|
||||
proto::GetDefinitionResponse { links }
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
@@ -467,30 +504,60 @@ impl LspCommand for GetDefinition {
|
||||
project: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Location>> {
|
||||
let mut locations = Vec::new();
|
||||
for location in message.locations {
|
||||
let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let mut links = Vec::new();
|
||||
for link in message.links {
|
||||
let origin = match link.origin {
|
||||
Some(origin) => {
|
||||
let buffer = origin
|
||||
.buffer
|
||||
.ok_or_else(|| anyhow!("missing origin buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = origin
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin start"))?;
|
||||
let end = origin
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
Some(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
|
||||
let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = location
|
||||
let start = target
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target start"))?;
|
||||
let end = location
|
||||
let end = target
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
locations.push(Location {
|
||||
let target = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
};
|
||||
|
||||
links.push(LocationLink { origin, target })
|
||||
}
|
||||
Ok(locations)
|
||||
Ok(links)
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
|
||||
|
||||
@@ -202,12 +202,18 @@ pub struct DiagnosticSummary {
|
||||
pub warning_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Location {
|
||||
pub buffer: ModelHandle<Buffer>,
|
||||
pub range: Range<language::Anchor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocationLink {
|
||||
pub origin: Option<Location>,
|
||||
pub target: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DocumentHighlight {
|
||||
pub range: Range<language::Anchor>,
|
||||
@@ -2915,7 +2921,7 @@ impl Project {
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
position: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Location>>> {
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
|
||||
}
|
||||
@@ -7564,7 +7570,7 @@ mod tests {
|
||||
assert_eq!(definitions.len(), 1);
|
||||
let definition = definitions.pop().unwrap();
|
||||
cx.update(|cx| {
|
||||
let target_buffer = definition.buffer.read(cx);
|
||||
let target_buffer = definition.target.buffer.read(cx);
|
||||
assert_eq!(
|
||||
target_buffer
|
||||
.file()
|
||||
@@ -7574,7 +7580,7 @@ mod tests {
|
||||
.abs_path(cx),
|
||||
Path::new("/dir/a.rs"),
|
||||
);
|
||||
assert_eq!(definition.range.to_offset(target_buffer), 9..10);
|
||||
assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
|
||||
assert_eq!(
|
||||
list_worktrees(&project, cx),
|
||||
[("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
|
||||
|
||||
@@ -248,7 +248,7 @@ message GetDefinition {
|
||||
}
|
||||
|
||||
message GetDefinitionResponse {
|
||||
repeated Location locations = 1;
|
||||
repeated LocationLink links = 1;
|
||||
}
|
||||
|
||||
message GetReferences {
|
||||
@@ -279,6 +279,11 @@ message Location {
|
||||
Anchor end = 3;
|
||||
}
|
||||
|
||||
message LocationLink {
|
||||
optional Location origin = 1;
|
||||
Location target = 2;
|
||||
}
|
||||
|
||||
message DocumentHighlight {
|
||||
Kind kind = 1;
|
||||
Anchor start = 2;
|
||||
|
||||
@@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 25;
|
||||
pub const PROTOCOL_VERSION: u32 = 26;
|
||||
|
||||
@@ -446,6 +446,7 @@ pub struct Editor {
|
||||
pub code_actions_indicator: Color,
|
||||
pub unnecessary_code_fade: f32,
|
||||
pub hover_popover: HoverPopover,
|
||||
pub link_definition: HighlightStyle,
|
||||
pub jump_icon: Interactive<IconButton>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user