Detect and open URLs properly
This commit is contained in:
parent
23f25562b5
commit
6123c67de9
3 changed files with 79 additions and 45 deletions
|
@ -74,7 +74,7 @@ const DEBUG_LINE_HEIGHT: f32 = 5.;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// Regex Copied from alacritty's ui_config.rs
|
// Regex Copied from alacritty's ui_config.rs
|
||||||
pub static ref URL_REGEX: RegexSearch = RegexSearch::new("(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+").unwrap();
|
static ref URL_REGEX: RegexSearch = RegexSearch::new("(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+").unwrap();
|
||||||
|
|
||||||
static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap();
|
static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap();
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,10 @@ pub enum Event {
|
||||||
Wakeup,
|
Wakeup,
|
||||||
BlinkChanged,
|
BlinkChanged,
|
||||||
SelectionsChanged,
|
SelectionsChanged,
|
||||||
Open(String),
|
Open {
|
||||||
|
is_url: bool,
|
||||||
|
maybe_url_or_path: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -592,7 +595,14 @@ pub struct TerminalContent {
|
||||||
pub cursor: RenderableCursor,
|
pub cursor: RenderableCursor,
|
||||||
pub cursor_char: char,
|
pub cursor_char: char,
|
||||||
pub size: TerminalSize,
|
pub size: TerminalSize,
|
||||||
pub last_hovered_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
|
pub last_hovered_word: Option<HoveredWord>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HoveredWord {
|
||||||
|
pub word: String,
|
||||||
|
pub word_match: RangeInclusive<Point>,
|
||||||
|
pub id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TerminalContent {
|
impl Default for TerminalContent {
|
||||||
|
@ -609,7 +619,7 @@ impl Default for TerminalContent {
|
||||||
},
|
},
|
||||||
cursor_char: Default::default(),
|
cursor_char: Default::default(),
|
||||||
size: Default::default(),
|
size: Default::default(),
|
||||||
last_hovered_hyperlink: None,
|
last_hovered_word: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,7 +636,7 @@ pub struct Terminal {
|
||||||
events: VecDeque<InternalEvent>,
|
events: VecDeque<InternalEvent>,
|
||||||
/// This is only used for mouse mode cell change detection
|
/// This is only used for mouse mode cell change detection
|
||||||
last_mouse: Option<(Point, AlacDirection)>,
|
last_mouse: Option<(Point, AlacDirection)>,
|
||||||
/// This is only used for terminal hyperlink checking
|
/// This is only used for terminal hovered word checking
|
||||||
last_mouse_position: Option<Vector2F>,
|
last_mouse_position: Option<Vector2F>,
|
||||||
pub matches: Vec<RangeInclusive<Point>>,
|
pub matches: Vec<RangeInclusive<Point>>,
|
||||||
pub last_content: TerminalContent,
|
pub last_content: TerminalContent,
|
||||||
|
@ -773,7 +783,7 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
InternalEvent::Scroll(scroll) => {
|
InternalEvent::Scroll(scroll) => {
|
||||||
term.scroll_display(*scroll);
|
term.scroll_display(*scroll);
|
||||||
self.refresh_hyperlink();
|
self.refresh_hovered_word();
|
||||||
}
|
}
|
||||||
InternalEvent::SetSelection(selection) => {
|
InternalEvent::SetSelection(selection) => {
|
||||||
term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
|
term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
|
||||||
|
@ -808,10 +818,10 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
InternalEvent::ScrollToPoint(point) => {
|
InternalEvent::ScrollToPoint(point) => {
|
||||||
term.scroll_to_point(*point);
|
term.scroll_to_point(*point);
|
||||||
self.refresh_hyperlink();
|
self.refresh_hovered_word();
|
||||||
}
|
}
|
||||||
InternalEvent::FindHyperlink(position, open) => {
|
InternalEvent::FindHyperlink(position, open) => {
|
||||||
let prev_hyperlink = self.last_content.last_hovered_hyperlink.take();
|
let prev_hovered_word = self.last_content.last_hovered_word.take();
|
||||||
|
|
||||||
let point = grid_point(
|
let point = grid_point(
|
||||||
*position,
|
*position,
|
||||||
|
@ -851,41 +861,57 @@ impl Terminal {
|
||||||
let url = link.unwrap().uri().to_owned();
|
let url = link.unwrap().uri().to_owned();
|
||||||
let url_match = min_index..=max_index;
|
let url_match = min_index..=max_index;
|
||||||
|
|
||||||
Some((url, url_match))
|
Some((url, true, url_match))
|
||||||
} else if let Some(url_match) = regex_match_at(term, point, &WORD_REGEX) {
|
} else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) {
|
||||||
let url = term.bounds_to_string(*url_match.start(), *url_match.end());
|
let maybe_url_or_path =
|
||||||
|
term.bounds_to_string(*word_match.start(), *word_match.end());
|
||||||
|
let is_url = regex_match_at(term, point, &URL_REGEX).is_some();
|
||||||
|
|
||||||
Some((url, url_match))
|
Some((maybe_url_or_path, is_url, word_match))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((url, url_match)) = found_url {
|
if let Some((maybe_url_or_path, is_url, url_match)) = found_url {
|
||||||
if *open {
|
if *open {
|
||||||
cx.emit(Event::Open(url))
|
cx.emit(Event::Open {
|
||||||
|
is_url,
|
||||||
|
maybe_url_or_path,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
self.update_hyperlink(prev_hyperlink, url, url_match);
|
self.update_selected_word(prev_hovered_word, maybe_url_or_path, url_match);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hyperlink(
|
fn update_selected_word(
|
||||||
&mut self,
|
&mut self,
|
||||||
prev_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
|
prev_word: Option<HoveredWord>,
|
||||||
url: String,
|
word: String,
|
||||||
url_match: RangeInclusive<Point>,
|
word_match: RangeInclusive<Point>,
|
||||||
) {
|
) {
|
||||||
if let Some(prev_hyperlink) = prev_hyperlink {
|
if let Some(prev_word) = prev_word {
|
||||||
if prev_hyperlink.0 == url && prev_hyperlink.1 == url_match {
|
if prev_word.word == word && prev_word.word_match == word_match {
|
||||||
self.last_content.last_hovered_hyperlink = Some((url, url_match, prev_hyperlink.2));
|
self.last_content.last_hovered_word = Some(HoveredWord {
|
||||||
|
word,
|
||||||
|
word_match,
|
||||||
|
id: prev_word.id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
self.last_content.last_hovered_hyperlink =
|
self.last_content.last_hovered_word = Some(HoveredWord {
|
||||||
Some((url, url_match, self.next_link_id()));
|
word,
|
||||||
|
word_match,
|
||||||
|
id: self.next_link_id(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.last_content.last_hovered_hyperlink = Some((url, url_match, self.next_link_id()));
|
self.last_content.last_hovered_word = Some(HoveredWord {
|
||||||
|
word,
|
||||||
|
word_match,
|
||||||
|
id: self.next_link_id(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -974,9 +1000,9 @@ impl Terminal {
|
||||||
if changed {
|
if changed {
|
||||||
self.cmd_pressed = cmd;
|
self.cmd_pressed = cmd;
|
||||||
if cmd {
|
if cmd {
|
||||||
self.refresh_hyperlink();
|
self.refresh_hovered_word();
|
||||||
} else {
|
} else {
|
||||||
self.last_content.last_hovered_hyperlink.take();
|
self.last_content.last_hovered_word.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1054,7 +1080,7 @@ impl Terminal {
|
||||||
cursor: content.cursor,
|
cursor: content.cursor,
|
||||||
cursor_char: term.grid()[content.cursor.point].c,
|
cursor_char: term.grid()[content.cursor.point].c,
|
||||||
size: last_content.size,
|
size: last_content.size,
|
||||||
last_hovered_hyperlink: last_content.last_hovered_hyperlink.clone(),
|
last_hovered_word: last_content.last_hovered_word.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,13 +1135,13 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.cmd_pressed {
|
} else if self.cmd_pressed {
|
||||||
self.hyperlink_from_position(Some(position));
|
self.word_from_position(Some(position));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hyperlink_from_position(&mut self, position: Option<Vector2F>) {
|
fn word_from_position(&mut self, position: Option<Vector2F>) {
|
||||||
if self.selection_phase == SelectionPhase::Selecting {
|
if self.selection_phase == SelectionPhase::Selecting {
|
||||||
self.last_content.last_hovered_hyperlink = None;
|
self.last_content.last_hovered_word = None;
|
||||||
} else if let Some(position) = position {
|
} else if let Some(position) = position {
|
||||||
self.events
|
self.events
|
||||||
.push_back(InternalEvent::FindHyperlink(position, false));
|
.push_back(InternalEvent::FindHyperlink(position, false));
|
||||||
|
@ -1274,8 +1300,8 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_hyperlink(&mut self) {
|
pub fn refresh_hovered_word(&mut self) {
|
||||||
self.hyperlink_from_position(self.last_mouse_position);
|
self.word_from_position(self.last_mouse_position);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option<i32> {
|
fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option<i32> {
|
||||||
|
|
|
@ -583,17 +583,23 @@ impl Element<TerminalView> for TerminalElement {
|
||||||
let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| {
|
let last_hovered_hyperlink = 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_hyperlink.clone()
|
terminal.last_content.last_hovered_word.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
|
let hyperlink_tooltip = last_hovered_hyperlink.map(|hovered_word| {
|
||||||
let mut tooltip = Overlay::new(
|
let mut tooltip = Overlay::new(
|
||||||
Empty::new()
|
Empty::new()
|
||||||
.contained()
|
.contained()
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(dimensions.width())
|
.with_width(dimensions.width())
|
||||||
.with_height(dimensions.height())
|
.with_height(dimensions.height())
|
||||||
.with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx),
|
.with_tooltip::<TerminalElement>(
|
||||||
|
hovered_word.id,
|
||||||
|
hovered_word.word,
|
||||||
|
None,
|
||||||
|
tooltip_style,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
|
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
|
||||||
.into_any();
|
.into_any();
|
||||||
|
@ -613,7 +619,7 @@ impl Element<TerminalView> for TerminalElement {
|
||||||
cursor_char,
|
cursor_char,
|
||||||
selection,
|
selection,
|
||||||
cursor,
|
cursor,
|
||||||
last_hovered_hyperlink,
|
last_hovered_word,
|
||||||
..
|
..
|
||||||
} = { &terminal_handle.read(cx).last_content };
|
} = { &terminal_handle.read(cx).last_content };
|
||||||
|
|
||||||
|
@ -634,9 +640,9 @@ impl Element<TerminalView> for TerminalElement {
|
||||||
&terminal_theme,
|
&terminal_theme,
|
||||||
cx.text_layout_cache(),
|
cx.text_layout_cache(),
|
||||||
cx.font_cache(),
|
cx.font_cache(),
|
||||||
last_hovered_hyperlink
|
last_hovered_word
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(_, range, _)| (link_style, range)),
|
.map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
|
||||||
);
|
);
|
||||||
|
|
||||||
//Layout cursor. Rectangle is used for IME, so we should lay it out even
|
//Layout cursor. Rectangle is used for IME, so we should lay it out even
|
||||||
|
|
|
@ -166,10 +166,11 @@ impl TerminalView {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Open(maybe_url_or_path) => {
|
Event::Open {
|
||||||
// TODO kb, what is the API for this?
|
is_url,
|
||||||
// terminal::URL_REGEX.matches(maybe_url_or_path)
|
maybe_url_or_path,
|
||||||
if maybe_url_or_path.starts_with("http") {
|
} => {
|
||||||
|
if *is_url {
|
||||||
cx.platform().open_url(maybe_url_or_path);
|
cx.platform().open_url(maybe_url_or_path);
|
||||||
} else if let Some(workspace) = workspace.upgrade(cx) {
|
} else if let Some(workspace) = workspace.upgrade(cx) {
|
||||||
let path_like =
|
let path_like =
|
||||||
|
@ -180,10 +181,11 @@ impl TerminalView {
|
||||||
let maybe_path = path_like.path_like;
|
let maybe_path = path_like.path_like;
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
if false { //&& workspace.contains_path() {
|
if false { //&& workspace.contains_path() {
|
||||||
//
|
// TODO kb
|
||||||
} else if maybe_path.exists() {
|
} else if maybe_path.exists() {
|
||||||
|
let visible = maybe_path.is_dir();
|
||||||
workspace
|
workspace
|
||||||
.open_abs_path(maybe_path, true, cx)
|
.open_abs_path(maybe_path, visible, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue