mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-08 19:43:11 +00:00
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:
parent
583273b6ee
commit
1a82470897
13 changed files with 551 additions and 601 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -2416,6 +2416,7 @@ dependencies = [
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"language",
|
"language",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"linkify",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
"multi_buffer",
|
"multi_buffer",
|
||||||
|
@ -4134,6 +4135,15 @@ dependencies = [
|
||||||
"safemem",
|
"safemem",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linkify"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linkme"
|
name = "linkme"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
|
|
@ -20,7 +20,7 @@ test-support = [
|
||||||
"util/test-support",
|
"util/test-support",
|
||||||
"workspace/test-support",
|
"workspace/test-support",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
"tree-sitter-typescript"
|
"tree-sitter-typescript",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -40,6 +40,7 @@ indoc = "1.0.4"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
|
linkify = "0.10.0"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
multi_buffer = { path = "../multi_buffer" }
|
multi_buffer = { path = "../multi_buffer" }
|
||||||
|
|
|
@ -25,8 +25,8 @@ mod wrap_map;
|
||||||
|
|
||||||
use crate::EditorStyle;
|
use crate::EditorStyle;
|
||||||
use crate::{
|
use crate::{
|
||||||
link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt,
|
hover_links::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, InlayId,
|
||||||
InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
|
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
|
||||||
};
|
};
|
||||||
pub use block_map::{BlockMap, BlockPoint};
|
pub use block_map::{BlockMap, BlockPoint};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
|
|
|
@ -1168,7 +1168,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{InlayHighlights, TextHighlights},
|
display_map::{InlayHighlights, TextHighlights},
|
||||||
link_go_to_definition::InlayHighlight,
|
hover_links::InlayHighlight,
|
||||||
InlayId, MultiBuffer,
|
InlayId, MultiBuffer,
|
||||||
};
|
};
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
|
|
|
@ -22,9 +22,9 @@ mod inlay_hint_cache;
|
||||||
mod debounced_delay;
|
mod debounced_delay;
|
||||||
mod git;
|
mod git;
|
||||||
mod highlight_matching_bracket;
|
mod highlight_matching_bracket;
|
||||||
|
mod hover_links;
|
||||||
mod hover_popover;
|
mod hover_popover;
|
||||||
pub mod items;
|
pub mod items;
|
||||||
mod link_go_to_definition;
|
|
||||||
mod mouse_context_menu;
|
mod mouse_context_menu;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
@ -77,7 +77,7 @@ use language::{
|
||||||
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
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 lsp::{DiagnosticSeverity, LanguageServerId};
|
||||||
use mouse_context_menu::MouseContextMenu;
|
use mouse_context_menu::MouseContextMenu;
|
||||||
use movement::TextLayoutDetails;
|
use movement::TextLayoutDetails;
|
||||||
|
@ -402,7 +402,7 @@ pub struct Editor {
|
||||||
remote_id: Option<ViewId>,
|
remote_id: Option<ViewId>,
|
||||||
hover_state: HoverState,
|
hover_state: HoverState,
|
||||||
gutter_hovered: bool,
|
gutter_hovered: bool,
|
||||||
link_go_to_definition_state: LinkGoToDefinitionState,
|
hovered_link_state: Option<HoveredLinkState>,
|
||||||
copilot_state: CopilotState,
|
copilot_state: CopilotState,
|
||||||
inlay_hint_cache: InlayHintCache,
|
inlay_hint_cache: InlayHintCache,
|
||||||
next_inlay_id: usize,
|
next_inlay_id: usize,
|
||||||
|
@ -1477,7 +1477,7 @@ impl Editor {
|
||||||
leader_peer_id: None,
|
leader_peer_id: None,
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
hover_state: Default::default(),
|
hover_state: Default::default(),
|
||||||
link_go_to_definition_state: Default::default(),
|
hovered_link_state: Default::default(),
|
||||||
copilot_state: Default::default(),
|
copilot_state: Default::default(),
|
||||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||||
gutter_hovered: false,
|
gutter_hovered: false,
|
||||||
|
@ -7243,11 +7243,8 @@ impl Editor {
|
||||||
cx.spawn(|editor, mut cx| async move {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
let definitions = definitions.await?;
|
let definitions = definitions.await?;
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
editor.navigate_to_definitions(
|
editor.navigate_to_hover_links(
|
||||||
definitions
|
definitions.into_iter().map(HoverLink::Text).collect(),
|
||||||
.into_iter()
|
|
||||||
.map(GoToDefinitionLink::Text)
|
|
||||||
.collect(),
|
|
||||||
split,
|
split,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -7257,9 +7254,9 @@ impl Editor {
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn navigate_to_definitions(
|
pub fn navigate_to_hover_links(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut definitions: Vec<GoToDefinitionLink>,
|
mut definitions: Vec<HoverLink>,
|
||||||
split: bool,
|
split: bool,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) {
|
||||||
|
@ -7271,10 +7268,14 @@ impl Editor {
|
||||||
if definitions.len() == 1 {
|
if definitions.len() == 1 {
|
||||||
let definition = definitions.pop().unwrap();
|
let definition = definitions.pop().unwrap();
|
||||||
let target_task = match definition {
|
let target_task = match definition {
|
||||||
GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
|
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
|
||||||
GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
|
HoverLink::InlayHint(lsp_location, server_id) => {
|
||||||
self.compute_target_location(lsp_location, server_id, cx)
|
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 {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
let target = target_task.await.context("target resolution task")?;
|
let target = target_task.await.context("target resolution task")?;
|
||||||
|
@ -7325,8 +7326,7 @@ impl Editor {
|
||||||
let title = definitions
|
let title = definitions
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|definition| match definition {
|
.find_map(|definition| match definition {
|
||||||
GoToDefinitionLink::Text(link) => {
|
HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
|
||||||
link.origin.as_ref().map(|origin| {
|
|
||||||
let buffer = origin.buffer.read(cx);
|
let buffer = origin.buffer.read(cx);
|
||||||
format!(
|
format!(
|
||||||
"Definitions for {}",
|
"Definitions for {}",
|
||||||
|
@ -7334,20 +7334,19 @@ impl Editor {
|
||||||
.text_for_range(origin.range.clone())
|
.text_for_range(origin.range.clone())
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
)
|
)
|
||||||
})
|
}),
|
||||||
}
|
HoverLink::InlayHint(_, _) => None,
|
||||||
GoToDefinitionLink::InlayHint(_, _) => None,
|
HoverLink::Url(_) => None,
|
||||||
})
|
})
|
||||||
.unwrap_or("Definitions".to_string());
|
.unwrap_or("Definitions".to_string());
|
||||||
let location_tasks = definitions
|
let location_tasks = definitions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|definition| match definition {
|
.map(|definition| match definition {
|
||||||
GoToDefinitionLink::Text(link) => {
|
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
|
||||||
Task::Ready(Some(Ok(Some(link.target))))
|
HoverLink::InlayHint(lsp_location, server_id) => {
|
||||||
}
|
|
||||||
GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
|
|
||||||
editor.compute_target_location(lsp_location, server_id, cx)
|
editor.compute_target_location(lsp_location, server_id, cx)
|
||||||
}
|
}
|
||||||
|
HoverLink::Url(_) => Task::ready(Ok(None)),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
(title, location_tasks)
|
(title, location_tasks)
|
||||||
|
|
|
@ -9,11 +9,6 @@ use crate::{
|
||||||
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||||
},
|
},
|
||||||
items::BufferSearchHighlights,
|
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,
|
mouse_context_menu,
|
||||||
scroll::scroll_amount::ScrollAmount,
|
scroll::scroll_amount::ScrollAmount,
|
||||||
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
||||||
|
@ -337,7 +332,14 @@ impl EditorElement {
|
||||||
register_action(view, cx, Editor::display_cursor_names);
|
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({
|
cx.on_key_event({
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
move |event: &ModifiersChangedEvent, phase, cx| {
|
move |event: &ModifiersChangedEvent, phase, cx| {
|
||||||
|
@ -345,46 +347,41 @@ impl EditorElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
|
editor.update(cx, |editor, cx| {
|
||||||
cx.stop_propagation();
|
Self::modifiers_changed(
|
||||||
}
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn modifiers_changed(
|
fn modifiers_changed(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
event: &ModifiersChangedEvent,
|
event: &ModifiersChangedEvent,
|
||||||
|
position_map: &PositionMap,
|
||||||
|
text_bounds: Bounds<Pixels>,
|
||||||
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
let pending_selection = editor.has_pending_selection();
|
let mouse_position = cx.mouse_position();
|
||||||
|
if !text_bounds.contains(&mouse_position)
|
||||||
if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
|
|| !cx.was_top_layer(&mouse_position, stacking_order)
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
if editor.link_go_to_definition_state.symbol_range.is_some()
|
return;
|
||||||
|| !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.update_hovered_link(
|
||||||
|
position_map.point_for_position(text_bounds, mouse_position),
|
||||||
editor.clear_highlights::<LinkGoToDefinitionState>(cx);
|
&position_map.snapshot,
|
||||||
}
|
event.modifiers,
|
||||||
|
cx,
|
||||||
false
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_left_down(
|
fn mouse_left_down(
|
||||||
|
@ -485,13 +482,7 @@ impl EditorElement {
|
||||||
&& cx.was_top_layer(&event.position, stacking_order)
|
&& cx.was_top_layer(&event.position, stacking_order)
|
||||||
{
|
{
|
||||||
let point = position_map.point_for_position(text_bounds, event.position);
|
let point = position_map.point_for_position(text_bounds, event.position);
|
||||||
let could_be_inlay = point.as_valid().is_none();
|
editor.handle_click_hovered_link(point, event.modifiers, cx);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
} else if end_selection {
|
} else if end_selection {
|
||||||
|
@ -564,31 +555,14 @@ impl EditorElement {
|
||||||
if text_hovered && was_top {
|
if text_hovered && was_top {
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
|
|
||||||
match point_for_position.as_valid() {
|
editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
|
||||||
Some(point) => {
|
|
||||||
update_go_to_definition_link(
|
if let Some(point) = point_for_position.as_valid() {
|
||||||
editor,
|
|
||||||
Some(GoToDefinitionTrigger::Text(point)),
|
|
||||||
modifiers.command,
|
|
||||||
modifiers.shift,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
hover_at(editor, Some(point), cx);
|
hover_at(editor, Some(point), cx);
|
||||||
Self::update_visible_cursor(editor, point, position_map, 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
editor.hide_hovered_link(cx);
|
||||||
hover_at(editor, None, cx);
|
hover_at(editor, None, cx);
|
||||||
if gutter_hovered && was_top {
|
if gutter_hovered && was_top {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
@ -930,13 +904,13 @@ impl EditorElement {
|
||||||
if self
|
if self
|
||||||
.editor
|
.editor
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.link_go_to_definition_state
|
.hovered_link_state
|
||||||
.definitions
|
.as_ref()
|
||||||
.is_empty()
|
.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);
|
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);
|
let key_context = self.editor.read(cx).key_context(cx);
|
||||||
cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
|
cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
|
||||||
self.register_actions(cx);
|
self.register_actions(cx);
|
||||||
self.register_key_listeners(cx);
|
|
||||||
|
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
self.register_key_listeners(cx, text_bounds, &layout);
|
||||||
cx.handle_input(
|
cx.handle_input(
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
ElementInputHandler::new(bounds, self.editor.clone()),
|
ElementInputHandler::new(bounds, self.editor.clone()),
|
||||||
|
@ -3224,16 +3198,6 @@ pub struct PointForPosition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn as_valid(&self) -> Option<DisplayPoint> {
|
||||||
if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
|
if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
|
||||||
Some(self.previous_valid)
|
Some(self.previous_valid)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{InlayOffset, ToDisplayPoint},
|
display_map::{InlayOffset, ToDisplayPoint},
|
||||||
link_go_to_definition::{InlayHighlight, RangeInEditor},
|
hover_links::{InlayHighlight, RangeInEditor},
|
||||||
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
|
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
|
||||||
ExcerptId, Hover, RangeToAnchorExt,
|
ExcerptId, Hover, RangeToAnchorExt,
|
||||||
};
|
};
|
||||||
|
@ -605,8 +605,8 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
editor_tests::init_test,
|
editor_tests::init_test,
|
||||||
element::PointForPosition,
|
element::PointForPosition,
|
||||||
|
hover_links::update_inlay_link_and_hover_points,
|
||||||
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
|
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,
|
test::editor_lsp_test_context::EditorLspTestContext,
|
||||||
InlayId,
|
InlayId,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
|
editor_settings::SeedQuerySetting, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll,
|
||||||
persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorEvent, EditorSettings,
|
Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
|
||||||
ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
|
NavigationData, ToPoint as _,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
|
@ -682,8 +682,7 @@ impl Item for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
hide_link_definition(self, cx);
|
self.hide_hovered_link(cx);
|
||||||
self.link_go_to_definition_state.last_trigger_point = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dirty(&self, cx: &AppContext) -> bool {
|
fn is_dirty(&self, cx: &AppContext) -> bool {
|
||||||
|
|
|
@ -4,7 +4,8 @@ use crate::{
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyWindowHandle, AppContext, Keystroke, ModelContext, View, ViewContext, VisualTestContext,
|
AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext,
|
||||||
|
VisualTestContext,
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -187,6 +188,31 @@ impl EditorTestContext {
|
||||||
ranges[0].start.to_display_point(&snapshot)
|
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 `»`
|
// Returns anchors for the current buffer using `«` and `»`
|
||||||
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
|
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
|
||||||
let ranges = self.ranges(marked_text);
|
let ranges = self.ranges(marked_text);
|
||||||
|
@ -343,7 +369,7 @@ impl EditorTestContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for EditorTestContext {
|
impl Deref for EditorTestContext {
|
||||||
type Target = gpui::TestAppContext;
|
type Target = gpui::VisualTestContext;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.cx
|
&self.cx
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||||
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
||||||
ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform,
|
ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers,
|
||||||
Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||||
ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
|
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||||
|
TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
|
@ -236,6 +237,11 @@ impl TestAppContext {
|
||||||
self.test_platform.has_pending_prompt()
|
self.test_platform.has_pending_prompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All the urls that have been opened with cx.open_url() during this test.
|
||||||
|
pub fn opened_url(&self) -> Option<String> {
|
||||||
|
self.test_platform.opened_url.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Simulates the user resizing the window to the new size.
|
/// Simulates the user resizing the window to the new size.
|
||||||
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
|
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
|
||||||
self.test_window(window_handle).simulate_resize(size);
|
self.test_window(window_handle).simulate_resize(size);
|
||||||
|
@ -625,6 +631,36 @@ impl<'a> VisualTestContext {
|
||||||
self.cx.simulate_input(self.window, input)
|
self.cx.simulate_input(self.window, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simulate a mouse move event to the given point
|
||||||
|
pub fn simulate_mouse_move(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
|
||||||
|
self.simulate_event(MouseMoveEvent {
|
||||||
|
position,
|
||||||
|
modifiers,
|
||||||
|
pressed_button: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulate a primary mouse click at the given point
|
||||||
|
pub fn simulate_click(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
|
||||||
|
self.simulate_event(MouseDownEvent {
|
||||||
|
position,
|
||||||
|
modifiers,
|
||||||
|
button: MouseButton::Left,
|
||||||
|
click_count: 1,
|
||||||
|
});
|
||||||
|
self.simulate_event(MouseUpEvent {
|
||||||
|
position,
|
||||||
|
modifiers,
|
||||||
|
button: MouseButton::Left,
|
||||||
|
click_count: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulate a modifiers changed event
|
||||||
|
pub fn simulate_modifiers_change(&mut self, modifiers: Modifiers) {
|
||||||
|
self.simulate_event(ModifiersChangedEvent { modifiers })
|
||||||
|
}
|
||||||
|
|
||||||
/// Simulates the user resizing the window to the new size.
|
/// Simulates the user resizing the window to the new size.
|
||||||
pub fn simulate_resize(&self, size: Size<Pixels>) {
|
pub fn simulate_resize(&self, size: Size<Pixels>) {
|
||||||
self.simulate_window_resize(self.window, size)
|
self.simulate_window_resize(self.window, size)
|
||||||
|
|
|
@ -170,4 +170,34 @@ impl Modifiers {
|
||||||
pub fn modified(&self) -> bool {
|
pub fn modified(&self) -> bool {
|
||||||
self.control || self.alt || self.shift || self.command || self.function
|
self.control || self.alt || self.shift || self.command || self.function
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// helper method for Modifiers with no modifiers
|
||||||
|
pub fn none() -> Modifiers {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper method for Modifiers with just command
|
||||||
|
pub fn command() -> Modifiers {
|
||||||
|
Modifiers {
|
||||||
|
command: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper method for Modifiers with just shift
|
||||||
|
pub fn shift() -> Modifiers {
|
||||||
|
Modifiers {
|
||||||
|
shift: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper method for Modifiers with command + shift
|
||||||
|
pub fn command_shift() -> Modifiers {
|
||||||
|
Modifiers {
|
||||||
|
shift: true,
|
||||||
|
command: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub(crate) struct TestPlatform {
|
||||||
active_cursor: Mutex<CursorStyle>,
|
active_cursor: Mutex<CursorStyle>,
|
||||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||||
pub(crate) prompts: RefCell<TestPrompts>,
|
pub(crate) prompts: RefCell<TestPrompts>,
|
||||||
|
pub opened_url: RefCell<Option<String>>,
|
||||||
weak: Weak<Self>,
|
weak: Weak<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ impl TestPlatform {
|
||||||
active_window: Default::default(),
|
active_window: Default::default(),
|
||||||
current_clipboard_item: Mutex::new(None),
|
current_clipboard_item: Mutex::new(None),
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
|
opened_url: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,8 +190,8 @@ impl Platform for TestPlatform {
|
||||||
|
|
||||||
fn stop_display_link(&self, _display_id: DisplayId) {}
|
fn stop_display_link(&self, _display_id: DisplayId) {}
|
||||||
|
|
||||||
fn open_url(&self, _url: &str) {
|
fn open_url(&self, url: &str) {
|
||||||
unimplemented!()
|
*self.opened_url.borrow_mut() = Some(url.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
|
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
|
||||||
|
|
Loading…
Reference in a new issue