Finished terminal hyperlinks for now
This commit is contained in:
parent
d2d49633f1
commit
4bc0afdafa
3 changed files with 124 additions and 73 deletions
|
@ -14,6 +14,7 @@ pub struct Overlay {
|
|||
anchor_position: Option<Vector2F>,
|
||||
anchor_corner: AnchorCorner,
|
||||
fit_mode: OverlayFitMode,
|
||||
position_mode: OverlayPositionMode,
|
||||
hoverable: bool,
|
||||
}
|
||||
|
||||
|
@ -24,6 +25,12 @@ pub enum OverlayFitMode {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum OverlayPositionMode {
|
||||
Window,
|
||||
Local,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AnchorCorner {
|
||||
TopLeft,
|
||||
|
@ -73,6 +80,7 @@ impl Overlay {
|
|||
anchor_position: None,
|
||||
anchor_corner: AnchorCorner::TopLeft,
|
||||
fit_mode: OverlayFitMode::None,
|
||||
position_mode: OverlayPositionMode::Window,
|
||||
hoverable: false,
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +100,11 @@ impl Overlay {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self {
|
||||
self.position_mode = position_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hoverable(mut self, hoverable: bool) -> Self {
|
||||
self.hoverable = hoverable;
|
||||
self
|
||||
|
@ -123,8 +136,20 @@ impl Element for Overlay {
|
|||
size: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
) {
|
||||
let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
|
||||
let mut bounds = self.anchor_corner.get_bounds(anchor_position, *size);
|
||||
let (anchor_position, mut bounds) = match self.position_mode {
|
||||
OverlayPositionMode::Window => {
|
||||
let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
|
||||
let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
|
||||
(anchor_position, bounds)
|
||||
}
|
||||
OverlayPositionMode::Local => {
|
||||
let anchor_position = self.anchor_position.unwrap_or_default();
|
||||
let bounds = self
|
||||
.anchor_corner
|
||||
.get_bounds(bounds.origin() + anchor_position, *size);
|
||||
(anchor_position, bounds)
|
||||
}
|
||||
};
|
||||
|
||||
match self.fit_mode {
|
||||
OverlayFitMode::SnapToWindow => {
|
||||
|
|
|
@ -385,6 +385,8 @@ impl TerminalBuilder {
|
|||
breadcrumb_text: String::new(),
|
||||
scroll_px: 0.,
|
||||
last_mouse_position: None,
|
||||
next_link_id: 0,
|
||||
selection_phase: SelectionPhase::Ended,
|
||||
};
|
||||
|
||||
Ok(TerminalBuilder {
|
||||
|
@ -471,7 +473,7 @@ pub struct TerminalContent {
|
|||
cursor: RenderableCursor,
|
||||
cursor_char: char,
|
||||
size: TerminalSize,
|
||||
last_hovered_hyperlink: Option<(String, RangeInclusive<Point>)>,
|
||||
last_hovered_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
|
||||
}
|
||||
|
||||
impl Default for TerminalContent {
|
||||
|
@ -493,6 +495,12 @@ impl Default for TerminalContent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum SelectionPhase {
|
||||
Selecting,
|
||||
Ended,
|
||||
}
|
||||
|
||||
pub struct Terminal {
|
||||
pty_tx: Notifier,
|
||||
term: Arc<FairMutex<Term<ZedListener>>>,
|
||||
|
@ -511,6 +519,8 @@ pub struct Terminal {
|
|||
shell_fd: u32,
|
||||
foreground_process_info: Option<LocalProcessInfo>,
|
||||
scroll_px: f32,
|
||||
next_link_id: usize,
|
||||
selection_phase: SelectionPhase,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
|
@ -654,7 +664,7 @@ impl Terminal {
|
|||
}
|
||||
InternalEvent::ScrollToPoint(point) => term.scroll_to_point(*point),
|
||||
InternalEvent::Hyperlink(position, open) => {
|
||||
self.last_content.last_hovered_hyperlink = None;
|
||||
let prev_hyperlink = self.last_content.last_hovered_hyperlink.take();
|
||||
|
||||
let point = grid_point(
|
||||
*position,
|
||||
|
@ -668,13 +678,37 @@ impl Terminal {
|
|||
if *open {
|
||||
open_uri(&url).log_err();
|
||||
} else {
|
||||
self.last_content.last_hovered_hyperlink = Some((url, url_match));
|
||||
self.update_hyperlink(prev_hyperlink, url, url_match);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hyperlink(
|
||||
&mut self,
|
||||
prev_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
|
||||
url: String,
|
||||
url_match: RangeInclusive<Point>,
|
||||
) {
|
||||
if let Some(prev_hyperlink) = prev_hyperlink {
|
||||
if prev_hyperlink.0 == url && prev_hyperlink.1 == url_match {
|
||||
self.last_content.last_hovered_hyperlink = Some((url, url_match, prev_hyperlink.2));
|
||||
} else {
|
||||
self.last_content.last_hovered_hyperlink =
|
||||
Some((url, url_match, self.next_link_id()));
|
||||
}
|
||||
} else {
|
||||
self.last_content.last_hovered_hyperlink = Some((url, url_match, self.next_link_id()));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_link_id(&mut self) -> usize {
|
||||
let res = self.next_link_id;
|
||||
self.next_link_id = self.next_link_id.wrapping_add(1);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn last_content(&self) -> &TerminalContent {
|
||||
&self.last_content
|
||||
}
|
||||
|
@ -846,7 +880,8 @@ impl Terminal {
|
|||
}
|
||||
|
||||
pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
|
||||
self.last_content.last_hovered_hyperlink = None;
|
||||
let prev_hyperlink = self.last_content.last_hovered_hyperlink.take();
|
||||
|
||||
let position = e.position.sub(origin);
|
||||
self.last_mouse_position = Some(position);
|
||||
if self.mouse_mode(e.shift) {
|
||||
|
@ -862,19 +897,26 @@ impl Terminal {
|
|||
self.pty_tx.notify(bytes);
|
||||
}
|
||||
}
|
||||
} else if e.cmd {
|
||||
self.fill_hyperlink(Some(position));
|
||||
} else {
|
||||
self.fill_hyperlink(Some(position), prev_hyperlink);
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_hyperlink(&mut self, position: Option<Vector2F>) {
|
||||
if let Some(position) = position {
|
||||
fn fill_hyperlink(
|
||||
&mut self,
|
||||
position: Option<Vector2F>,
|
||||
prev_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
|
||||
) {
|
||||
if self.selection_phase == SelectionPhase::Selecting {
|
||||
self.last_content.last_hovered_hyperlink = None;
|
||||
} else if let Some(position) = position {
|
||||
let content_index = content_index_for_mouse(position, &self.last_content);
|
||||
let link = self.last_content.cells[content_index].hyperlink();
|
||||
if link.is_some() {
|
||||
let mut min_index = content_index;
|
||||
loop {
|
||||
if self.last_content.cells[min_index - 1].hyperlink() == link {
|
||||
if min_index >= 1 && self.last_content.cells[min_index - 1].hyperlink() == link
|
||||
{
|
||||
min_index = min_index - 1;
|
||||
} else {
|
||||
break;
|
||||
|
@ -882,21 +924,24 @@ impl Terminal {
|
|||
}
|
||||
|
||||
let mut max_index = content_index;
|
||||
let len = self.last_content.cells.len();
|
||||
loop {
|
||||
if self.last_content.cells[max_index + 1].hyperlink() == link {
|
||||
if max_index < len - 1
|
||||
&& self.last_content.cells[max_index + 1].hyperlink() == link
|
||||
{
|
||||
max_index = max_index + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.last_content.last_hovered_hyperlink = link.map(|link| {
|
||||
(
|
||||
link.uri().to_owned(),
|
||||
self.last_content.cells[min_index].point
|
||||
..=self.last_content.cells[max_index].point,
|
||||
)
|
||||
});
|
||||
if let Some(link) = link {
|
||||
let url = link.uri().to_owned();
|
||||
let url_match = self.last_content.cells[min_index].point
|
||||
..=self.last_content.cells[max_index].point;
|
||||
|
||||
self.update_hyperlink(prev_hyperlink, url, url_match);
|
||||
};
|
||||
} else {
|
||||
self.events
|
||||
.push_back(InternalEvent::Hyperlink(position, false));
|
||||
|
@ -909,6 +954,7 @@ impl Terminal {
|
|||
self.last_mouse_position = Some(position);
|
||||
|
||||
if !self.mouse_mode(e.shift) {
|
||||
self.selection_phase = SelectionPhase::Selecting;
|
||||
// Alacritty has the same ordering, of first updating the selection
|
||||
// then scrolling 15ms later
|
||||
self.events
|
||||
|
@ -969,7 +1015,9 @@ impl Terminal {
|
|||
pub fn left_click(&mut self, e: &ClickRegionEvent, origin: Vector2F) {
|
||||
let position = e.position.sub(origin);
|
||||
if !self.mouse_mode(e.shift) {
|
||||
if e.cmd {
|
||||
if self.last_content.last_hovered_hyperlink.is_some()
|
||||
&& self.last_content.selection.is_none()
|
||||
{
|
||||
let mouse_cell_index = content_index_for_mouse(position, &self.last_content);
|
||||
if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
|
||||
open_uri(link.uri()).log_err();
|
||||
|
@ -1021,6 +1069,7 @@ impl Terminal {
|
|||
// so let's do that here
|
||||
self.copy();
|
||||
}
|
||||
self.selection_phase = SelectionPhase::Ended;
|
||||
self.last_mouse = None;
|
||||
}
|
||||
|
||||
|
@ -1061,10 +1110,10 @@ impl Terminal {
|
|||
}
|
||||
|
||||
pub fn refresh_hyperlink(&mut self, cmd: bool) -> bool {
|
||||
self.last_content.last_hovered_hyperlink = None;
|
||||
let prev_hyperlink = self.last_content.last_hovered_hyperlink.take();
|
||||
|
||||
if cmd {
|
||||
self.fill_hyperlink(self.last_mouse_position);
|
||||
self.fill_hyperlink(self.last_mouse_position, prev_hyperlink);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
|
@ -7,7 +7,7 @@ use alacritty_terminal::{
|
|||
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{Overlay, Tooltip},
|
||||
elements::{Empty, Overlay},
|
||||
fonts::{HighlightStyle, Properties, Style::Italic, TextStyle, Underline, Weight},
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
|
@ -15,7 +15,7 @@ use gpui::{
|
|||
},
|
||||
serde_json::json,
|
||||
text_layout::{Line, RunStyle},
|
||||
Axis, Element, ElementBox, Event, EventContext, FontCache, KeyDownEvent, ModelContext,
|
||||
Element, ElementBox, Event, EventContext, FontCache, KeyDownEvent, ModelContext,
|
||||
ModifiersChangedEvent, MouseButton, MouseRegion, PaintContext, Quad, SizeConstraint,
|
||||
TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
|
@ -324,7 +324,6 @@ impl TerminalElement {
|
|||
.unwrap_or_default();
|
||||
|
||||
if indexed.cell.hyperlink().is_some() {
|
||||
underline.squiggly = true;
|
||||
if underline.thickness == OrderedFloat(0.) {
|
||||
underline.thickness = OrderedFloat(1.);
|
||||
}
|
||||
|
@ -594,51 +593,34 @@ impl Element for TerminalElement {
|
|||
};
|
||||
let terminal_handle = self.terminal.upgrade(cx).unwrap();
|
||||
|
||||
let (last_hovered_hyperlink, last_mouse) =
|
||||
terminal_handle.update(cx.app, |terminal, cx| {
|
||||
terminal.set_size(dimensions);
|
||||
terminal.try_sync(cx);
|
||||
(
|
||||
terminal.last_content.last_hovered_hyperlink.clone(),
|
||||
terminal.last_mouse_position,
|
||||
)
|
||||
});
|
||||
let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
|
||||
terminal.set_size(dimensions);
|
||||
terminal.try_sync(cx);
|
||||
terminal.last_content.last_hovered_hyperlink.clone()
|
||||
});
|
||||
|
||||
let view_handle = self.view.clone();
|
||||
let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _)| {
|
||||
last_mouse.and_then(|last_mouse| {
|
||||
view_handle.upgrade(cx).map(|handle| {
|
||||
let mut tooltip = cx.render(&handle, |_, cx| {
|
||||
// TODO: Use the correct dynamic line height
|
||||
// let mut collapsed_tooltip = Tooltip::render_tooltip(
|
||||
// uri.clone(),
|
||||
// tooltip_style.clone(),
|
||||
// None,
|
||||
// false,
|
||||
// )
|
||||
// .boxed();
|
||||
let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _, id)| {
|
||||
// last_mouse.and_then(|_last_mouse| {
|
||||
view_handle.upgrade(cx).map(|handle| {
|
||||
let mut tooltip = cx.render(&handle, |_, cx| {
|
||||
Overlay::new(
|
||||
Empty::new()
|
||||
.contained()
|
||||
.constrained()
|
||||
.with_width(dimensions.width())
|
||||
.with_height(dimensions.height())
|
||||
.with_tooltip::<TerminalElement, _>(id, uri, None, tooltip_style, cx)
|
||||
.boxed(),
|
||||
)
|
||||
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
|
||||
.boxed()
|
||||
});
|
||||
|
||||
Overlay::new(
|
||||
Tooltip::render_tooltip(uri, tooltip_style, None, false)
|
||||
.constrained()
|
||||
.with_height(text_style.line_height(cx.font_cache()))
|
||||
// .dynamically(move |constraint, cx| {
|
||||
// SizeConstraint::strict_along(
|
||||
// Axis::Vertical,
|
||||
// collapsed_tooltip.layout(constraint, cx).y(),
|
||||
// )
|
||||
// })
|
||||
.boxed(),
|
||||
)
|
||||
.with_fit_mode(gpui::elements::OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_position(last_mouse)
|
||||
.boxed()
|
||||
});
|
||||
|
||||
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||
tooltip
|
||||
})
|
||||
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||
tooltip
|
||||
})
|
||||
// })
|
||||
});
|
||||
|
||||
let TerminalContent {
|
||||
|
@ -672,7 +654,7 @@ impl Element for TerminalElement {
|
|||
self.modal,
|
||||
last_hovered_hyperlink
|
||||
.as_ref()
|
||||
.map(|(_, range)| (link_style, range)),
|
||||
.map(|(_, range, _)| (link_style, range)),
|
||||
);
|
||||
|
||||
//Layout cursor. Rectangle is used for IME, so we should lay it out even
|
||||
|
@ -822,12 +804,7 @@ impl Element for TerminalElement {
|
|||
}
|
||||
|
||||
if let Some(element) = &mut layout.hyperlink_tooltip {
|
||||
element.paint(
|
||||
visible_bounds.lower_left()
|
||||
- vec2f(-layout.size.cell_width, layout.size.line_height),
|
||||
visible_bounds,
|
||||
cx,
|
||||
)
|
||||
element.paint(origin, visible_bounds, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue