From 75d900704eb838ea2e07f90f012e4871049b398c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 12 Jul 2023 22:08:44 +0300 Subject: [PATCH 01/11] Refactor terminal highlights and open mechanisms Co-authored-by: Mikayla --- crates/terminal/src/terminal.rs | 14 ++++++++------ crates/terminal_view/src/terminal_element.rs | 4 ++++ crates/terminal_view/src/terminal_view.rs | 8 +++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 39e77b590b..739f52db01 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -75,11 +75,11 @@ const DEBUG_LINE_HEIGHT: f32 = 5.; // Regex Copied from alacritty's ui_config.rs lazy_static! { - 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(); } ///Upward flowing events, for changing the title and such -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Event { TitleChanged, BreadcrumbsChanged, @@ -88,6 +88,7 @@ pub enum Event { Wakeup, BlinkChanged, SelectionsChanged, + Open(String), } #[derive(Clone)] @@ -806,6 +807,7 @@ impl Terminal { term.scroll_to_point(*point); self.refresh_hyperlink(); } + // We need to change this to a word boundary check InternalEvent::FindHyperlink(position, open) => { let prev_hyperlink = self.last_content.last_hovered_hyperlink.take(); @@ -848,7 +850,7 @@ impl Terminal { let url_match = min_index..=max_index; Some((url, url_match)) - } else if let Some(url_match) = regex_match_at(term, point, &URL_REGEX) { + } else if let Some(url_match) = regex_match_at(term, point, &WORD_REGEX) { let url = term.bounds_to_string(*url_match.start(), *url_match.end()); Some((url, url_match)) @@ -858,7 +860,7 @@ impl Terminal { if let Some((url, url_match)) = found_url { if *open { - cx.platform().open_url(url.as_str()); + cx.emit(Event::Open(url)) } else { self.update_hyperlink(prev_hyperlink, url, url_match); } @@ -1089,7 +1091,7 @@ impl Terminal { self.pty_tx.notify(bytes); } } - } else { + } else if e.cmd { self.hyperlink_from_position(Some(position)); } } @@ -1208,7 +1210,7 @@ impl Terminal { let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { cx.platform().open_url(link.uri()); - } else { + } else if e.cmd { self.events .push_back(InternalEvent::FindHyperlink(position, true)); } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index b92059f5d6..72517b5a10 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -392,6 +392,10 @@ impl TerminalElement { let mut region = MouseRegion::new::(cx.view_id(), 0, visible_bounds); + // 1. Get file:linenumber syntax working ✔️ + // 2. Switch terminal to look for word boundaries, on cmd-hover + // 3. Send those query strings to the resolver thing above + // Terminal Emulator controlled behavior: region = region // Start selections diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3dd401e392..5f6fabcf1c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -158,7 +158,13 @@ impl TerminalView { .detach(); } } - _ => cx.emit(*event), + Event::Open(url) => { + // Get a workspace pointer from the new() function above + // Guess for project path or url + // Either run open buffer action OR platform open depending on whatever happens + cx.platform().open_url(url); + } + _ => cx.emit(event.clone()), }) .detach(); From f52722b6a4bf1a5db39bbffa73c858f1a1ccdc11 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 14 Jul 2023 18:26:35 +0300 Subject: [PATCH 02/11] Properly handle Cmd press for terminal highlights --- crates/terminal/src/terminal.rs | 24 ++++++++++++++++---- crates/terminal_view/src/terminal_element.rs | 4 ---- crates/terminal_view/src/terminal_view.rs | 17 +++++++++++++- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 739f52db01..fcbd02096c 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -51,7 +51,7 @@ use gpui::{ fonts, geometry::vector::{vec2f, Vector2F}, keymap_matcher::Keystroke, - platform::{MouseButton, MouseMovedEvent, TouchPhase}, + platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase}, scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp}, AppContext, ClipboardItem, Entity, ModelContext, Task, }; @@ -494,6 +494,7 @@ impl TerminalBuilder { last_mouse_position: None, next_link_id: 0, selection_phase: SelectionPhase::Ended, + cmd_pressed: false, }; Ok(TerminalBuilder { @@ -638,6 +639,7 @@ pub struct Terminal { scroll_px: f32, next_link_id: usize, selection_phase: SelectionPhase, + cmd_pressed: bool, } impl Terminal { @@ -807,7 +809,6 @@ impl Terminal { term.scroll_to_point(*point); self.refresh_hyperlink(); } - // We need to change this to a word boundary check InternalEvent::FindHyperlink(position, open) => { let prev_hyperlink = self.last_content.last_hovered_hyperlink.take(); @@ -966,6 +967,21 @@ impl Terminal { } } + pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { + let cmd = modifiers.cmd; + let changed = self.cmd_pressed != cmd; + if changed { + self.cmd_pressed = cmd; + if cmd { + self.refresh_hyperlink(); + } else { + self.last_content.last_hovered_hyperlink.take(); + } + } + + changed + } + ///Paste text into the terminal pub fn paste(&mut self, text: &str) { let paste_text = if self.last_content.mode.contains(TermMode::BRACKETED_PASTE) { @@ -1091,7 +1107,7 @@ impl Terminal { self.pty_tx.notify(bytes); } } - } else if e.cmd { + } else if self.cmd_pressed { self.hyperlink_from_position(Some(position)); } } @@ -1210,7 +1226,7 @@ impl Terminal { let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { cx.platform().open_url(link.uri()); - } else if e.cmd { + } else if self.cmd_pressed { self.events .push_back(InternalEvent::FindHyperlink(position, true)); } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 72517b5a10..b92059f5d6 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -392,10 +392,6 @@ impl TerminalElement { let mut region = MouseRegion::new::(cx.view_id(), 0, visible_bounds); - // 1. Get file:linenumber syntax working ✔️ - // 2. Switch terminal to look for word boundaries, on cmd-hover - // 3. Send those query strings to the resolver thing above - // Terminal Emulator controlled behavior: region = region // Start selections diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 5f6fabcf1c..4dbeb19033 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -11,7 +11,7 @@ use gpui::{ geometry::vector::Vector2F, impl_actions, keymap_matcher::{KeymapContext, Keystroke}, - platform::KeyDownEvent, + platform::{KeyDownEvent, ModifiersChangedEvent}, AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; @@ -159,6 +159,7 @@ impl TerminalView { } } Event::Open(url) => { + // TODO kb // Get a workspace pointer from the new() function above // Guess for project path or url // Either run open buffer action OR platform open depending on whatever happens @@ -399,6 +400,20 @@ impl View for TerminalView { cx.notify(); } + fn modifiers_changed( + &mut self, + event: &ModifiersChangedEvent, + cx: &mut ViewContext, + ) -> bool { + let handled = self + .terminal() + .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); + if handled { + cx.notify(); + } + handled + } + fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext) -> bool { self.clear_bel(cx); self.pause_cursor_blinking(cx); From 23f25562b535abcb49dc938fefe1ab33bb70cb01 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 14 Jul 2023 19:34:41 +0300 Subject: [PATCH 03/11] Map initial approach to string opening --- crates/terminal/src/terminal.rs | 5 ++- crates/terminal_view/src/terminal_panel.rs | 12 ++++-- crates/terminal_view/src/terminal_view.rs | 46 +++++++++++++++++----- crates/zed/src/main.rs | 9 ++++- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fcbd02096c..9d73f7d126 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -72,9 +72,10 @@ const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; -// Regex Copied from alacritty's ui_config.rs - lazy_static! { + // 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 WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap(); } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ad61903a9d..6ad321c735 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -261,10 +261,14 @@ impl TerminalPanel { .create_terminal(working_directory, window_id, cx) .log_err() }) { - let terminal = - Box::new(cx.add_view(|cx| { - TerminalView::new(terminal, workspace.database_id(), cx) - })); + let terminal = Box::new(cx.add_view(|cx| { + TerminalView::new( + terminal, + workspace.weak_handle(), + workspace.database_id(), + cx, + ) + })); pane.update(cx, |pane, cx| { let focus = pane.has_focus(); pane.add_item(terminal, true, focus, None, cx); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 4dbeb19033..7038eb284b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -32,7 +32,7 @@ use terminal::{ }, Event, Terminal, TerminalBlink, WorkingDirectory, }; -use util::ResultExt; +use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, notifications::NotifyResultExt, @@ -117,19 +117,27 @@ impl TerminalView { .notify_err(workspace, cx); if let Some(terminal) = terminal { - let view = cx.add_view(|cx| TerminalView::new(terminal, workspace.database_id(), cx)); + let view = cx.add_view(|cx| { + TerminalView::new( + terminal, + workspace.weak_handle(), + workspace.database_id(), + cx, + ) + }); workspace.add_item(Box::new(view), cx) } } pub fn new( terminal: ModelHandle, + workspace: WeakViewHandle, workspace_id: WorkspaceId, cx: &mut ViewContext, ) -> Self { let view_id = cx.view_id(); cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&terminal, |this, _, event, cx| match event { + cx.subscribe(&terminal, move |this, _, event, cx| match event { Event::Wakeup => { if !cx.is_self_focused() { this.has_new_content = true; @@ -158,12 +166,30 @@ impl TerminalView { .detach(); } } - Event::Open(url) => { - // TODO kb - // Get a workspace pointer from the new() function above - // Guess for project path or url - // Either run open buffer action OR platform open depending on whatever happens - cx.platform().open_url(url); + Event::Open(maybe_url_or_path) => { + // TODO kb, what is the API for this? + // terminal::URL_REGEX.matches(maybe_url_or_path) + if maybe_url_or_path.starts_with("http") { + cx.platform().open_url(maybe_url_or_path); + } else if let Some(workspace) = workspace.upgrade(cx) { + let path_like = + PathLikeWithPosition::parse_str(maybe_url_or_path.as_str(), |path_str| { + Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf()) + }) + .expect("infallible"); + let maybe_path = path_like.path_like; + workspace.update(cx, |workspace, cx| { + if false { //&& workspace.contains_path() { + // + } else if maybe_path.exists() { + workspace + .open_abs_path(maybe_path, true, cx) + .detach_and_log_err(cx); + } + }); + } + + // TODO kb let terminal know if we cannot open the string } _ => cx.emit(event.clone()), }) @@ -639,7 +665,7 @@ impl Item for TerminalView { project.create_terminal(cwd, window_id, cx) })?; Ok(pane.update(&mut cx, |_, cx| { - cx.add_view(|cx| TerminalView::new(terminal, workspace_id, cx)) + cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx)) })?) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ccf381b5b1..8f528771c9 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -895,7 +895,14 @@ pub fn dock_default_item_factory( }) .notify_err(workspace, cx)?; - let terminal_view = cx.add_view(|cx| TerminalView::new(terminal, workspace.database_id(), cx)); + let terminal_view = cx.add_view(|cx| { + TerminalView::new( + terminal, + workspace.weak_handle(), + workspace.database_id(), + cx, + ) + }); Some(Box::new(terminal_view)) } From 6123c67de94cec57f687af5746420a94a5bc947b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 15 Jul 2023 01:11:20 +0300 Subject: [PATCH 04/11] Detect and open URLs properly --- crates/terminal/src/terminal.rs | 92 +++++++++++++------- crates/terminal_view/src/terminal_element.rs | 18 ++-- crates/terminal_view/src/terminal_view.rs | 14 +-- 3 files changed, 79 insertions(+), 45 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9d73f7d126..fae79eda1d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -74,7 +74,7 @@ const DEBUG_LINE_HEIGHT: f32 = 5.; lazy_static! { // 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(); } @@ -89,7 +89,10 @@ pub enum Event { Wakeup, BlinkChanged, SelectionsChanged, - Open(String), + Open { + is_url: bool, + maybe_url_or_path: String, + }, } #[derive(Clone)] @@ -592,7 +595,14 @@ pub struct TerminalContent { pub cursor: RenderableCursor, pub cursor_char: char, pub size: TerminalSize, - pub last_hovered_hyperlink: Option<(String, RangeInclusive, usize)>, + pub last_hovered_word: Option, +} + +#[derive(Clone)] +pub struct HoveredWord { + pub word: String, + pub word_match: RangeInclusive, + pub id: usize, } impl Default for TerminalContent { @@ -609,7 +619,7 @@ impl Default for TerminalContent { }, cursor_char: Default::default(), size: Default::default(), - last_hovered_hyperlink: None, + last_hovered_word: None, } } } @@ -626,7 +636,7 @@ pub struct Terminal { events: VecDeque, /// This is only used for mouse mode cell change detection 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, pub matches: Vec>, pub last_content: TerminalContent, @@ -773,7 +783,7 @@ impl Terminal { } InternalEvent::Scroll(scroll) => { term.scroll_display(*scroll); - self.refresh_hyperlink(); + self.refresh_hovered_word(); } InternalEvent::SetSelection(selection) => { term.selection = selection.as_ref().map(|(sel, _)| sel.clone()); @@ -808,10 +818,10 @@ impl Terminal { } InternalEvent::ScrollToPoint(point) => { term.scroll_to_point(*point); - self.refresh_hyperlink(); + self.refresh_hovered_word(); } 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( *position, @@ -851,41 +861,57 @@ impl Terminal { let url = link.unwrap().uri().to_owned(); let url_match = min_index..=max_index; - Some((url, url_match)) - } else if let Some(url_match) = regex_match_at(term, point, &WORD_REGEX) { - let url = term.bounds_to_string(*url_match.start(), *url_match.end()); + Some((url, true, url_match)) + } else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) { + 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 { None }; - if let Some((url, url_match)) = found_url { + if let Some((maybe_url_or_path, is_url, url_match)) = found_url { if *open { - cx.emit(Event::Open(url)) + cx.emit(Event::Open { + is_url, + maybe_url_or_path, + }) } 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, - prev_hyperlink: Option<(String, RangeInclusive, usize)>, - url: String, - url_match: RangeInclusive, + prev_word: Option, + word: String, + word_match: RangeInclusive, ) { - 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)); + if let Some(prev_word) = prev_word { + if prev_word.word == word && prev_word.word_match == word_match { + self.last_content.last_hovered_word = Some(HoveredWord { + word, + word_match, + id: prev_word.id, + }); } 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(), + }); } } 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 { self.cmd_pressed = cmd; if cmd { - self.refresh_hyperlink(); + self.refresh_hovered_word(); } 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_char: term.grid()[content.cursor.point].c, 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 { - self.hyperlink_from_position(Some(position)); + self.word_from_position(Some(position)); } } - fn hyperlink_from_position(&mut self, position: Option) { + fn word_from_position(&mut self, position: Option) { 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 { self.events .push_back(InternalEvent::FindHyperlink(position, false)); @@ -1274,8 +1300,8 @@ impl Terminal { } } - pub fn refresh_hyperlink(&mut self) { - self.hyperlink_from_position(self.last_mouse_position); + pub fn refresh_hovered_word(&mut self) { + self.word_from_position(self.last_mouse_position); } fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index b92059f5d6..aabfb99922 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -583,17 +583,23 @@ impl Element for TerminalElement { let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| { terminal.set_size(dimensions); 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( Empty::new() .contained() .constrained() .with_width(dimensions.width()) .with_height(dimensions.height()) - .with_tooltip::(id, uri, None, tooltip_style, cx), + .with_tooltip::( + hovered_word.id, + hovered_word.word, + None, + tooltip_style, + cx, + ), ) .with_position_mode(gpui::elements::OverlayPositionMode::Local) .into_any(); @@ -613,7 +619,7 @@ impl Element for TerminalElement { cursor_char, selection, cursor, - last_hovered_hyperlink, + last_hovered_word, .. } = { &terminal_handle.read(cx).last_content }; @@ -634,9 +640,9 @@ impl Element for TerminalElement { &terminal_theme, cx.text_layout_cache(), cx.font_cache(), - last_hovered_hyperlink + last_hovered_word .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 diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 7038eb284b..476ff49a57 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -166,10 +166,11 @@ impl TerminalView { .detach(); } } - Event::Open(maybe_url_or_path) => { - // TODO kb, what is the API for this? - // terminal::URL_REGEX.matches(maybe_url_or_path) - if maybe_url_or_path.starts_with("http") { + Event::Open { + is_url, + maybe_url_or_path, + } => { + if *is_url { cx.platform().open_url(maybe_url_or_path); } else if let Some(workspace) = workspace.upgrade(cx) { let path_like = @@ -180,10 +181,11 @@ impl TerminalView { let maybe_path = path_like.path_like; workspace.update(cx, |workspace, cx| { if false { //&& workspace.contains_path() { - // + // TODO kb } else if maybe_path.exists() { + let visible = maybe_path.is_dir(); workspace - .open_abs_path(maybe_path, true, cx) + .open_abs_path(maybe_path, visible, cx) .detach_and_log_err(cx); } }); From 6349d90cac88808809184ca012d8f5ecf4a68953 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 00:37:03 +0300 Subject: [PATCH 05/11] Properly open project directories --- crates/project/src/project.rs | 4 +++- crates/terminal_view/src/terminal_view.rs | 24 ++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5bb8af3f38..b3255df812 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1915,7 +1915,9 @@ impl Project { return; } - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + let abs_path = file.abs_path(cx); + let uri = lsp::Url::from_file_path(&abs_path) + .unwrap_or_else(|()| panic!("Failed to register file {abs_path:?}")); let initial_snapshot = buffer.text_snapshot(); let language = buffer.language().cloned(); let worktree_id = file.worktree_id(cx); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 476ff49a57..49f334b2d9 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -180,18 +180,28 @@ impl TerminalView { .expect("infallible"); let maybe_path = path_like.path_like; workspace.update(cx, |workspace, cx| { - if false { //&& workspace.contains_path() { - // TODO kb - } else if maybe_path.exists() { - let visible = maybe_path.is_dir(); + let potential_abs_paths = if maybe_path.is_absolute() { + vec![maybe_path] + } else { workspace - .open_abs_path(maybe_path, visible, cx) - .detach_and_log_err(cx); + .worktrees(cx) + .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path)) + .collect() + }; + + for path in potential_abs_paths { + if path.exists() { + let visible = path.is_dir(); + workspace + .open_abs_path(path, visible, cx) + .detach_and_log_err(cx); + break; + } } }); } - // TODO kb let terminal know if we cannot open the string + // TODO kb let terminal know if we cannot open the string + remove the error message when folder open returns None } _ => cx.emit(event.clone()), }) From 82a9d53c8aca67ca511baf0c94300608ab6a0ec8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 01:13:27 +0300 Subject: [PATCH 06/11] Only highlight the openable things --- crates/terminal/src/terminal.rs | 50 ++++++++++--- crates/terminal_view/src/terminal_view.rs | 85 ++++++++++++++--------- 2 files changed, 94 insertions(+), 41 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fae79eda1d..17bfa1550e 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -33,6 +33,7 @@ use mappings::mouse::{ use procinfo::LocalProcessInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use smol::channel::Sender; use util::truncate_and_trailoff; use std::{ @@ -89,10 +90,12 @@ pub enum Event { Wakeup, BlinkChanged, SelectionsChanged, - Open { - is_url: bool, - maybe_url_or_path: String, + OpenUrl(String), + ProbePathOpen { + maybe_path: String, + can_open_tx: Sender, }, + OpenPath(String), } #[derive(Clone)] @@ -874,12 +877,43 @@ impl Terminal { if let Some((maybe_url_or_path, is_url, url_match)) = found_url { if *open { - cx.emit(Event::Open { - is_url, - maybe_url_or_path, - }) + let event = if is_url { + Event::OpenUrl(maybe_url_or_path) + } else { + Event::OpenPath(maybe_url_or_path) + }; + cx.emit(event); } else { - self.update_selected_word(prev_hovered_word, maybe_url_or_path, url_match); + if is_url { + self.update_selected_word( + prev_hovered_word, + maybe_url_or_path, + url_match, + ); + } else { + let (can_open_tx, can_open_rx) = smol::channel::bounded(1); + cx.emit(Event::ProbePathOpen { + maybe_path: maybe_url_or_path.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.update_selected_word( + prev_hovered_word, + maybe_url_or_path, + url_match, + ); + } else { + terminal.last_content.last_hovered_word.take(); + } + cx.notify(); + }); + }) + .detach(); + }; } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 49f334b2d9..f0371fcb3e 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -166,42 +166,27 @@ impl TerminalView { .detach(); } } - Event::Open { - is_url, - maybe_url_or_path, + Event::ProbePathOpen { + maybe_path, + can_open_tx, } => { - if *is_url { - cx.platform().open_url(maybe_url_or_path); - } else if let Some(workspace) = workspace.upgrade(cx) { - let path_like = - PathLikeWithPosition::parse_str(maybe_url_or_path.as_str(), |path_str| { - Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf()) - }) - .expect("infallible"); - let maybe_path = path_like.path_like; - workspace.update(cx, |workspace, cx| { - let potential_abs_paths = if maybe_path.is_absolute() { - vec![maybe_path] - } else { + let can_open = !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() { + // TODO kb change selections using path_like row & column + let visible = path.path_like.is_dir(); + if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { workspace - .worktrees(cx) - .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path)) - .collect() - }; - - for path in potential_abs_paths { - if path.exists() { - let visible = path.is_dir(); - workspace - .open_abs_path(path, visible, cx) - .detach_and_log_err(cx); - break; - } - } - }); + .open_abs_path(path.path_like, visible, cx) + .detach_and_log_err(cx); + }); + } } - - // TODO kb let terminal know if we cannot open the string + remove the error message when folder open returns None } _ => cx.emit(event.clone()), }) @@ -389,6 +374,40 @@ impl TerminalView { } } +fn possible_open_targets( + workspace: &WeakViewHandle, + maybe_path: &String, + cx: &mut ViewContext<'_, '_, TerminalView>, +) -> Vec> { + let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |path_str| { + Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf()) + }) + .expect("infallible"); + let maybe_path = path_like.path_like; + let potential_abs_paths = if maybe_path.is_absolute() { + vec![maybe_path] + } else if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + workspace + .worktrees(cx) + .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path)) + .collect() + }) + } else { + Vec::new() + }; + + potential_abs_paths + .into_iter() + .filter(|path| path.exists()) + .map(|path| PathLikeWithPosition { + path_like: path, + row: path_like.row, + column: path_like.column, + }) + .collect() +} + pub fn regex_search_for_query(query: project::search::SearchQuery) -> Option { let searcher = match query { project::search::SearchQuery::Text { query, .. } => RegexSearch::new(&query), From 94358ffb16491c204b14fc6976709d6f2ec189ad Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 01:31:14 +0300 Subject: [PATCH 07/11] Use lines and columns from the file url strings --- crates/terminal_view/src/terminal_view.rs | 46 +++++++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index f0371fcb3e..7c79a76cff 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -3,8 +3,10 @@ pub mod terminal_element; pub mod terminal_panel; use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement}; +use anyhow::Context; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; +use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ actions, elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack}, @@ -15,6 +17,7 @@ use gpui::{ AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; +use language::Bias; use project::{LocalWorktree, Project}; use serde::Deserialize; use smallvec::{smallvec, SmallVec}; @@ -177,15 +180,42 @@ impl TerminalView { 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() { - // TODO kb change selections using path_like row & column let visible = path.path_like.is_dir(); - if let Some(workspace) = workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - workspace - .open_abs_path(path.path_like, visible, cx) - .detach_and_log_err(cx); - }); - } + 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::() { + 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()), From 6f7a6e57fcefa7e7c8f78a27dd6e604d73a76e9c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 02:03:27 +0300 Subject: [PATCH 08/11] Avoid excessive blinking on cmd-hover --- crates/terminal/src/terminal.rs | 62 +++++++++++++++++---------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 17bfa1550e..256cbe652d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -884,36 +884,13 @@ impl Terminal { }; cx.emit(event); } else { - if is_url { - self.update_selected_word( - prev_hovered_word, - maybe_url_or_path, - url_match, - ); - } else { - let (can_open_tx, can_open_rx) = smol::channel::bounded(1); - cx.emit(Event::ProbePathOpen { - maybe_path: maybe_url_or_path.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.update_selected_word( - prev_hovered_word, - maybe_url_or_path, - url_match, - ); - } else { - terminal.last_content.last_hovered_word.take(); - } - cx.notify(); - }); - }) - .detach(); - }; + self.update_selected_word( + prev_hovered_word, + maybe_url_or_path, + url_match, + !is_url, + cx, + ); } } } @@ -925,6 +902,8 @@ impl Terminal { prev_word: Option, word: String, word_match: RangeInclusive, + should_probe_word: bool, + cx: &mut ModelContext, ) { if let Some(prev_word) = prev_word { if prev_word.word == word && prev_word.word_match == word_match { @@ -933,6 +912,29 @@ impl Terminal { word_match, id: prev_word.id, }); + } else if should_probe_word { + 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, From 10db05f87f1a00a616bd8fcfcbc37ca427d46d6b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 13:28:44 +0300 Subject: [PATCH 09/11] Rework terminal highlight event flow --- crates/terminal/src/terminal.rs | 109 ++++++++----------- crates/terminal_view/src/terminal_element.rs | 14 ++- crates/terminal_view/src/terminal_view.rs | 105 ++++++++++-------- 3 files changed, 112 insertions(+), 116 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 256cbe652d..438224c81e 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -33,7 +33,6 @@ use mappings::mouse::{ use procinfo::LocalProcessInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use smol::channel::Sender; use util::truncate_and_trailoff; use std::{ @@ -90,12 +89,18 @@ pub enum Event { Wakeup, BlinkChanged, SelectionsChanged, - OpenUrl(String), - ProbePathOpen { - maybe_path: String, - can_open_tx: Sender, - }, - OpenPath(String), + NewNavigationTarget(MaybeNavigationTarget), + Open(MaybeNavigationTarget), +} + +/// A string inside terminal, potentially useful as a URI that can be opened. +#[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)] @@ -502,6 +507,7 @@ impl TerminalBuilder { next_link_id: 0, selection_phase: SelectionPhase::Ended, cmd_pressed: false, + found_word: false, }; Ok(TerminalBuilder { @@ -654,6 +660,7 @@ pub struct Terminal { next_link_id: usize, selection_phase: SelectionPhase, cmd_pressed: bool, + found_word: bool, } impl Terminal { @@ -834,7 +841,7 @@ impl Terminal { .grid_clamp(term, alacritty_terminal::index::Boundary::Cursor); 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; loop { let new_min_index = @@ -875,20 +882,21 @@ impl Terminal { 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 { - let event = if is_url { - Event::OpenUrl(maybe_url_or_path) + let target = if is_url { + MaybeNavigationTarget::Url(maybe_url_or_path) } else { - Event::OpenPath(maybe_url_or_path) + MaybeNavigationTarget::PathLike(maybe_url_or_path) }; - cx.emit(event); + cx.emit(Event::Open(target)); } else { self.update_selected_word( prev_hovered_word, - maybe_url_or_path, url_match, - !is_url, + maybe_url_or_path, + is_url, cx, ); } @@ -900,9 +908,9 @@ impl Terminal { fn update_selected_word( &mut self, prev_word: Option, - word: String, word_match: RangeInclusive, - should_probe_word: bool, + word: String, + is_url: bool, cx: &mut ModelContext, ) { if let Some(prev_word) = prev_word { @@ -912,43 +920,21 @@ impl Terminal { word_match, id: prev_word.id, }); - } else if should_probe_word { - 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(), - }); + return; } - } 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 { @@ -1031,17 +1017,8 @@ impl Terminal { } pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { - let cmd = modifiers.cmd; - let changed = self.cmd_pressed != cmd; - if changed { - self.cmd_pressed = cmd; - if cmd { - self.refresh_hovered_word(); - } else { - self.last_content.last_hovered_word.take(); - } - } - + let changed = self.cmd_pressed != modifiers.cmd; + self.cmd_pressed = modifiers.cmd; 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); } @@ -1415,6 +1392,10 @@ impl Terminal { }) .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 { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index aabfb99922..e29beb3ad5 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -163,6 +163,7 @@ pub struct TerminalElement { terminal: WeakModelHandle, focused: bool, cursor_visible: bool, + can_navigate_to_selected_word: bool, } impl TerminalElement { @@ -170,11 +171,13 @@ impl TerminalElement { terminal: WeakModelHandle, focused: bool, cursor_visible: bool, + can_navigate_to_selected_word: bool, ) -> TerminalElement { TerminalElement { terminal, focused, cursor_visible, + can_navigate_to_selected_word, } } @@ -580,13 +583,17 @@ impl Element for TerminalElement { let background_color = terminal_theme.background; 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.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( Empty::new() .contained() @@ -619,7 +626,6 @@ impl Element for TerminalElement { cursor_char, selection, cursor, - last_hovered_word, .. } = { &terminal_handle.read(cx).last_content }; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 7c79a76cff..3f4101d16c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -33,7 +33,7 @@ use terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, - Event, Terminal, TerminalBlink, WorkingDirectory, + Event, MaybeNavigationTarget, Terminal, TerminalBlink, WorkingDirectory, }; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ @@ -93,6 +93,7 @@ pub struct TerminalView { blinking_on: bool, blinking_paused: bool, blink_epoch: usize, + can_navigate_to_selected_word: bool, workspace_id: WorkspaceId, } @@ -169,55 +170,61 @@ impl TerminalView { .detach(); } } - Event::ProbePathOpen { - maybe_path, - can_open_tx, - } => { - let can_open = !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::() { - 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::NewNavigationTarget(maybe_navigation_target) => { + this.can_navigate_to_selected_word = match maybe_navigation_target { + MaybeNavigationTarget::Url(_) => true, + MaybeNavigationTarget::PathLike(maybe_path) => { + !possible_open_targets(&workspace, maybe_path, cx).is_empty() + } } } + 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::() { + 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()), }) .detach(); @@ -231,6 +238,7 @@ impl TerminalView { blinking_on: false, blinking_paused: false, blink_epoch: 0, + can_navigate_to_selected_word: false, workspace_id, } } @@ -466,6 +474,7 @@ impl View for TerminalView { terminal_handle, focused, self.should_show_cursor(focused, cx), + self.can_navigate_to_selected_word, ) .contained(), ) From 6ed7820f7ca2d73d2a2c2e1baf84fa5c7325cb25 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 16:08:17 +0300 Subject: [PATCH 10/11] Consider all terminal when searching for words --- crates/terminal/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 438224c81e..450ca35b44 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -838,7 +838,7 @@ impl Terminal { self.last_content.size, term.grid().display_offset(), ) - .grid_clamp(term, alacritty_terminal::index::Boundary::Cursor); + .grid_clamp(term, alacritty_terminal::index::Boundary::Grid); let link = term.grid().index(point).hyperlink(); let found_word = if link.is_some() { From 33921183dc31170585b09d598a9ca6799724bbc2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Jul 2023 17:05:52 +0300 Subject: [PATCH 11/11] Avoid extra blinking on mouse moves --- crates/terminal/src/terminal.rs | 53 ++++++++++++++--------- crates/terminal_view/src/terminal_view.rs | 5 ++- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 450ca35b44..3a64cff24f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -89,7 +89,7 @@ pub enum Event { Wakeup, BlinkChanged, SelectionsChanged, - NewNavigationTarget(MaybeNavigationTarget), + NewNavigationTarget(Option), Open(MaybeNavigationTarget), } @@ -507,7 +507,7 @@ impl TerminalBuilder { next_link_id: 0, selection_phase: SelectionPhase::Ended, cmd_pressed: false, - found_word: false, + hovered_word: false, }; Ok(TerminalBuilder { @@ -660,7 +660,7 @@ pub struct Terminal { next_link_id: usize, selection_phase: SelectionPhase, cmd_pressed: bool, - found_word: bool, + hovered_word: bool, } impl Terminal { @@ -882,23 +882,31 @@ impl Terminal { None }; - self.found_word = found_word.is_some(); - if let Some((maybe_url_or_path, is_url, url_match)) = found_word { - if *open { - let target = if is_url { - MaybeNavigationTarget::Url(maybe_url_or_path) + match found_word { + Some((maybe_url_or_path, is_url, url_match)) => { + if *open { + let target = if is_url { + MaybeNavigationTarget::Url(maybe_url_or_path) + } else { + MaybeNavigationTarget::PathLike(maybe_url_or_path) + }; + cx.emit(Event::Open(target)); } else { - MaybeNavigationTarget::PathLike(maybe_url_or_path) - }; - cx.emit(Event::Open(target)); - } else { - self.update_selected_word( - prev_hovered_word, - url_match, - maybe_url_or_path, - is_url, - cx, - ); + self.update_selected_word( + prev_hovered_word, + url_match, + maybe_url_or_path, + is_url, + cx, + ); + } + self.hovered_word = true; + } + None => { + if self.hovered_word { + cx.emit(Event::NewNavigationTarget(None)); + } + self.hovered_word = false; } } } @@ -934,7 +942,7 @@ impl Terminal { } else { MaybeNavigationTarget::PathLike(word) }; - cx.emit(Event::NewNavigationTarget(navigation_target)); + cx.emit(Event::NewNavigationTarget(Some(navigation_target))); } fn next_link_id(&mut self) -> usize { @@ -1018,6 +1026,9 @@ impl Terminal { pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { let changed = self.cmd_pressed != modifiers.cmd; + if !self.cmd_pressed && modifiers.cmd { + self.refresh_hovered_word(); + } self.cmd_pressed = modifiers.cmd; changed } @@ -1394,7 +1405,7 @@ impl Terminal { } pub fn can_navigate_to_selected_word(&self) -> bool { - self.cmd_pressed && self.found_word + self.cmd_pressed && self.hovered_word } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3f4101d16c..cdb1d40efc 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -172,10 +172,11 @@ impl TerminalView { } Event::NewNavigationTarget(maybe_navigation_target) => { this.can_navigate_to_selected_word = match maybe_navigation_target { - MaybeNavigationTarget::Url(_) => true, - MaybeNavigationTarget::PathLike(maybe_path) => { + Some(MaybeNavigationTarget::Url(_)) => true, + Some(MaybeNavigationTarget::PathLike(maybe_path)) => { !possible_open_targets(&workspace, maybe_path, cx).is_empty() } + None => false, } } Event::Open(maybe_navigation_target) => match maybe_navigation_target {