Rework terminal highlight event flow

This commit is contained in:
Kirill Bulatov 2023-07-18 13:28:44 +03:00
parent 6f7a6e57fc
commit 10db05f87f
3 changed files with 112 additions and 116 deletions

View file

@ -33,7 +33,6 @@ use mappings::mouse::{
use procinfo::LocalProcessInfo; use procinfo::LocalProcessInfo;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smol::channel::Sender;
use util::truncate_and_trailoff; use util::truncate_and_trailoff;
use std::{ use std::{
@ -90,12 +89,18 @@ pub enum Event {
Wakeup, Wakeup,
BlinkChanged, BlinkChanged,
SelectionsChanged, SelectionsChanged,
OpenUrl(String), NewNavigationTarget(MaybeNavigationTarget),
ProbePathOpen { Open(MaybeNavigationTarget),
maybe_path: String, }
can_open_tx: Sender<bool>,
}, /// A string inside terminal, potentially useful as a URI that can be opened.
OpenPath(String), #[derive(Clone, Debug)]
pub enum MaybeNavigationTarget {
/// HTTP, git, etc. string determined by the [`URL_REGEX`] regex.
Url(String),
/// File system path, absolute or relative, existing or not.
/// Might have line and column number(s) attached as `file.rs:1:23`
PathLike(String),
} }
#[derive(Clone)] #[derive(Clone)]
@ -502,6 +507,7 @@ impl TerminalBuilder {
next_link_id: 0, next_link_id: 0,
selection_phase: SelectionPhase::Ended, selection_phase: SelectionPhase::Ended,
cmd_pressed: false, cmd_pressed: false,
found_word: false,
}; };
Ok(TerminalBuilder { Ok(TerminalBuilder {
@ -654,6 +660,7 @@ pub struct Terminal {
next_link_id: usize, next_link_id: usize,
selection_phase: SelectionPhase, selection_phase: SelectionPhase,
cmd_pressed: bool, cmd_pressed: bool,
found_word: bool,
} }
impl Terminal { impl Terminal {
@ -834,7 +841,7 @@ impl Terminal {
.grid_clamp(term, alacritty_terminal::index::Boundary::Cursor); .grid_clamp(term, alacritty_terminal::index::Boundary::Cursor);
let link = term.grid().index(point).hyperlink(); let link = term.grid().index(point).hyperlink();
let found_url = if link.is_some() { let found_word = if link.is_some() {
let mut min_index = point; let mut min_index = point;
loop { loop {
let new_min_index = let new_min_index =
@ -875,20 +882,21 @@ impl Terminal {
None None
}; };
if let Some((maybe_url_or_path, is_url, url_match)) = found_url { self.found_word = found_word.is_some();
if let Some((maybe_url_or_path, is_url, url_match)) = found_word {
if *open { if *open {
let event = if is_url { let target = if is_url {
Event::OpenUrl(maybe_url_or_path) MaybeNavigationTarget::Url(maybe_url_or_path)
} else { } else {
Event::OpenPath(maybe_url_or_path) MaybeNavigationTarget::PathLike(maybe_url_or_path)
}; };
cx.emit(event); cx.emit(Event::Open(target));
} else { } else {
self.update_selected_word( self.update_selected_word(
prev_hovered_word, prev_hovered_word,
maybe_url_or_path,
url_match, url_match,
!is_url, maybe_url_or_path,
is_url,
cx, cx,
); );
} }
@ -900,9 +908,9 @@ impl Terminal {
fn update_selected_word( fn update_selected_word(
&mut self, &mut self,
prev_word: Option<HoveredWord>, prev_word: Option<HoveredWord>,
word: String,
word_match: RangeInclusive<Point>, word_match: RangeInclusive<Point>,
should_probe_word: bool, word: String,
is_url: bool,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
if let Some(prev_word) = prev_word { if let Some(prev_word) = prev_word {
@ -912,43 +920,21 @@ impl Terminal {
word_match, word_match,
id: prev_word.id, id: prev_word.id,
}); });
} else if should_probe_word { return;
let (can_open_tx, can_open_rx) = smol::channel::bounded(1);
cx.emit(Event::ProbePathOpen {
maybe_path: word.clone(),
can_open_tx,
});
cx.spawn(|terminal, mut cx| async move {
let can_open = can_open_rx.recv().await.unwrap_or(false);
terminal.update(&mut cx, |terminal, cx| {
if can_open {
terminal.last_content.last_hovered_word = Some(HoveredWord {
word,
word_match,
id: terminal.next_link_id(),
});
} else {
terminal.last_content.last_hovered_word.take();
}
cx.notify();
});
})
.detach();
} else {
self.last_content.last_hovered_word = Some(HoveredWord {
word,
word_match,
id: self.next_link_id(),
});
} }
} else {
self.last_content.last_hovered_word = Some(HoveredWord {
word,
word_match,
id: self.next_link_id(),
});
} }
self.last_content.last_hovered_word = Some(HoveredWord {
word: word.clone(),
word_match,
id: self.next_link_id(),
});
let navigation_target = if is_url {
MaybeNavigationTarget::Url(word)
} else {
MaybeNavigationTarget::PathLike(word)
};
cx.emit(Event::NewNavigationTarget(navigation_target));
} }
fn next_link_id(&mut self) -> usize { fn next_link_id(&mut self) -> usize {
@ -1031,17 +1017,8 @@ impl Terminal {
} }
pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool {
let cmd = modifiers.cmd; let changed = self.cmd_pressed != modifiers.cmd;
let changed = self.cmd_pressed != cmd; self.cmd_pressed = modifiers.cmd;
if changed {
self.cmd_pressed = cmd;
if cmd {
self.refresh_hovered_word();
} else {
self.last_content.last_hovered_word.take();
}
}
changed changed
} }
@ -1336,7 +1313,7 @@ impl Terminal {
} }
} }
pub fn refresh_hovered_word(&mut self) { fn refresh_hovered_word(&mut self) {
self.word_from_position(self.last_mouse_position); self.word_from_position(self.last_mouse_position);
} }
@ -1415,6 +1392,10 @@ impl Terminal {
}) })
.unwrap_or_else(|| "Terminal".to_string()) .unwrap_or_else(|| "Terminal".to_string())
} }
pub fn can_navigate_to_selected_word(&self) -> bool {
self.cmd_pressed && self.found_word
}
} }
impl Drop for Terminal { impl Drop for Terminal {

View file

@ -163,6 +163,7 @@ pub struct TerminalElement {
terminal: WeakModelHandle<Terminal>, terminal: WeakModelHandle<Terminal>,
focused: bool, focused: bool,
cursor_visible: bool, cursor_visible: bool,
can_navigate_to_selected_word: bool,
} }
impl TerminalElement { impl TerminalElement {
@ -170,11 +171,13 @@ impl TerminalElement {
terminal: WeakModelHandle<Terminal>, terminal: WeakModelHandle<Terminal>,
focused: bool, focused: bool,
cursor_visible: bool, cursor_visible: bool,
can_navigate_to_selected_word: bool,
) -> TerminalElement { ) -> TerminalElement {
TerminalElement { TerminalElement {
terminal, terminal,
focused, focused,
cursor_visible, cursor_visible,
can_navigate_to_selected_word,
} }
} }
@ -580,13 +583,17 @@ impl Element<TerminalView> for TerminalElement {
let background_color = terminal_theme.background; let background_color = terminal_theme.background;
let terminal_handle = self.terminal.upgrade(cx).unwrap(); let terminal_handle = self.terminal.upgrade(cx).unwrap();
let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| { let last_hovered_word = terminal_handle.update(cx, |terminal, cx| {
terminal.set_size(dimensions); terminal.set_size(dimensions);
terminal.try_sync(cx); terminal.try_sync(cx);
terminal.last_content.last_hovered_word.clone() if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
terminal.last_content.last_hovered_word.clone()
} else {
None
}
}); });
let hyperlink_tooltip = last_hovered_hyperlink.map(|hovered_word| { let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
let mut tooltip = Overlay::new( let mut tooltip = Overlay::new(
Empty::new() Empty::new()
.contained() .contained()
@ -619,7 +626,6 @@ impl Element<TerminalView> for TerminalElement {
cursor_char, cursor_char,
selection, selection,
cursor, cursor,
last_hovered_word,
.. ..
} = { &terminal_handle.read(cx).last_content }; } = { &terminal_handle.read(cx).last_content };

View file

@ -33,7 +33,7 @@ use terminal::{
index::Point, index::Point,
term::{search::RegexSearch, TermMode}, term::{search::RegexSearch, TermMode},
}, },
Event, Terminal, TerminalBlink, WorkingDirectory, Event, MaybeNavigationTarget, Terminal, TerminalBlink, WorkingDirectory,
}; };
use util::{paths::PathLikeWithPosition, ResultExt}; use util::{paths::PathLikeWithPosition, ResultExt};
use workspace::{ use workspace::{
@ -93,6 +93,7 @@ pub struct TerminalView {
blinking_on: bool, blinking_on: bool,
blinking_paused: bool, blinking_paused: bool,
blink_epoch: usize, blink_epoch: usize,
can_navigate_to_selected_word: bool,
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
} }
@ -169,55 +170,61 @@ impl TerminalView {
.detach(); .detach();
} }
} }
Event::ProbePathOpen { Event::NewNavigationTarget(maybe_navigation_target) => {
maybe_path, this.can_navigate_to_selected_word = match maybe_navigation_target {
can_open_tx, MaybeNavigationTarget::Url(_) => true,
} => { MaybeNavigationTarget::PathLike(maybe_path) => {
let can_open = !possible_open_targets(&workspace, maybe_path, cx).is_empty(); !possible_open_targets(&workspace, maybe_path, cx).is_empty()
can_open_tx.send_blocking(can_open).ok(); }
}
Event::OpenUrl(url) => cx.platform().open_url(url),
Event::OpenPath(maybe_path) => {
let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
if let Some(path) = potential_abs_paths.into_iter().next() {
let visible = path.path_like.is_dir();
let task_workspace = workspace.clone();
cx.spawn(|_, mut cx| async move {
let opened_item = task_workspace
.update(&mut cx, |workspace, cx| {
workspace.open_abs_path(path.path_like, visible, cx)
})
.context("workspace update")?
.await
.context("workspace update")?;
if let Some(row) = path.row {
let col = path.column.unwrap_or(0);
if let Some(active_editor) = opened_item.downcast::<Editor>() {
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx).display_snapshot;
let point = snapshot.buffer_snapshot.clip_point(
language::Point::new(
row.saturating_sub(1),
col.saturating_sub(1),
),
Bias::Left,
);
editor.change_selections(
Some(Autoscroll::center()),
cx,
|s| s.select_ranges([point..point]),
);
})
.log_err();
}
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
} }
} }
Event::Open(maybe_navigation_target) => match maybe_navigation_target {
MaybeNavigationTarget::Url(url) => cx.platform().open_url(url),
MaybeNavigationTarget::PathLike(maybe_path) => {
if !this.can_navigate_to_selected_word {
return;
}
let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
if let Some(path) = potential_abs_paths.into_iter().next() {
let visible = path.path_like.is_dir();
let task_workspace = workspace.clone();
cx.spawn(|_, mut cx| async move {
let opened_item = task_workspace
.update(&mut cx, |workspace, cx| {
workspace.open_abs_path(path.path_like, visible, cx)
})
.context("workspace update")?
.await
.context("workspace update")?;
if let Some(row) = path.row {
let col = path.column.unwrap_or(0);
if let Some(active_editor) = opened_item.downcast::<Editor>() {
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx).display_snapshot;
let point = snapshot.buffer_snapshot.clip_point(
language::Point::new(
row.saturating_sub(1),
col.saturating_sub(1),
),
Bias::Left,
);
editor.change_selections(
Some(Autoscroll::center()),
cx,
|s| s.select_ranges([point..point]),
);
})
.log_err();
}
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
},
_ => cx.emit(event.clone()), _ => cx.emit(event.clone()),
}) })
.detach(); .detach();
@ -231,6 +238,7 @@ impl TerminalView {
blinking_on: false, blinking_on: false,
blinking_paused: false, blinking_paused: false,
blink_epoch: 0, blink_epoch: 0,
can_navigate_to_selected_word: false,
workspace_id, workspace_id,
} }
} }
@ -466,6 +474,7 @@ impl View for TerminalView {
terminal_handle, terminal_handle,
focused, focused,
self.should_show_cursor(focused, cx), self.should_show_cursor(focused, cx),
self.can_navigate_to_selected_word,
) )
.contained(), .contained(),
) )