Detailed changes
@@ -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 => {
@@ -316,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);
@@ -4603,9 +4604,13 @@ impl Editor {
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 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| {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
@@ -8,6 +8,7 @@ use crate::{
hover_popover::HoverAt,
EditorStyle,
};
+use crate::{hover_popover::HoverAt, link_go_to_definition::FetchDefinition};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
use gpui::{
@@ -1417,7 +1418,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,9 +1427,26 @@ 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, .. } => {
+ Event::MouseMoved { position, cmd, .. } => {
// 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
+ };
+
+ if *cmd {
+ cx.dispatch_action(FetchDefinition { point });
+ }
+
if paint
.context_menu_bounds
.map_or(false, |context_menu_bounds| {
@@ -1445,18 +1463,6 @@ impl Element for EditorElement {
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
}
@@ -5,7 +5,9 @@ use std::{
use gpui::{
actions,
+ color::Color,
elements::{Flex, MouseEventHandler, Padding, Text},
+ fonts::{HighlightStyle, Underline},
impl_internal_actions,
platform::CursorStyle,
Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext,
@@ -45,9 +47,135 @@ pub struct LinkGoToDefinitionState {
pub fn fetch_definition(
editor: &mut Editor,
- FetchDefinition { point }: &FetchDefinition,
+ &FetchDefinition { point }: &FetchDefinition,
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(
@@ -32,6 +32,10 @@ pub enum Event {
},
LeftMouseDragged {
position: Vector2F,
+ ctrl: bool,
+ alt: bool,
+ shift: bool,
+ cmd: bool,
},
RightMouseDown {
position: Vector2F,
@@ -61,6 +65,10 @@ pub enum Event {
MouseMoved {
position: Vector2F,
left_mouse_down: bool,
+ ctrl: bool,
+ cmd: bool,
+ alt: bool,
+ shift: bool,
},
}
@@ -71,7 +79,7 @@ impl Event {
Event::ScrollWheel { position, .. }
| Event::LeftMouseDown { position, .. }
| Event::LeftMouseUp { position, .. }
- | Event::LeftMouseDragged { position }
+ | Event::LeftMouseDragged { position, .. }
| Event::RightMouseDown { position, .. }
| Event::RightMouseUp { position, .. }
| Event::NavigateMouseDown { position, .. }
@@ -218,14 +218,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,12 +242,19 @@ 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,
}
@@ -597,7 +597,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
@@ -805,7 +805,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 {
@@ -208,6 +208,12 @@ pub struct Location {
pub range: Range<language::Anchor>,
}
+#[derive(Debug)]
+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;