Open URLs with cmd-click (#7312)

Release Notes:

- Added ability to cmd-click on URLs in all buffers

---------

Co-authored-by: fdionisi <code@fdionisi.me>
This commit is contained in:
Conrad Irwin
2024-02-02 22:05:28 -07:00
committed by GitHub
co-authored by fdionisi
parent 583273b6ee
commit 1a82470897
13 changed files with 551 additions and 601 deletions
+2 -2
View File
@@ -25,8 +25,8 @@ mod wrap_map;
use crate::EditorStyle;
use crate::{
link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt,
InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
hover_links::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, InlayId,
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
};
pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet};
+1 -1
View File
@@ -1168,7 +1168,7 @@ mod tests {
use super::*;
use crate::{
display_map::{InlayHighlights, TextHighlights},
link_go_to_definition::InlayHighlight,
hover_links::InlayHighlight,
InlayId, MultiBuffer,
};
use gpui::AppContext;
+28 -29
View File
@@ -22,9 +22,9 @@ mod inlay_hint_cache;
mod debounced_delay;
mod git;
mod highlight_matching_bracket;
mod hover_links;
mod hover_popover;
pub mod items;
mod link_go_to_definition;
mod mouse_context_menu;
pub mod movement;
mod persistence;
@@ -77,7 +77,7 @@ use language::{
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
use lsp::{DiagnosticSeverity, LanguageServerId};
use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
@@ -402,7 +402,7 @@ pub struct Editor {
remote_id: Option<ViewId>,
hover_state: HoverState,
gutter_hovered: bool,
link_go_to_definition_state: LinkGoToDefinitionState,
hovered_link_state: Option<HoveredLinkState>,
copilot_state: CopilotState,
inlay_hint_cache: InlayHintCache,
next_inlay_id: usize,
@@ -1477,7 +1477,7 @@ impl Editor {
leader_peer_id: None,
remote_id: None,
hover_state: Default::default(),
link_go_to_definition_state: Default::default(),
hovered_link_state: Default::default(),
copilot_state: Default::default(),
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
@@ -7243,11 +7243,8 @@ impl Editor {
cx.spawn(|editor, mut cx| async move {
let definitions = definitions.await?;
editor.update(&mut cx, |editor, cx| {
editor.navigate_to_definitions(
definitions
.into_iter()
.map(GoToDefinitionLink::Text)
.collect(),
editor.navigate_to_hover_links(
definitions.into_iter().map(HoverLink::Text).collect(),
split,
cx,
);
@@ -7257,9 +7254,9 @@ impl Editor {
.detach_and_log_err(cx);
}
pub fn navigate_to_definitions(
pub fn navigate_to_hover_links(
&mut self,
mut definitions: Vec<GoToDefinitionLink>,
mut definitions: Vec<HoverLink>,
split: bool,
cx: &mut ViewContext<Editor>,
) {
@@ -7271,10 +7268,14 @@ impl Editor {
if definitions.len() == 1 {
let definition = definitions.pop().unwrap();
let target_task = match definition {
GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
HoverLink::InlayHint(lsp_location, server_id) => {
self.compute_target_location(lsp_location, server_id, cx)
}
HoverLink::Url(url) => {
cx.open_url(&url);
Task::ready(Ok(None))
}
};
cx.spawn(|editor, mut cx| async move {
let target = target_task.await.context("target resolution task")?;
@@ -7325,29 +7326,27 @@ impl Editor {
let title = definitions
.iter()
.find_map(|definition| match definition {
GoToDefinitionLink::Text(link) => {
link.origin.as_ref().map(|origin| {
let buffer = origin.buffer.read(cx);
format!(
"Definitions for {}",
buffer
.text_for_range(origin.range.clone())
.collect::<String>()
)
})
}
GoToDefinitionLink::InlayHint(_, _) => None,
HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
let buffer = origin.buffer.read(cx);
format!(
"Definitions for {}",
buffer
.text_for_range(origin.range.clone())
.collect::<String>()
)
}),
HoverLink::InlayHint(_, _) => None,
HoverLink::Url(_) => None,
})
.unwrap_or("Definitions".to_string());
let location_tasks = definitions
.into_iter()
.map(|definition| match definition {
GoToDefinitionLink::Text(link) => {
Task::Ready(Some(Ok(Some(link.target))))
}
GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
HoverLink::InlayHint(lsp_location, server_id) => {
editor.compute_target_location(lsp_location, server_id, cx)
}
HoverLink::Url(_) => Task::ready(Ok(None)),
})
.collect::<Vec<_>>();
(title, location_tasks)
+46 -82
View File
@@ -9,11 +9,6 @@ use crate::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
items::BufferSearchHighlights,
link_go_to_definition::{
go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
LinkGoToDefinitionState,
},
mouse_context_menu,
scroll::scroll_amount::ScrollAmount,
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
@@ -337,7 +332,14 @@ impl EditorElement {
register_action(view, cx, Editor::display_cursor_names);
}
fn register_key_listeners(&self, cx: &mut ElementContext) {
fn register_key_listeners(
&self,
cx: &mut ElementContext,
text_bounds: Bounds<Pixels>,
layout: &LayoutState,
) {
let position_map = layout.position_map.clone();
let stacking_order = cx.stacking_order().clone();
cx.on_key_event({
let editor = self.editor.clone();
move |event: &ModifiersChangedEvent, phase, cx| {
@@ -345,46 +347,41 @@ impl EditorElement {
return;
}
if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
cx.stop_propagation();
}
editor.update(cx, |editor, cx| {
Self::modifiers_changed(
editor,
event,
&position_map,
text_bounds,
&stacking_order,
cx,
)
})
}
});
}
pub(crate) fn modifiers_changed(
fn modifiers_changed(
editor: &mut Editor,
event: &ModifiersChangedEvent,
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) -> bool {
let pending_selection = editor.has_pending_selection();
if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
if event.command && !pending_selection {
let point = point.clone();
let snapshot = editor.snapshot(cx);
let kind = point.definition_kind(event.shift);
show_link_definition(kind, editor, point, snapshot, cx);
return false;
}
}
) {
let mouse_position = cx.mouse_position();
if !text_bounds.contains(&mouse_position)
|| !cx.was_top_layer(&mouse_position, stacking_order)
{
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_highlights::<LinkGoToDefinitionState>(cx);
return;
}
false
editor.update_hovered_link(
position_map.point_for_position(text_bounds, mouse_position),
&position_map.snapshot,
event.modifiers,
cx,
)
}
fn mouse_left_down(
@@ -485,13 +482,7 @@ impl EditorElement {
&& cx.was_top_layer(&event.position, stacking_order)
{
let point = position_map.point_for_position(text_bounds, event.position);
let could_be_inlay = point.as_valid().is_none();
let split = event.modifiers.alt;
if event.modifiers.shift || could_be_inlay {
go_to_fetched_type_definition(editor, point, split, cx);
} else {
go_to_fetched_definition(editor, point, split, cx);
}
editor.handle_click_hovered_link(point, event.modifiers, cx);
cx.stop_propagation();
} else if end_selection {
@@ -564,31 +555,14 @@ impl EditorElement {
if text_hovered && was_top {
let point_for_position = position_map.point_for_position(text_bounds, event.position);
match point_for_position.as_valid() {
Some(point) => {
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::Text(point)),
modifiers.command,
modifiers.shift,
cx,
);
hover_at(editor, Some(point), cx);
Self::update_visible_cursor(editor, point, position_map, cx);
}
None => {
update_inlay_link_and_hover_points(
&position_map.snapshot,
point_for_position,
editor,
modifiers.command,
modifiers.shift,
cx,
);
}
editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
if let Some(point) = point_for_position.as_valid() {
hover_at(editor, Some(point), cx);
Self::update_visible_cursor(editor, point, position_map, cx);
}
} else {
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
editor.hide_hovered_link(cx);
hover_at(editor, None, cx);
if gutter_hovered && was_top {
cx.stop_propagation();
@@ -930,13 +904,13 @@ impl EditorElement {
if self
.editor
.read(cx)
.link_go_to_definition_state
.definitions
.is_empty()
.hovered_link_state
.as_ref()
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
{
cx.set_cursor_style(CursorStyle::IBeam);
} else {
cx.set_cursor_style(CursorStyle::PointingHand);
} else {
cx.set_cursor_style(CursorStyle::IBeam);
}
}
@@ -3105,9 +3079,9 @@ impl Element for EditorElement {
let key_context = self.editor.read(cx).key_context(cx);
cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
self.register_actions(cx);
self.register_key_listeners(cx);
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
self.register_key_listeners(cx, text_bounds, &layout);
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.editor.clone()),
@@ -3224,16 +3198,6 @@ pub struct PointForPosition {
}
impl PointForPosition {
#[cfg(test)]
pub fn valid(valid: DisplayPoint) -> Self {
Self {
previous_valid: valid,
next_valid: valid,
exact_unclipped: valid,
column_overshoot_after_line_end: 0,
}
}
pub fn as_valid(&self) -> Option<DisplayPoint> {
if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
Some(self.previous_valid)
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::{
display_map::{InlayOffset, ToDisplayPoint},
link_go_to_definition::{InlayHighlight, RangeInEditor},
hover_links::{InlayHighlight, RangeInEditor},
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
ExcerptId, Hover, RangeToAnchorExt,
};
@@ -605,8 +605,8 @@ mod tests {
use crate::{
editor_tests::init_test,
element::PointForPosition,
hover_links::update_inlay_link_and_hover_points,
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
link_go_to_definition::update_inlay_link_and_hover_points,
test::editor_lsp_test_context::EditorLspTestContext,
InlayId,
};
+4 -5
View File
@@ -1,7 +1,7 @@
use crate::{
editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorEvent, EditorSettings,
ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
editor_settings::SeedQuerySetting, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll,
Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
};
use anyhow::{anyhow, Context as _, Result};
use collections::HashSet;
@@ -682,8 +682,7 @@ impl Item for Editor {
}
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
hide_link_definition(self, cx);
self.link_go_to_definition_state.last_trigger_point = None;
self.hide_hovered_link(cx);
}
fn is_dirty(&self, cx: &AppContext) -> bool {
+28 -2
View File
@@ -4,7 +4,8 @@ use crate::{
use collections::BTreeMap;
use futures::Future;
use gpui::{
AnyWindowHandle, AppContext, Keystroke, ModelContext, View, ViewContext, VisualTestContext,
AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext,
VisualTestContext,
};
use indoc::indoc;
use itertools::Itertools;
@@ -187,6 +188,31 @@ impl EditorTestContext {
ranges[0].start.to_display_point(&snapshot)
}
pub fn pixel_position(&mut self, marked_text: &str) -> Point<Pixels> {
let display_point = self.display_point(marked_text);
self.pixel_position_for(display_point)
}
pub fn pixel_position_for(&mut self, display_point: DisplayPoint) -> Point<Pixels> {
self.update_editor(|editor, cx| {
let newest_point = editor.selections.newest_display(cx).head();
let pixel_position = editor.pixel_position_of_newest_cursor.unwrap();
let line_height = editor
.style()
.unwrap()
.text
.line_height_in_pixels(cx.rem_size());
let snapshot = editor.snapshot(cx);
let details = editor.text_layout_details(cx);
let y = pixel_position.y
+ line_height * (display_point.row() as f32 - newest_point.row() as f32);
let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details)
- snapshot.x_for_display_point(newest_point, &details);
Point::new(x, y)
})
}
// Returns anchors for the current buffer using `«` and `»`
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
let ranges = self.ranges(marked_text);
@@ -343,7 +369,7 @@ impl EditorTestContext {
}
impl Deref for EditorTestContext {
type Target = gpui::TestAppContext;
type Target = gpui::VisualTestContext;
fn deref(&self) -> &Self::Target {
&self.cx