Working underline based on symbol origin

This commit is contained in:
Keith Simmons 2022-06-13 16:21:53 -07:00
parent 4286a9b564
commit 848445455d
11 changed files with 332 additions and 74 deletions

View file

@ -1980,13 +1980,13 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!(definitions_1.len(), 1); assert_eq!(definitions_1.len(), 1);
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2); 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!( assert_eq!(
target_buffer.text(), target_buffer.text(),
"const TWO: usize = 2;\nconst THREE: usize = 3;" "const TWO: usize = 2;\nconst THREE: usize = 3;"
); );
assert_eq!( 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) 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| { cx_b.read(|cx| {
assert_eq!(definitions_2.len(), 1); assert_eq!(definitions_2.len(), 1);
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2); 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!( assert_eq!(
target_buffer.text(), target_buffer.text(),
"const TWO: usize = 2;\nconst THREE: usize = 3;" "const TWO: usize = 2;\nconst THREE: usize = 3;"
); );
assert_eq!( 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) 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)] #[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 buffer_b2 = buffer_b2.await.unwrap();
let definitions = definitions.await.unwrap(); let definitions = definitions.await.unwrap();
assert_eq!(definitions.len(), 1); assert_eq!(definitions.len(), 1);
assert_eq!(definitions[0].buffer, buffer_b2); assert_eq!(definitions[0].target.buffer, buffer_b2);
} }
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
@ -5593,9 +5596,9 @@ impl TestClient {
log::info!("{}: detaching definitions request", guest_username); log::info!("{}: detaching definitions request", guest_username);
cx.update(|cx| definitions.detach_and_log_err(cx)); cx.update(|cx| definitions.detach_and_log_err(cx));
} else { } else {
client client.buffers.extend(
.buffers definitions.await?.into_iter().map(|loc| loc.target.buffer),
.extend(definitions.await?.into_iter().map(|loc| loc.buffer)); );
} }
} }
50..=54 => { 50..=54 => {

View file

@ -316,6 +316,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_async_action(Editor::find_all_references); cx.add_async_action(Editor::find_all_references);
hover_popover::init(cx); hover_popover::init(cx);
link_go_to_definition::init(cx);
workspace::register_project_item::<Editor>(cx); workspace::register_project_item::<Editor>(cx);
workspace::register_followable_item::<Editor>(cx); workspace::register_followable_item::<Editor>(cx);
@ -4603,9 +4604,13 @@ impl Editor {
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
let nav_history = workspace.active_pane().read(cx).nav_history().clone(); let nav_history = workspace.active_pane().read(cx).nav_history().clone();
for definition in definitions { for definition in definitions {
let range = definition.range.to_offset(definition.buffer.read(cx)); let range = definition
.target
.range
.to_offset(definition.target.buffer.read(cx));
let target_editor_handle = workspace.open_project_item(definition.buffer, cx); let target_editor_handle =
workspace.open_project_item(definition.target.buffer, cx);
target_editor_handle.update(cx, |target_editor, cx| { target_editor_handle.update(cx, |target_editor, cx| {
// When selecting a definition in a different buffer, disable the nav history // When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location. // to avoid creating a history entry at the previous cursor location.

View file

@ -8,6 +8,7 @@ use crate::{
hover_popover::HoverAt, hover_popover::HoverAt,
EditorStyle, EditorStyle,
}; };
use crate::{hover_popover::HoverAt, link_go_to_definition::FetchDefinition};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
use gpui::{ use gpui::{
@ -1417,7 +1418,7 @@ impl Element for EditorElement {
cx, cx,
), ),
Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx), Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx),
Event::LeftMouseDragged { position } => { Event::LeftMouseDragged { position, .. } => {
self.mouse_dragged(*position, layout, paint, cx) self.mouse_dragged(*position, layout, paint, cx)
} }
Event::ScrollWheel { Event::ScrollWheel {
@ -1426,9 +1427,26 @@ impl Element for EditorElement {
precise, precise,
} => self.scroll(*position, *delta, *precise, layout, paint, cx), } => self.scroll(*position, *delta, *precise, layout, paint, cx),
Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx), Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
Event::MouseMoved { position, .. } => { Event::MouseMoved { position, cmd, .. } => {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed // 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 // 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
};
if *cmd {
cx.dispatch_action(FetchDefinition { point });
}
if paint if paint
.context_menu_bounds .context_menu_bounds
.map_or(false, |context_menu_bounds| { .map_or(false, |context_menu_bounds| {
@ -1445,18 +1463,6 @@ impl Element for EditorElement {
return false; 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 }); cx.dispatch_action(HoverAt { point });
true true
} }

View file

@ -5,7 +5,9 @@ use std::{
use gpui::{ use gpui::{
actions, actions,
color::Color,
elements::{Flex, MouseEventHandler, Padding, Text}, elements::{Flex, MouseEventHandler, Padding, Text},
fonts::{HighlightStyle, Underline},
impl_internal_actions, impl_internal_actions,
platform::CursorStyle, platform::CursorStyle,
Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext, Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext,
@ -45,9 +47,135 @@ pub struct LinkGoToDefinitionState {
pub fn fetch_definition( pub fn fetch_definition(
editor: &mut Editor, editor: &mut Editor,
FetchDefinition { point }: &FetchDefinition, &FetchDefinition { point }: &FetchDefinition,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
if let Some(point) = point {
show_link_definition(editor, point, cx);
} else {
//TODO: Also needs to be dispatched when cmd modifier is released
hide_link_definition(editor, cx);
}
}
pub fn show_link_definition(
editor: &mut Editor,
point: DisplayPoint,
cx: &mut ViewContext<Editor>,
) {
if editor.pending_rename.is_some() {
return;
}
let snapshot = editor.snapshot(cx);
let multibuffer_offset = point.to_offset(&snapshot.display_snapshot, Bias::Left);
let (buffer, buffer_position) = if let Some(output) = editor
.buffer
.read(cx)
.text_anchor_for_position(multibuffer_offset, cx)
{
output
} else {
return;
};
let excerpt_id = if let Some((excerpt_id, _, _)) = editor
.buffer()
.read(cx)
.excerpt_containing(multibuffer_offset, cx)
{
excerpt_id
} else {
return;
};
let project = if let Some(project) = editor.project.clone() {
project
} else {
return;
};
// Get input anchor
let anchor = snapshot
.buffer_snapshot
.anchor_at(multibuffer_offset, Bias::Left);
// Don't request again if the location is the same as the previous request
if let Some(triggered_from) = &editor.link_go_to_definition_state.triggered_from {
if triggered_from
.cmp(&anchor, &snapshot.buffer_snapshot)
.is_eq()
{
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 origin_range = definition_request.await.ok().and_then(|definition_result| {
definition_result
.into_iter()
.filter_map(|link| {
link.origin.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
})
})
.next()
});
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
if let Some(origin_range) = origin_range {
this.highlight_text::<LinkGoToDefinitionState>(
vec![origin_range],
HighlightStyle {
underline: Some(Underline {
color: Some(Color::red()),
thickness: 1.0.into(),
squiggly: false,
}),
..Default::default()
},
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>) {
// only notify the context once
if editor.link_go_to_definition_state.symbol_range.is_some() {
editor.link_go_to_definition_state.symbol_range.take();
cx.notify();
}
editor.link_go_to_definition_state.task = None;
editor.link_go_to_definition_state.triggered_from = None;
editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
} }
pub fn go_to_fetched_definition( pub fn go_to_fetched_definition(

View file

@ -32,6 +32,10 @@ pub enum Event {
}, },
LeftMouseDragged { LeftMouseDragged {
position: Vector2F, position: Vector2F,
ctrl: bool,
alt: bool,
shift: bool,
cmd: bool,
}, },
RightMouseDown { RightMouseDown {
position: Vector2F, position: Vector2F,
@ -61,6 +65,10 @@ pub enum Event {
MouseMoved { MouseMoved {
position: Vector2F, position: Vector2F,
left_mouse_down: bool, left_mouse_down: bool,
ctrl: bool,
cmd: bool,
alt: bool,
shift: bool,
}, },
} }
@ -71,7 +79,7 @@ impl Event {
Event::ScrollWheel { position, .. } Event::ScrollWheel { position, .. }
| Event::LeftMouseDown { position, .. } | Event::LeftMouseDown { position, .. }
| Event::LeftMouseUp { position, .. } | Event::LeftMouseUp { position, .. }
| Event::LeftMouseDragged { position } | Event::LeftMouseDragged { position, .. }
| Event::RightMouseDown { position, .. } | Event::RightMouseDown { position, .. }
| Event::RightMouseUp { position, .. } | Event::RightMouseUp { position, .. }
| Event::NavigateMouseDown { position, .. } | Event::NavigateMouseDown { position, .. }

View file

@ -218,14 +218,19 @@ impl Event {
direction, direction,
}) })
} }
NSEventType::NSLeftMouseDragged => { NSEventType::NSLeftMouseDragged => window_height.map(|window_height| {
window_height.map(|window_height| Self::LeftMouseDragged { let modifiers = native_event.modifierFlags();
Self::LeftMouseDragged {
position: vec2f( position: vec2f(
native_event.locationInWindow().x as f32, native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y 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 { NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel {
position: vec2f( position: vec2f(
native_event.locationInWindow().x as f32, native_event.locationInWindow().x as f32,
@ -237,12 +242,19 @@ impl Event {
), ),
precise: native_event.hasPreciseScrollingDeltas() == YES, precise: native_event.hasPreciseScrollingDeltas() == YES,
}), }),
NSEventType::NSMouseMoved => window_height.map(|window_height| Self::MouseMoved { NSEventType::NSMouseMoved => window_height.map(|window_height| {
position: vec2f( let modifiers = native_event.modifierFlags();
native_event.locationInWindow().x as f32, Self::MouseMoved {
window_height - native_event.locationInWindow().y as f32, position: vec2f(
), native_event.locationInWindow().x as f32,
left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0, 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, _ => None,
} }

View file

@ -597,7 +597,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
if let Some(event) = event { if let Some(event) = event {
match &event { match &event {
Event::LeftMouseDragged { position } => { Event::LeftMouseDragged { position, .. } => {
window_state_borrow.synthetic_drag_counter += 1; window_state_borrow.synthetic_drag_counter += 1;
window_state_borrow window_state_borrow
.executor .executor
@ -805,7 +805,14 @@ async fn synthetic_drag(
if window_state_borrow.synthetic_drag_counter == drag_id { if window_state_borrow.synthetic_drag_counter == drag_id {
if let Some(mut callback) = window_state_borrow.event_callback.take() { if let Some(mut callback) = window_state_borrow.event_callback.take() {
drop(window_state_borrow); 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); window_state.borrow_mut().event_callback = Some(callback);
} }
} else { } else {

View file

@ -294,7 +294,13 @@ impl Presenter {
Event::MouseMoved { .. } => { Event::MouseMoved { .. } => {
self.last_mouse_moved_event = Some(event.clone()); 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 if let Some((clicked_region, prev_drag_position)) = self
.clicked_region .clicked_region
.as_ref() .as_ref()
@ -308,6 +314,10 @@ impl Presenter {
self.last_mouse_moved_event = Some(Event::MouseMoved { self.last_mouse_moved_event = Some(Event::MouseMoved {
position, position,
left_mouse_down: true, left_mouse_down: true,
shift,
ctrl,
alt,
cmd,
}); });
} }
_ => {} _ => {}
@ -403,6 +413,7 @@ impl Presenter {
if let Event::MouseMoved { if let Event::MouseMoved {
position, position,
left_mouse_down, left_mouse_down,
..
} = event } = event
{ {
if !left_mouse_down { if !left_mouse_down {

View file

@ -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 anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::{proto, PeerId}; use client::{proto, PeerId};
@ -328,7 +330,7 @@ impl LspCommand for PerformRename {
#[async_trait(?Send)] #[async_trait(?Send)]
impl LspCommand for GetDefinition { impl LspCommand for GetDefinition {
type Response = Vec<Location>; type Response = Vec<LocationLink>;
type LspRequest = lsp::request::GotoDefinition; type LspRequest = lsp::request::GotoDefinition;
type ProtoRequest = proto::GetDefinition; type ProtoRequest = proto::GetDefinition;
@ -351,7 +353,7 @@ impl LspCommand for GetDefinition {
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<Location>> { ) -> Result<Vec<LocationLink>> {
let mut definitions = Vec::new(); let mut definitions = Vec::new();
let (lsp_adapter, language_server) = project let (lsp_adapter, language_server) = project
.read_with(&cx, |project, cx| { .read_with(&cx, |project, cx| {
@ -362,24 +364,26 @@ impl LspCommand for GetDefinition {
.ok_or_else(|| anyhow!("no language server found for buffer"))?; .ok_or_else(|| anyhow!("no language server found for buffer"))?;
if let Some(message) = message { if let Some(message) = message {
let mut unresolved_locations = Vec::new(); let mut unresolved_links = Vec::new();
match message { match message {
lsp::GotoDefinitionResponse::Scalar(loc) => { lsp::GotoDefinitionResponse::Scalar(loc) => {
unresolved_locations.push((loc.uri, loc.range)); unresolved_links.push((None, loc.uri, loc.range));
} }
lsp::GotoDefinitionResponse::Array(locs) => { 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) => { lsp::GotoDefinitionResponse::Link(links) => {
unresolved_locations.extend( unresolved_links.extend(links.into_iter().map(|l| {
links (
.into_iter() l.origin_selection_range,
.map(|l| (l.target_uri, l.target_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 let target_buffer_handle = project
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp( this.open_local_buffer_via_lsp(
@ -392,16 +396,34 @@ impl LspCommand for GetDefinition {
.await?; .await?;
cx.read(|cx| { 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_buffer = target_buffer_handle.read(cx);
let target_start = target_buffer let target_start = target_buffer
.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left); .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
let target_end = target_buffer let target_end = target_buffer
.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left); .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
definitions.push(Location { let target_location = Location {
buffer: target_buffer_handle, buffer: target_buffer_handle,
range: target_buffer.anchor_after(target_start) range: target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end), ..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( fn response_to_proto(
response: Vec<Location>, response: Vec<LocationLink>,
project: &mut Project, project: &mut Project,
peer_id: PeerId, peer_id: PeerId,
_: &clock::Global, _: &clock::Global,
cx: &AppContext, cx: &AppContext,
) -> proto::GetDefinitionResponse { ) -> proto::GetDefinitionResponse {
let locations = response let links = response
.into_iter() .into_iter()
.map(|definition| { .map(|definition| {
let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx); let origin = definition.origin.map(|origin| {
proto::Location { let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
start: Some(serialize_anchor(&definition.range.start)), proto::Location {
end: Some(serialize_anchor(&definition.range.end)), 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), buffer: Some(buffer),
};
proto::LocationLink {
origin,
target: Some(target),
} }
}) })
.collect(); .collect();
proto::GetDefinitionResponse { locations } proto::GetDefinitionResponse { links }
} }
async fn response_from_proto( async fn response_from_proto(
@ -467,30 +504,60 @@ impl LspCommand for GetDefinition {
project: ModelHandle<Project>, project: ModelHandle<Project>,
_: ModelHandle<Buffer>, _: ModelHandle<Buffer>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<Location>> { ) -> Result<Vec<LocationLink>> {
let mut locations = Vec::new(); let mut links = Vec::new();
for location in message.locations { for link in message.links {
let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?; 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 let buffer = project
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
.await?; .await?;
let start = location let start = target
.start .start
.and_then(deserialize_anchor) .and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target start"))?; .ok_or_else(|| anyhow!("missing target start"))?;
let end = location let end = target
.end .end
.and_then(deserialize_anchor) .and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target end"))?; .ok_or_else(|| anyhow!("missing target end"))?;
buffer buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await; .await;
locations.push(Location { let target = Location {
buffer, buffer,
range: start..end, range: start..end,
}) };
links.push(LocationLink { origin, target })
} }
Ok(locations) Ok(links)
} }
fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 { fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {

View file

@ -208,6 +208,12 @@ pub struct Location {
pub range: Range<language::Anchor>, pub range: Range<language::Anchor>,
} }
#[derive(Debug)]
pub struct LocationLink {
pub origin: Option<Location>,
pub target: Location,
}
#[derive(Debug)] #[derive(Debug)]
pub struct DocumentHighlight { pub struct DocumentHighlight {
pub range: Range<language::Anchor>, pub range: Range<language::Anchor>,
@ -2915,7 +2921,7 @@ impl Project {
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
position: T, position: T,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Location>>> { ) -> Task<Result<Vec<LocationLink>>> {
let position = position.to_point_utf16(buffer.read(cx)); let position = position.to_point_utf16(buffer.read(cx));
self.request_lsp(buffer.clone(), GetDefinition { position }, cx) self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
} }
@ -7564,7 +7570,7 @@ mod tests {
assert_eq!(definitions.len(), 1); assert_eq!(definitions.len(), 1);
let definition = definitions.pop().unwrap(); let definition = definitions.pop().unwrap();
cx.update(|cx| { cx.update(|cx| {
let target_buffer = definition.buffer.read(cx); let target_buffer = definition.target.buffer.read(cx);
assert_eq!( assert_eq!(
target_buffer target_buffer
.file() .file()
@ -7574,7 +7580,7 @@ mod tests {
.abs_path(cx), .abs_path(cx),
Path::new("/dir/a.rs"), 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!( assert_eq!(
list_worktrees(&project, cx), list_worktrees(&project, cx),
[("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]

View file

@ -248,7 +248,7 @@ message GetDefinition {
} }
message GetDefinitionResponse { message GetDefinitionResponse {
repeated Location locations = 1; repeated LocationLink links = 1;
} }
message GetReferences { message GetReferences {
@ -279,6 +279,11 @@ message Location {
Anchor end = 3; Anchor end = 3;
} }
message LocationLink {
optional Location origin = 1;
Location target = 2;
}
message DocumentHighlight { message DocumentHighlight {
Kind kind = 1; Kind kind = 1;
Anchor start = 2; Anchor start = 2;