@@ -384,6 +384,7 @@ impl TerminalBuilder {
foreground_process_info: None,
breadcrumb_text: String::new(),
scroll_px: 0.,
+ last_mouse_position: None,
};
Ok(TerminalBuilder {
@@ -496,7 +497,10 @@ pub struct Terminal {
pty_tx: Notifier,
term: Arc<FairMutex<Term<ZedListener>>>,
events: VecDeque<InternalEvent>,
+ /// This is only used for mouse mode cell change detection
last_mouse: Option<(Point, AlacDirection)>,
+ /// This is only used for terminal hyperlink checking
+ last_mouse_position: Option<Vector2F>,
pub matches: Vec<RangeInclusive<Point>>,
last_content: TerminalContent,
last_synced: Instant,
@@ -813,7 +817,8 @@ impl Terminal {
}
}
- pub fn focus_out(&self) {
+ pub fn focus_out(&mut self) {
+ self.last_mouse_position = None;
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[O".to_string());
}
@@ -843,6 +848,7 @@ impl Terminal {
pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
self.last_content.last_hovered_hyperlink = None;
let position = e.position.sub(origin);
+ self.last_mouse_position = Some(position);
if self.mouse_mode(e.shift) {
let point = grid_point(
position,
@@ -857,9 +863,14 @@ impl Terminal {
}
}
} else if e.cmd {
+ self.fill_hyperlink(Some(position));
+ }
+ }
+
+ fn fill_hyperlink(&mut self, position: Option<Vector2F>) {
+ 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 {
@@ -895,6 +906,7 @@ impl Terminal {
pub fn mouse_drag(&mut self, e: DragRegionEvent, origin: Vector2F) {
let position = e.position.sub(origin);
+ self.last_mouse_position = Some(position);
if !self.mouse_mode(e.shift) {
// Alacritty has the same ordering, of first updating the selection
@@ -1048,6 +1060,17 @@ impl Terminal {
}
}
+ pub fn refresh_hyperlink(&mut self, cmd: bool) -> bool {
+ self.last_content.last_hovered_hyperlink = None;
+
+ if cmd {
+ self.fill_hyperlink(self.last_mouse_position);
+ true
+ } else {
+ false
+ }
+ }
+
fn determine_scroll_lines(
&mut self,
e: &ScrollWheelRegionEvent,
@@ -7,15 +7,17 @@ use alacritty_terminal::{
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
use gpui::{
color::Color,
- fonts::{Properties, Style::Italic, TextStyle, Underline, Weight},
+ elements::{Overlay, Tooltip},
+ fonts::{HighlightStyle, Properties, Style::Italic, TextStyle, Underline, Weight},
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
serde_json::json,
text_layout::{Line, RunStyle},
- Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseRegion,
- PaintContext, Quad, TextLayoutCache, WeakModelHandle, WeakViewHandle,
+ Axis, Element, ElementBox, Event, EventContext, FontCache, KeyDownEvent, ModelContext,
+ ModifiersChangedEvent, MouseButton, MouseRegion, PaintContext, Quad, SizeConstraint,
+ TextLayoutCache, WeakModelHandle, WeakViewHandle,
};
use itertools::Itertools;
use ordered_float::OrderedFloat;
@@ -42,6 +44,7 @@ pub struct LayoutState {
size: TerminalSize,
mode: TermMode,
display_offset: usize,
+ hyperlink_tooltip: Option<ElementBox>,
}
///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@@ -180,6 +183,7 @@ impl TerminalElement {
text_layout_cache: &TextLayoutCache,
font_cache: &FontCache,
modal: bool,
+ hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
let mut cells = vec![];
let mut rects = vec![];
@@ -245,6 +249,7 @@ impl TerminalElement {
text_style,
font_cache,
modal,
+ hyperlink,
);
let layout_cell = text_layout_cache.layout_str(
@@ -304,6 +309,7 @@ impl TerminalElement {
text_style: &TextStyle,
font_cache: &FontCache,
modal: bool,
+ hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
) -> RunStyle {
let flags = indexed.cell.flags;
let fg = convert_color(&fg, &style.colors, modal);
@@ -339,11 +345,25 @@ impl TerminalElement {
.select_font(text_style.font_family_id, &properties)
.unwrap_or(text_style.font_id);
- RunStyle {
+ let mut result = RunStyle {
color: fg,
font_id,
underline,
+ };
+
+ if let Some((style, range)) = hyperlink {
+ if range.contains(&indexed.point) {
+ if let Some(underline) = style.underline {
+ result.underline = underline;
+ }
+
+ if let Some(color) = style.color {
+ result.color = color;
+ }
+ }
}
+
+ result
}
fn generic_button_handler<E>(
@@ -373,7 +393,7 @@ impl TerminalElement {
) {
let connection = self.terminal;
- let mut region = MouseRegion::new::<Self>(view_id, view_id, visible_bounds);
+ let mut region = MouseRegion::new::<Self>(view_id, 0, visible_bounds);
// Terminal Emulator controlled behavior:
region = region
@@ -549,6 +569,9 @@ impl Element for TerminalElement {
//Setup layout information
let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone.
+ let link_style = settings.theme.editor.link_definition;
+ let tooltip_style = settings.theme.tooltip.clone();
+
let text_style = TerminalElement::make_text_style(font_cache, settings);
let selection_color = settings.theme.editor.selection.selection;
let match_color = settings.theme.search.match_background;
@@ -571,9 +594,51 @@ impl Element for TerminalElement {
};
let terminal_handle = self.terminal.upgrade(cx).unwrap();
- terminal_handle.update(cx.app, |terminal, cx| {
- terminal.set_size(dimensions);
- terminal.try_sync(cx)
+ 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 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();
+
+ 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
+ })
+ })
});
let TerminalContent {
@@ -585,7 +650,7 @@ impl Element for TerminalElement {
cursor,
last_hovered_hyperlink,
..
- } = &terminal_handle.read(cx).last_content;
+ } = { &terminal_handle.read(cx).last_content };
// searches, highlights to a single range representations
let mut relative_highlighted_ranges = Vec::new();
@@ -605,6 +670,9 @@ impl Element for TerminalElement {
cx.text_layout_cache,
cx.font_cache(),
self.modal,
+ last_hovered_hyperlink
+ .as_ref()
+ .map(|(_, range)| (link_style, range)),
);
//Layout cursor. Rectangle is used for IME, so we should lay it out even
@@ -636,10 +704,11 @@ impl Element for TerminalElement {
)
};
+ let focused = self.focused;
TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
move |(cursor_position, block_width)| {
let shape = match cursor.shape {
- AlacCursorShape::Block if !self.focused => CursorShape::Hollow,
+ AlacCursorShape::Block if !focused => CursorShape::Hollow,
AlacCursorShape::Block => CursorShape::Block,
AlacCursorShape::Underline => CursorShape::Underscore,
AlacCursorShape::Beam => CursorShape::Bar,
@@ -672,6 +741,7 @@ impl Element for TerminalElement {
relative_highlighted_ranges,
mode: *mode,
display_offset: *display_offset,
+ hyperlink_tooltip,
},
)
}
@@ -694,7 +764,11 @@ impl Element for TerminalElement {
cx.scene.push_cursor_region(gpui::CursorRegion {
bounds,
- style: gpui::CursorStyle::IBeam,
+ style: if layout.hyperlink_tooltip.is_some() {
+ gpui::CursorStyle::PointingHand
+ } else {
+ gpui::CursorStyle::IBeam
+ },
});
cx.paint_layer(clip_bounds, |cx| {
@@ -746,6 +820,15 @@ 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,
+ )
+ }
});
}
@@ -784,6 +867,18 @@ impl Element for TerminalElement {
})
})
.unwrap_or(false)
+ } else if let Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) = event {
+ self.terminal
+ .upgrade(cx.app)
+ .map(|model_handle| {
+ if model_handle.update(cx.app, |term, _| term.refresh_hyperlink(*cmd)) {
+ cx.notify();
+ true
+ } else {
+ false
+ }
+ })
+ .unwrap_or(false)
} else {
false
}