From 0c4e2ef4195fe57b4b847490d3ac579037c9a0a7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 14:08:18 -0800 Subject: [PATCH 01/16] Fix terminal selection when cursor leaves terminal bounds --- crates/terminal_view/src/terminal_element.rs | 53 +++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 328a6a1c4e8cd37dcc698ada592a60e7c9767628..ffdca7d8135d2702a3cbc9b4d42070e8ec4e41a4 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -2,10 +2,11 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, - FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, - Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, - Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, StyleRefinement, - Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext, + FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveBounds, InteractiveElement, + InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, + ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, + ShapedLine, StatefulInteractiveElement, StyleRefinement, Styled, TextRun, TextStyle, + TextSystem, UnderlineStyle, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -598,33 +599,48 @@ impl TerminalElement { ) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; self.interactivity.on_mouse_down(MouseButton::Left, { let terminal = terminal.clone(); let focus = focus.clone(); move |e, cx| { cx.focus(&focus); - //todo!(context menu) - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); terminal.update(cx, |terminal, cx| { terminal.mouse_down(&e, origin); - cx.notify(); }) } }); - self.interactivity.on_mouse_move({ - let terminal = terminal.clone(); - let focus = focus.clone(); - move |e, cx| { - if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { + + cx.on_mouse_event({ + let bounds = bounds.clone(); + let focus = self.focus.clone(); + let terminal = self.terminal.clone(); + move |e: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble || !focus.is_focused(cx) { + return; + } + + if e.pressed_button.is_some() && !cx.has_active_drag() { terminal.update(cx, |terminal, cx| { terminal.mouse_drag(e, origin, bounds); cx.notify(); }) } + + if interactive_bounds.visibly_contains(&e.position, cx) { + terminal.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); + cx.notify(); + }) + } } }); + self.interactivity.on_mouse_up( MouseButton::Left, TerminalElement::generic_button_handler( @@ -651,19 +667,6 @@ impl TerminalElement { } } }); - - self.interactivity.on_mouse_move({ - let terminal = terminal.clone(); - let focus = focus.clone(); - move |e, cx| { - if focus.is_focused(cx) { - terminal.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) - } - } - }); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { From 32cd4d778a43c1b697da4a9f6d796a606a6ae169 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 17:10:08 -0500 Subject: [PATCH 02/16] Render an empty placeholder when not showing file icons in the project panel (#3897) This PR makes it so when we're not showing file icons in the project panel we render an empty placeholder instead of nothing. This prevents the indentation of the items in the file tree from changing based on the presence of the icon. Release Notes: - Fixed layout shift when `project_panel.file_icons` is set to `false`. --- crates/project_panel/src/project_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e0bee14df27479021634d3400b13850d44fa51a9..6662014c465909d782c91f45e0cb81b9c7e7053d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1395,7 +1395,7 @@ impl ProjectPanel { .child(if let Some(icon) = &icon { div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) } else { - div() + div().size(IconSize::default().rems()).invisible() }) .child( if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { From 47476faef1e0121f66791f8e0aad906e1f263273 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 17:25:11 -0500 Subject: [PATCH 03/16] Fix label color for inactive tabs (#3899) This PR fixes an issue where certain tabs were not using the correct color for their labels when they were inactive. Release Notes: - Fixed an issue where some inactive tabs were not using the correct label color. --- crates/diagnostics/src/diagnostics.rs | 9 +++++++-- crates/language_tools/src/lsp_log.rs | 12 +++++++++--- crates/language_tools/src/syntax_tree_view.rs | 10 ++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 77e6a7673ff838f3f4a5ac3029b9d5bddacbb9ee..9d5a62cff19eea509aaa05c716a9188bd90a43c9 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -641,8 +641,13 @@ impl Item for ProjectDiagnosticsEditor { fn tab_content(&self, _detail: Option, selected: bool, _: &WindowContext) -> AnyElement { if self.summary.error_count == 0 && self.summary.warning_count == 0 { - let label = Label::new("No problems"); - label.into_any_element() + Label::new("No problems") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } else { h_stack() .gap_1() diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index e38de7d3734a9e95cb301c4139dc6b998f033bbd..123149eae2e184df4e1b2871ea8c25125eda711f 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -10,7 +10,7 @@ use language::{LanguageServerId, LanguageServerName}; use lsp::IoKind; use project::{search::SearchQuery, Project}; use std::{borrow::Cow, sync::Arc}; -use ui::{h_stack, popover_menu, Button, Checkbox, Clickable, ContextMenu, Label, Selection}; +use ui::{popover_menu, prelude::*, Button, Checkbox, ContextMenu, Label, Selection}; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchEvent, SearchableItem, SearchableItemHandle}, @@ -614,8 +614,14 @@ impl Item for LspLogView { Editor::to_item_events(event, f) } - fn tab_content(&self, _: Option, _: bool, _: &WindowContext<'_>) -> AnyElement { - Label::new("LSP Logs").into_any_element() + fn tab_content(&self, _: Option, selected: bool, _: &WindowContext<'_>) -> AnyElement { + Label::new("LSP Logs") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } fn as_searchable(&self, handle: &View) -> Option> { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index a36264261e8840a20ccb89c4ca7ca28fcd1b3851..c30564e9bfe3f8c0e0fd1f5c2f6827a4f070fd81 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -405,8 +405,14 @@ impl Item for SyntaxTreeView { fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {} - fn tab_content(&self, _: Option, _: bool, _: &WindowContext<'_>) -> AnyElement { - Label::new("Syntax Tree").into_any_element() + fn tab_content(&self, _: Option, selected: bool, _: &WindowContext<'_>) -> AnyElement { + Label::new("Syntax Tree") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } fn clone_on_split( From a8efb41e56dc7ae412e948aa3599cbe4bc9f97f2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jan 2024 17:28:07 -0500 Subject: [PATCH 04/16] Remove outdated TODOs --- crates/zed/src/app_menus.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index a4b0e21d6e20ee93b7d995b7f04ad58df279b316..2aff05d884265aac2538973bf9d7b65ed3d54385 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -150,14 +150,6 @@ pub fn app_menus() -> Vec> { MenuItem::action("View Dependency Licenses", crate::OpenLicenses), MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::separator(), - // todo!(): Needs `feedback` crate. - // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), - // MenuItem::action( - // "Copy System Specs Into Clipboard", - // feedback::CopySystemSpecsIntoClipboard, - // ), - // MenuItem::action("File Bug Report", feedback::FileBugReport), - // MenuItem::action("Request Feature", feedback::RequestFeature), MenuItem::separator(), MenuItem::action( "Documentation", From 269b6656189be1262fcbe0413c524914e395cc39 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jan 2024 17:32:00 -0500 Subject: [PATCH 05/16] Remove outdated TODO --- crates/terminal_view/src/terminal_panel.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 9e193e83b7d54cad1d7ca0d6a7cbc5ab857a95a8..526cdf391ae331aad67088cd2462cb1b704e3889 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -410,11 +410,6 @@ impl Panel for TerminalPanel { "TerminalPanel" } - // todo!() - // fn icon_tooltip(&self) -> (String, Option>) { - // ("Terminal Panel".into(), Some(Box::new(ToggleFocus))) - // } - fn icon(&self, _cx: &WindowContext) -> Option { Some(Icon::Terminal) } From 61db60b3e21a9a562d22afc222bdb152b9767286 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 14:55:45 -0800 Subject: [PATCH 06/16] Fix incorrect placement of terminal selection when dragging Co-authored-by: Mikayla --- crates/terminal/src/mappings/mouse.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index a32d83d28ddb99416cff22a85322184acabb2ac9..19c699216a77d47ce173cff06d7258f6fafa1649 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -162,22 +162,20 @@ pub fn mouse_side( pos: Point, cur_size: TerminalSize, ) -> alacritty_terminal::index::Direction { - let cell_width = cur_size.cell_width.floor(); + let cell_width = cur_size.cell_width; if cell_width == px(0.) { return Side::Right; } - let x = pos.x.floor(); - - let cell_x = cmp::max(px(0.), x - cell_width) % cell_width; - let half_cell_width = (cur_size.cell_width / 2.0).floor(); + let cell_x = cmp::max(px(0.), pos.x) % cell_width; + let half_cell_width = cur_size.cell_width / 2.0; let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; //Width: Pixels or columns? if cell_x > half_cell_width // Edge case when mouse leaves the window. - || x >= end_of_grid + || pos.x >= end_of_grid { Side::Right } else { From ba13540c77bb8a1e755b304150ea9d5d590b737d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 15:05:39 -0800 Subject: [PATCH 07/16] Fix inconsistent selection start when dragging outside of terminal bounds Co-authored-by: Mikayla --- crates/terminal/src/mappings/mouse.rs | 50 +++++++++++++++------------ crates/terminal/src/terminal.rs | 15 +++----- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 19c699216a77d47ce173cff06d7258f6fafa1649..af3e6b640fd64fe6dc3e2e2364a08107bf3634e4 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -158,37 +158,41 @@ pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) } } -pub fn mouse_side( +pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { + grid_point_and_side(pos, cur_size, display_offset).0 +} + +pub fn grid_point_and_side( pos: Point, cur_size: TerminalSize, -) -> alacritty_terminal::index::Direction { - let cell_width = cur_size.cell_width; - if cell_width == px(0.) { - return Side::Right; - } - - let cell_x = cmp::max(px(0.), pos.x) % cell_width; + display_offset: usize, +) -> (AlacPoint, Side) { + let mut col = GridCol((pos.x / cur_size.cell_width) as usize); + let cell_x = cmp::max(px(0.), pos.x) % cur_size.cell_width; let half_cell_width = cur_size.cell_width / 2.0; - let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; - let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; - - //Width: Pixels or columns? - if cell_x > half_cell_width - // Edge case when mouse leaves the window. - || pos.x >= end_of_grid - { + let mut side = if cell_x > half_cell_width { Side::Right } else { Side::Left - } -} + }; -pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { - let col = GridCol((pos.x / cur_size.cell_width) as usize); + if col > cur_size.last_column() { + col = cur_size.last_column(); + side = Side::Right; + } let col = min(col, cur_size.last_column()); - let line = (pos.y / cur_size.line_height) as i32; - let line = min(line, cur_size.bottommost_line().0); - AlacPoint::new(GridLine(line - display_offset as i32), col) + let mut line = (pos.y / cur_size.line_height) as i32; + if line > cur_size.bottommost_line() { + line = cur_size.bottommost_line().0 as i32; + side = Side::Right; + } else if line < 0 { + side = Side::Left; + } + + ( + AlacPoint::new(GridLine(line - display_offset as i32), col), + side, + ) } ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index b15bd7c6d659cc7794b770934e95775c0ae41507..70fceae66a5b8be562513843e03a1675ff66f3be 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -28,7 +28,8 @@ use futures::{ }; use mappings::mouse::{ - alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report, + alt_scroll, grid_point, grid_point_and_side, mouse_button_report, mouse_moved_report, + scroll_report, }; use procinfo::LocalProcessInfo; @@ -704,14 +705,12 @@ impl Terminal { } InternalEvent::UpdateSelection(position) => { if let Some(mut selection) = term.selection.take() { - let point = grid_point( + let (point, side) = grid_point_and_side( *position, self.last_content.size, term.grid().display_offset(), ); - let side = mouse_side(*position, self.last_content.size); - selection.update(point, side); term.selection = Some(selection); @@ -1088,12 +1087,11 @@ impl Terminal { let position = e.position - origin; self.last_mouse_position = Some(position); if self.mouse_mode(e.modifiers.shift) { - let point = grid_point( + let (point, side) = grid_point_and_side( position, self.last_content.size, self.last_content.display_offset, ); - let side = mouse_side(position, self.last_content.size); if self.mouse_changed(point, side) { if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) { @@ -1175,15 +1173,12 @@ impl Terminal { } } else if e.button == MouseButton::Left { let position = e.position - origin; - let point = grid_point( + let (point, side) = grid_point_and_side( position, self.last_content.size, self.last_content.display_offset, ); - // Use .opposite so that selection is inclusive of the cell clicked. - let side = mouse_side(position, self.last_content.size); - let selection_type = match e.click_count { 0 => return, //This is a release 1 => Some(SelectionType::Simple), From 3f06a050608f63f2c93aca1d90c96e7385d3b27e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 18:08:28 -0500 Subject: [PATCH 08/16] Iterate on collab panel filter input style (#3900) This PR takes another pass at the collab panel filter input to improve its styling. Release Notes: - Improved the look of the filter input in the collab panel. --- crates/collab_ui/src/collab_panel.rs | 60 ++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 3ed07e5bd1b8b3985f58d8ca89dd04e676f5425a..f4856b22017cc2bcd5cfc76cfb1dedb291fe2fe2 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -11,15 +11,16 @@ use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; -use editor::Editor; +use editor::{Editor, EditorElement, EditorStyle}; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, - FocusHandle, FocusableView, InteractiveElement, IntoElement, ListOffset, ListState, Model, - MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, SharedString, - Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, ListOffset, + ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, + RenderOnce, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, + VisualContext, WeakView, WhiteSpace, }; use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -29,10 +30,9 @@ use settings::{Settings, SettingsStore}; use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; -use ui::prelude::*; use ui::{ - h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, - Label, ListHeader, ListItem, Tooltip, + prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label, + ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1749,15 +1749,49 @@ impl CollabPanel { .size_full() .child(list(self.list_state.clone()).full()) .child( - v_stack().p_2().child( - v_stack() - .border_primary(cx) - .border_t() - .child(self.filter_editor.clone()), - ), + v_stack() + .child(div().mx_2().border_primary(cx).border_t()) + .child( + v_stack() + .p_2() + .child(self.render_filter_input(&self.filter_editor, cx)), + ), ) } + fn render_filter_input( + &self, + editor: &View, + cx: &mut ViewContext, + ) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if editor.read(cx).read_only() { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + EditorElement::new( + editor, + EditorStyle { + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } + fn render_header( &self, section: Section, From b3d8b2313901b29402cb145578b4fce2b22cb435 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:17:38 +0100 Subject: [PATCH 09/16] terminal/search: Partially fix search in terminal. There are two issues with search in terminal as is: - terminal's pane is not registered as a "legit" pane, so we dispatch buffer search bar::Deploy on the most recent "legit" pane. By legit I mean that workspace::active_pane will *never* return terminal pane as active. - We've had the implementation of as_searchable commented out. Duh! This commit fixes second issue. That means that if you drag the terminal over to the main editor pane (so that it's in a "legit" pane), it'll work. 1st issue still stands though. --- Cargo.lock | 1 + crates/terminal_view/Cargo.toml | 2 +- crates/terminal_view/src/terminal_panel.rs | 6 +++--- crates/terminal_view/src/terminal_view.rs | 9 ++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13d4be62337ae0557fbc59538db4a4bdceacd22e..4371742056075385f342917ae078a0b48698506f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7762,6 +7762,7 @@ dependencies = [ "procinfo", "project", "rand 0.8.5", + "search", "serde", "serde_derive", "settings", diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 110199a8d4b979734fda44c6d15ce03b583ed16f..d84846018346dc530465eb66835f9a9cc894ac59 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -13,7 +13,7 @@ editor = { path = "../editor" } language = { path = "../language" } gpui = { path = "../gpui" } project = { path = "../project" } -# search = { path = "../search" } +search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 526cdf391ae331aad67088cd2462cb1b704e3889..f5179d39a971dcc2ebab3191dec9075806b5bc2a 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -101,9 +101,9 @@ impl TerminalPanel { }) .into_any_element() }); - // let buffer_search_bar = cx.build_view(search::BufferSearchBar::new); - // pane.toolbar() - // .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); + let buffer_search_bar = cx.add_view(search::BufferSearchBar::new); + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); pane }); let subscriptions = vec![ diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d4dea29b49e71e66604ec56799d166778792ae9a..521efef7906c621e4ffabc9a6060aae50fd2e8dd 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -28,7 +28,7 @@ use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, notifications::NotifyResultExt, register_deserializable_item, - searchable::{SearchEvent, SearchOptions, SearchableItem}, + searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; @@ -714,10 +714,9 @@ impl Item for TerminalView { false } - // todo!(search) - // fn as_searchable(&self, handle: &View) -> Option> { - // Some(Box::new(handle.clone())) - // } + fn as_searchable(&self, handle: &View) -> Option> { + Some(Box::new(handle.clone())) + } fn breadcrumb_location(&self) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft From cd0b15e23d9b68249a6dc544c01fff101e140456 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:18:48 +0100 Subject: [PATCH 10/16] fixup! terminal/search: Partially fix search in terminal. There are two issues with search in terminal as is: - terminal's pane is not registered as a "legit" pane, so we dispatch buffer search bar::Deploy on the most recent "legit" pane. By legit I mean that workspace::active_pane will *never* return terminal pane as active. - We've had the implementation of as_searchable commented out. Duh! --- crates/terminal_view/src/terminal_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f5179d39a971dcc2ebab3191dec9075806b5bc2a..7118d6f767b37ea9a21f73e51a88407c8a4c8c65 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -101,7 +101,7 @@ impl TerminalPanel { }) .into_any_element() }); - let buffer_search_bar = cx.add_view(search::BufferSearchBar::new); + let buffer_search_bar = cx.new_view(search::BufferSearchBar::new); pane.toolbar() .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); pane From f70eddc988dfcfa0c83db6d2a16ca80b820ae68e Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:56:35 +0100 Subject: [PATCH 11/16] Explore registrar-based API for search bar. This commit adds a Registrar trait for use by search crate. Registrar can register actions on some target and search can utilize that trait to opaquely add actions on that target. Notably, search is now opt-in (it always was in zed2 actually). Having editor doesn't make it searchable straight out of the gate. You might have to call BufferSearchBar::new a bunch more. --- crates/search/src/buffer_search.rs | 154 ++++++++++++--------- crates/terminal_view/src/terminal_panel.rs | 40 +++++- 2 files changed, 125 insertions(+), 69 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 67aa4955bc692483faafb9b6291e9e8550dac859..3521b1e8490d88c21ec66323ce9f03825ea6677f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -422,89 +422,115 @@ impl ToolbarItemView for BufferSearchBar { } } +/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +pub trait SearchActionsRegistrar { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ); +} + impl BufferSearchBar { - fn register(workspace: &mut Workspace) { - workspace.register_action(move |workspace, deploy: &Deploy, cx| { - let pane = workspace.active_pane(); - - pane.update(cx, |this, cx| { - this.toolbar().update(cx, |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| { - this.deploy(deploy, cx); - }); - return; - } - let view = cx.new_view(|cx| BufferSearchBar::new(cx)); - this.add_item(view.clone(), cx); - view.update(cx, |this, cx| this.deploy(deploy, cx)); - cx.notify(); - }) + pub fn register_inner( + registrar: &mut impl SearchActionsRegistrar, + supported_options: &workspace::searchable::SearchOptions, + ) { + // supported_options controls whether the action is registered in the first place, + // but we still perform dynamic checks as e.g. if a view (like Workspace) uses SearchableItemHandle, they cannot know in advance + // whether a given option is supported. + if supported_options.case { + registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + if this.supported_options().case { + this.toggle_case_sensitive(action, cx); + } }); - }); - fn register_action( - workspace: &mut Workspace, - update: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - workspace.register_action(move |workspace, action: &A, cx| { - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |this, cx| update(this, action, cx)); - cx.notify(); - } - }) - }); + } + if supported_options.word { + registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + if this.supported_options().word { + this.toggle_whole_word(action, cx); + } }); } - register_action(workspace, |this, action: &ToggleCaseSensitive, cx| { - if this.supported_options().case { - this.toggle_case_sensitive(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleWholeWord, cx| { - if this.supported_options().word { - this.toggle_whole_word(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleReplace, cx| { - if this.supported_options().replacement { - this.toggle_replace(action, cx); - } - }); - register_action(workspace, |this, _: &ActivateRegexMode, cx| { - if this.supported_options().regex { - this.activate_search_mode(SearchMode::Regex, cx); - } - }); - register_action(workspace, |this, _: &ActivateTextMode, cx| { + if supported_options.replacement { + registrar.register_handler(|this, action: &ToggleReplace, cx| { + if this.supported_options().replacement { + this.toggle_replace(action, cx); + } + }); + } + + if supported_options.regex { + registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + if this.supported_options().regex { + this.activate_search_mode(SearchMode::Regex, cx); + } + }); + } + + registrar.register_handler(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); }); - register_action(workspace, |this, action: &CycleMode, cx| { - if this.supported_options().regex { - // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting - // cycling. - this.cycle_mode(action, cx) - } - }); - register_action(workspace, |this, action: &SelectNextMatch, cx| { + + if supported_options.regex { + registrar.register_handler(|this, action: &CycleMode, cx| { + if this.supported_options().regex { + // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting + // cycling. + this.cycle_mode(action, cx) + } + }); + } + + registrar.register_handler(|this, action: &SelectNextMatch, cx| { this.select_next_match(action, cx); }); - register_action(workspace, |this, action: &SelectPrevMatch, cx| { + registrar.register_handler(|this, action: &SelectPrevMatch, cx| { this.select_prev_match(action, cx); }); - register_action(workspace, |this, action: &SelectAllMatches, cx| { + registrar.register_handler(|this, action: &SelectAllMatches, cx| { this.select_all_matches(action, cx); }); - register_action(workspace, |this, _: &editor::Cancel, cx| { + registrar.register_handler(|this, _: &editor::Cancel, cx| { if !this.dismissed { this.dismiss(&Dismiss, cx); return; } cx.propagate(); }); + registrar.register_handler(|this, deploy, cx| { + this.deploy(deploy, cx); + }) + } + fn register(workspace: &mut Workspace) { + impl SearchActionsRegistrar for Workspace { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.register_action(move |workspace, action: &A, cx| { + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + search_bar.update(cx, move |this, cx| callback(this, action, cx)); + cx.notify(); + } + }) + }); + }); + } + } + Self::register_inner( + workspace, + &workspace::searchable::SearchOptions { + case: true, + word: true, + regex: true, + replacement: true, + }, + ); } pub fn new(cx: &mut ViewContext) -> Self { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7118d6f767b37ea9a21f73e51a88407c8a4c8c65..f363c5228bc7ad0056b2ece3321a60b34305b55d 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -3,11 +3,13 @@ use std::{path::PathBuf, sync::Arc}; use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, div, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, - FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, - Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, + ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, + Pixels, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use project::Fs; +use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; @@ -17,6 +19,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, + searchable::SearchableItem, ui::Icon, Pane, Workspace, }; @@ -328,9 +331,36 @@ impl TerminalPanel { impl EventEmitter for TerminalPanel {} +struct ActionsRegistrar<'a, 'b> +where + 'b: 'a, +{ + div: Option
, + cx: &'a mut ViewContext<'b, TerminalPanel>, +} +impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + this.pane + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl Render for TerminalPanel { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - div().size_full().child(self.pane.clone()) + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let div = div(); + let mut registrar = ActionsRegistrar { div: Some(div), cx }; + BufferSearchBar::register_inner(&mut registrar, &TerminalView::supported_options()); + registrar.div.unwrap().size_full().child(self.pane.clone()) } } From 5ad125a9e93dd8223b4ca10823cf41cdf6b9078b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:51:56 +0100 Subject: [PATCH 12/16] Touchups to registrar API --- Cargo.lock | 1 - crates/feedback/Cargo.toml | 1 - crates/search/src/buffer_search.rs | 84 ++++++++-------------- crates/terminal_view/src/terminal_panel.rs | 3 +- 4 files changed, 32 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4371742056075385f342917ae078a0b48698506f..0316c343cb4ea5922897d9fc8ab417793e770ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2486,7 +2486,6 @@ dependencies = [ "postage", "project", "regex", - "search", "serde", "serde_derive", "settings", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index c6c8b9f4b24c80793ca1d177d0fe9eb0682bbf69..e14df6a2d48264b37686a5bb942cc9ce9dcb7504 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -18,7 +18,6 @@ gpui = { path = "../gpui" } language = { path = "../language" } menu = { path = "../menu" } project = { path = "../project" } -search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } ui = { path = "../ui" } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 3521b1e8490d88c21ec66323ce9f03825ea6677f..afd0f6c615649a2ef61f997e0edd6a330ffea5a2 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -431,57 +431,42 @@ pub trait SearchActionsRegistrar { } impl BufferSearchBar { - pub fn register_inner( - registrar: &mut impl SearchActionsRegistrar, - supported_options: &workspace::searchable::SearchOptions, - ) { - // supported_options controls whether the action is registered in the first place, - // but we still perform dynamic checks as e.g. if a view (like Workspace) uses SearchableItemHandle, they cannot know in advance - // whether a given option is supported. - if supported_options.case { - registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { - if this.supported_options().case { - this.toggle_case_sensitive(action, cx); - } - }); - } - if supported_options.word { - registrar.register_handler(|this, action: &ToggleWholeWord, cx| { - if this.supported_options().word { - this.toggle_whole_word(action, cx); - } - }); - } + pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { + registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + if this.supported_options().case { + this.toggle_case_sensitive(action, cx); + } + }); - if supported_options.replacement { - registrar.register_handler(|this, action: &ToggleReplace, cx| { - if this.supported_options().replacement { - this.toggle_replace(action, cx); - } - }); - } + registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + if this.supported_options().word { + this.toggle_whole_word(action, cx); + } + }); - if supported_options.regex { - registrar.register_handler(|this, _: &ActivateRegexMode, cx| { - if this.supported_options().regex { - this.activate_search_mode(SearchMode::Regex, cx); - } - }); - } + registrar.register_handler(|this, action: &ToggleReplace, cx| { + if this.supported_options().replacement { + this.toggle_replace(action, cx); + } + }); + + registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + if this.supported_options().regex { + this.activate_search_mode(SearchMode::Regex, cx); + } + }); registrar.register_handler(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); }); - if supported_options.regex { - registrar.register_handler(|this, action: &CycleMode, cx| { - if this.supported_options().regex { - // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting - // cycling. - this.cycle_mode(action, cx) - } - }); - } + registrar.register_handler(|this, action: &CycleMode, cx| { + if this.supported_options().regex { + // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting + // cycling. + this.cycle_mode(action, cx) + } + }); registrar.register_handler(|this, action: &SelectNextMatch, cx| { this.select_next_match(action, cx); @@ -500,6 +485,7 @@ impl BufferSearchBar { cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { + dbg!("Deploying?"); this.deploy(deploy, cx); }) } @@ -522,15 +508,7 @@ impl BufferSearchBar { }); } } - Self::register_inner( - workspace, - &workspace::searchable::SearchOptions { - case: true, - word: true, - regex: true, - replacement: true, - }, - ); + Self::register_inner(workspace); } pub fn new(cx: &mut ViewContext) -> Self { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f363c5228bc7ad0056b2ece3321a60b34305b55d..ed228faba8c7810657d27e6274235dfaa2ae405b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,6 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, - searchable::SearchableItem, ui::Icon, Pane, Workspace, }; @@ -359,7 +358,7 @@ impl Render for TerminalPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let div = div(); let mut registrar = ActionsRegistrar { div: Some(div), cx }; - BufferSearchBar::register_inner(&mut registrar, &TerminalView::supported_options()); + BufferSearchBar::register_inner(&mut registrar); registrar.div.unwrap().size_full().child(self.pane.clone()) } } From 9cdcdbea419ef5c62bcc25c876da63268ed8c4f1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:07:18 +0100 Subject: [PATCH 13/16] Use Registrar in Assistant Panel. This fixes various actions like "Activate regex mode" that were dispatched onto main pane instead of assistant search bar. --- crates/assistant/src/assistant_panel.rs | 34 +++++++++++++++++++++++-- crates/search/src/buffer_search.rs | 1 - 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9221d87f60fda92990997a1401bf54325a099c6b..7ab6aba227dc1cccb9957afe02144d33981ab404 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -38,7 +38,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; -use search::BufferSearchBar; +use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::{Settings, SettingsStore}; use std::{ @@ -1100,6 +1100,26 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { }) } +struct SearchRegistrar<'a, 'b> { + div: Option
, + cx: &'a mut ViewContext<'b, AssistantPanel>, +} + +impl SearchActionsRegistrar for SearchRegistrar<'_, '_> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + this.toolbar + .read(cx) + .item_of_type::() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { @@ -1156,6 +1176,16 @@ impl Render for AssistantPanel { div() }); + let contents = if self.active_editor().is_some() { + let mut registrar = SearchRegistrar { + div: Some(div()), + cx, + }; + BufferSearchBar::register_inner(&mut registrar); + registrar.div.unwrap() + } else { + div() + }; v_stack() .key_context("AssistantPanel") .size_full() @@ -1176,7 +1206,7 @@ impl Render for AssistantPanel { Some(self.toolbar.clone()) }) .child( - div() + contents .flex_1() .child(if let Some(editor) = self.active_editor() { editor.clone().into_any_element() diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index afd0f6c615649a2ef61f997e0edd6a330ffea5a2..6e44db60c54d3dd6b048894d841b0bc7bdbde55a 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -485,7 +485,6 @@ impl BufferSearchBar { cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { - dbg!("Deploying?"); this.deploy(deploy, cx); }) } From b6655def70599bb3d6aa8d8985b1c2e104206a26 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:03:22 +0100 Subject: [PATCH 14/16] Add DivRegistrar to reduce code duplication --- crates/assistant/src/assistant_panel.rs | 30 +++------------ crates/search/src/buffer_search.rs | 40 ++++++++++++++++++++ crates/terminal_view/src/terminal_panel.rs | 44 +++++++--------------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7ab6aba227dc1cccb9957afe02144d33981ab404..371cc9007a8c1f2bc5fd013573294bcb024cc1d9 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -38,7 +38,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; -use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; +use search::{buffer_search::DivRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::{Settings, SettingsStore}; use std::{ @@ -1100,26 +1100,6 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { }) } -struct SearchRegistrar<'a, 'b> { - div: Option
, - cx: &'a mut ViewContext<'b, AssistantPanel>, -} - -impl SearchActionsRegistrar for SearchRegistrar<'_, '_> { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - this.toolbar - .read(cx) - .item_of_type::() - .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); - })) - }); - } -} impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { @@ -1177,12 +1157,12 @@ impl Render for AssistantPanel { }); let contents = if self.active_editor().is_some() { - let mut registrar = SearchRegistrar { - div: Some(div()), + let mut registrar = DivRegistrar::new( + |panel, cx| panel.toolbar.read(cx).item_of_type::(), cx, - }; + ); BufferSearchBar::register_inner(&mut registrar); - registrar.div.unwrap() + registrar.into_div() } else { div() }; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 6e44db60c54d3dd6b048894d841b0bc7bdbde55a..371037e0478f62d0edd084470388c331ef29d586 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -430,6 +430,46 @@ pub trait SearchActionsRegistrar { ); } +type GetSearchBar = + for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option>; + +/// Registers search actions on a div that can be taken out. +pub struct DivRegistrar<'a, 'b, T: 'static> { + div: Option
, + cx: &'a mut ViewContext<'b, T>, + search_getter: GetSearchBar, +} + +impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> { + pub fn new(search_getter: GetSearchBar, cx: &'a mut ViewContext<'b, T>) -> Self { + Self { + div: Some(div()), + cx, + search_getter, + } + } + pub fn into_div(self) -> Div { + // This option is always Some; it's an option in the first place because we want to call methods + // on div that require ownership. + self.div.unwrap() + } +} + +impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + let getter = self.search_getter; + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + (getter)(this, cx) + .clone() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl BufferSearchBar { pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ed228faba8c7810657d27e6274235dfaa2ae405b..82d7208ef88ba625d8430e8f5d314a9b00cb7719 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -3,13 +3,12 @@ use std::{path::PathBuf, sync::Arc}; use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, - ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, - Pixels, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, - WindowContext, + actions, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, + FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, + Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use project::Fs; -use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; +use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; @@ -330,36 +329,21 @@ impl TerminalPanel { impl EventEmitter for TerminalPanel {} -struct ActionsRegistrar<'a, 'b> -where - 'b: 'a, -{ - div: Option
, - cx: &'a mut ViewContext<'b, TerminalPanel>, -} -impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - this.pane +impl Render for TerminalPanel { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let mut registrar = DivRegistrar::new( + |panel, cx| { + panel + .pane .read(cx) .toolbar() .read(cx) .item_of_type::() - .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); - })) - }); - } -} -impl Render for TerminalPanel { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let div = div(); - let mut registrar = ActionsRegistrar { div: Some(div), cx }; + }, + cx, + ); BufferSearchBar::register_inner(&mut registrar); - registrar.div.unwrap().size_full().child(self.pane.clone()) + registrar.into_div().size_full().child(self.pane.clone()) } } From 783256c80e6222acd39015fb6ca85f977dacc1b1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:04:28 +0100 Subject: [PATCH 15/16] Move Registrar implementation for Workspace to outer scope. This fixes various actions like "Activate regex mode" that were dispatched onto main pane instead of assistant search bar. --- crates/search/src/buffer_search.rs | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 371037e0478f62d0edd084470388c331ef29d586..7cce3c71c6c1b61ee0679be22d8e4260dc73a17d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -470,6 +470,26 @@ impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { }); } } + +/// Register actions for an active pane. +impl SearchActionsRegistrar for Workspace { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.register_action(move |workspace, action: &A, cx| { + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + search_bar.update(cx, move |this, cx| callback(this, action, cx)); + cx.notify(); + } + }) + }); + }); + } +} impl BufferSearchBar { pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { @@ -529,24 +549,6 @@ impl BufferSearchBar { }) } fn register(workspace: &mut Workspace) { - impl SearchActionsRegistrar for Workspace { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.register_action(move |workspace, action: &A, cx| { - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |this, cx| callback(this, action, cx)); - cx.notify(); - } - }) - }); - }); - } - } Self::register_inner(workspace); } pub fn new(cx: &mut ViewContext) -> Self { From ad20bc39c5fcf23018fe3fa9d616675b069db579 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 15:31:00 -0800 Subject: [PATCH 16/16] Fix accidental load of default keymap *after* loading user keymap Co-authored-by: Mikayla Co-authored-by: Marshall --- crates/zed/src/zed.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fea84c296419c2a7f41a538ec026cdececeee99f..c7d30230ea035b29121ceb487e4ea3ce483d5d54 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -400,7 +400,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }); workspace.focus_handle(cx).focus(cx); - load_default_keymap(cx); }) .detach(); } @@ -571,6 +570,8 @@ pub fn handle_keymap_file_changes( }) .detach(); + load_default_keymap(cx); + cx.spawn(move |cx| async move { let mut user_keymap = KeymapFile::default(); loop {