,
+ 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)));
+ }))
+ });
+ }
+}
+
+/// 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, |this, cx| {
- this.deploy(deploy, cx);
- });
- return;
+ search_bar.update(cx, move |this, cx| callback(this, action, cx));
+ cx.notify();
}
- 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();
})
});
});
- 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();
- }
- })
- });
- });
- }
-
- register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
+ }
+}
+impl BufferSearchBar {
+ 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);
}
});
- register_action(workspace, |this, action: &ToggleWholeWord, cx| {
+
+ registrar.register_handler(|this, action: &ToggleWholeWord, cx| {
if this.supported_options().word {
this.toggle_whole_word(action, cx);
}
});
- register_action(workspace, |this, action: &ToggleReplace, cx| {
+
+ registrar.register_handler(|this, action: &ToggleReplace, cx| {
if this.supported_options().replacement {
this.toggle_replace(action, cx);
}
});
- register_action(workspace, |this, _: &ActivateRegexMode, cx| {
+
+ registrar.register_handler(|this, _: &ActivateRegexMode, cx| {
if this.supported_options().regex {
this.activate_search_mode(SearchMode::Regex, cx);
}
});
- register_action(workspace, |this, _: &ActivateTextMode, cx| {
+
+ registrar.register_handler(|this, _: &ActivateTextMode, cx| {
this.activate_search_mode(SearchMode::Text, cx);
});
- register_action(workspace, |this, action: &CycleMode, 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)
}
});
- register_action(workspace, |this, action: &SelectNextMatch, 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) {
+ 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/search/src/project_search.rs b/crates/search/src/project_search.rs
index 94435e8b76..a370cca9f6 100644
--- a/crates/search/src/project_search.rs
+++ b/crates/search/src/project_search.rs
@@ -38,8 +38,8 @@ use std::{
use theme::ThemeSettings;
use ui::{
- h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon,
- LabelSize, Selectable, Tooltip,
+ h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize,
+ Selectable, ToggleButton, Tooltip,
};
use util::{paths::PathMatcher, ResultExt as _};
use workspace::{
@@ -61,12 +61,12 @@ struct ActiveSearches(HashMap, WeakView>);
struct ActiveSettings(HashMap, ProjectSearchSettings>);
pub fn init(cx: &mut AppContext) {
- // todo!() po
cx.set_global(ActiveSearches::default());
cx.set_global(ActiveSettings::default());
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace
- .register_action(ProjectSearchView::deploy)
+ .register_action(ProjectSearchView::new_search)
+ .register_action(ProjectSearchView::deploy_search)
.register_action(ProjectSearchBar::search_in_new);
})
.detach();
@@ -288,7 +288,6 @@ impl Render for ProjectSearchView {
.size_full()
.track_focus(&self.focus_handle)
.child(self.results_editor.clone())
- .into_any()
} else {
let model = self.model.read(cx);
let has_no_results = model.no_results.unwrap_or(false);
@@ -365,6 +364,7 @@ impl Render for ProjectSearchView {
.flex_1()
.size_full()
.justify_center()
+ .bg(cx.theme().colors().editor_background)
.track_focus(&self.focus_handle)
.child(
h_stack()
@@ -374,7 +374,6 @@ impl Render for ProjectSearchView {
.child(v_stack().child(major_text).children(minor_text))
.child(h_stack().flex_1()),
)
- .into_any()
}
}
}
@@ -943,11 +942,41 @@ impl ProjectSearchView {
});
}
+ // Re-activate the most recently activated search or the most recent if it has been closed.
+ // If no search exists in the workspace, create a new one.
+ fn deploy_search(
+ workspace: &mut Workspace,
+ _: &workspace::DeploySearch,
+ cx: &mut ViewContext,
+ ) {
+ let active_search = cx
+ .global::()
+ .0
+ .get(&workspace.project().downgrade());
+ let existing = active_search
+ .and_then(|active_search| {
+ workspace
+ .items_of_type::(cx)
+ .filter(|search| &search.downgrade() == active_search)
+ .last()
+ })
+ .or_else(|| workspace.item_of_type::(cx));
+ Self::existing_or_new_search(workspace, existing, cx)
+ }
+
// Add another search tab to the workspace.
- fn deploy(
+ fn new_search(
workspace: &mut Workspace,
_: &workspace::NewSearch,
cx: &mut ViewContext,
+ ) {
+ Self::existing_or_new_search(workspace, None, cx)
+ }
+
+ fn existing_or_new_search(
+ workspace: &mut Workspace,
+ existing: Option>,
+ cx: &mut ViewContext,
) {
// Clean up entries for dropped projects
cx.update_global(|state: &mut ActiveSearches, _cx| {
@@ -964,19 +993,27 @@ impl ProjectSearchView {
}
});
- let settings = cx
- .global::()
- .0
- .get(&workspace.project().downgrade());
-
- let settings = if let Some(settings) = settings {
- Some(settings.clone())
+ let search = if let Some(existing) = existing {
+ workspace.activate_item(&existing, cx);
+ existing
} else {
- None
- };
+ let settings = cx
+ .global::()
+ .0
+ .get(&workspace.project().downgrade());
- let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
- let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
+ let settings = if let Some(settings) = settings {
+ Some(settings.clone())
+ } else {
+ None
+ };
+
+ let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
+ let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
+
+ workspace.add_item(Box::new(view.clone()), cx);
+ view
+ };
workspace.add_item(Box::new(search.clone()), cx);
@@ -1641,20 +1678,26 @@ impl Render for ProjectSearchBar {
let mode_column = v_stack().items_start().justify_start().child(
h_stack()
+ .gap_2()
.child(
h_stack()
.child(
- Button::new("project-search-text-button", "Text")
+ ToggleButton::new("project-search-text-button", "Text")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Text)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Text, cx)
}))
.tooltip(|cx| {
Tooltip::for_action("Toggle text search", &ActivateTextMode, cx)
- }),
+ })
+ .first(),
)
.child(
- Button::new("project-search-regex-button", "Regex")
+ ToggleButton::new("project-search-regex-button", "Regex")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Regex)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Regex, cx)
@@ -1665,11 +1708,20 @@ impl Render for ProjectSearchBar {
&ActivateRegexMode,
cx,
)
+ })
+ .map(|this| {
+ if semantic_is_available {
+ this.middle()
+ } else {
+ this.last()
+ }
}),
)
.when(semantic_is_available, |this| {
this.child(
- Button::new("project-search-semantic-button", "Semantic")
+ ToggleButton::new("project-search-semantic-button", "Semantic")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Semantic)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Semantic, cx)
@@ -1680,7 +1732,8 @@ impl Render for ProjectSearchBar {
&ActivateSemanticMode,
cx,
)
- }),
+ })
+ .last(),
)
}),
)
@@ -1831,6 +1884,7 @@ impl Render for ProjectSearchBar {
.child(
h_stack()
.justify_between()
+ .gap_2()
.child(query_column)
.child(mode_column)
.child(replace_column)
@@ -2062,7 +2116,7 @@ pub mod tests {
}
#[gpui::test]
- async fn test_project_search_focus(cx: &mut TestAppContext) {
+ async fn test_deploy_project_search_focus(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
@@ -2103,7 +2157,237 @@ pub mod tests {
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
- ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+ ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
+ })
+ .unwrap();
+
+ let Some(search_view) = cx.read(|cx| {
+ workspace
+ .read(cx)
+ .unwrap()
+ .active_pane()
+ .read(cx)
+ .active_item()
+ .and_then(|item| item.downcast::())
+ }) else {
+ panic!("Search view expected to appear after new search event trigger")
+ };
+
+ cx.spawn(|mut cx| async move {
+ window
+ .update(&mut cx, |_, cx| {
+ cx.dispatch_action(ToggleFocus.boxed_clone())
+ })
+ .unwrap();
+ })
+ .detach();
+ cx.background_executor.run_until_parked();
+ window
+ .update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert!(
+ search_view.query_editor.focus_handle(cx).is_focused(cx),
+ "Empty search view should be focused after the toggle focus event: no results panel to focus on",
+ );
+ });
+ }).unwrap();
+
+ window
+ .update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ let query_editor = &search_view.query_editor;
+ assert!(
+ query_editor.focus_handle(cx).is_focused(cx),
+ "Search view should be focused after the new search view is activated",
+ );
+ let query_text = query_editor.read(cx).text(cx);
+ assert!(
+ query_text.is_empty(),
+ "New search query should be empty but got '{query_text}'",
+ );
+ let results_text = search_view
+ .results_editor
+ .update(cx, |editor, cx| editor.display_text(cx));
+ assert!(
+ results_text.is_empty(),
+ "Empty search view should have no results but got '{results_text}'"
+ );
+ });
+ })
+ .unwrap();
+
+ window
+ .update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ search_view.query_editor.update(cx, |query_editor, cx| {
+ query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx)
+ });
+ search_view.search(cx);
+ });
+ })
+ .unwrap();
+ cx.background_executor.run_until_parked();
+ window
+ .update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ let results_text = search_view
+ .results_editor
+ .update(cx, |editor, cx| editor.display_text(cx));
+ assert!(
+ results_text.is_empty(),
+ "Search view for mismatching query should have no results but got '{results_text}'"
+ );
+ assert!(
+ search_view.query_editor.focus_handle(cx).is_focused(cx),
+ "Search view should be focused after mismatching query had been used in search",
+ );
+ });
+ }).unwrap();
+
+ cx.spawn(|mut cx| async move {
+ window.update(&mut cx, |_, cx| {
+ cx.dispatch_action(ToggleFocus.boxed_clone())
+ })
+ })
+ .detach();
+ cx.background_executor.run_until_parked();
+ window.update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert!(
+ search_view.query_editor.focus_handle(cx).is_focused(cx),
+ "Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on",
+ );
+ });
+ }).unwrap();
+
+ window
+ .update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ search_view
+ .query_editor
+ .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
+ search_view.search(cx);
+ });
+ })
+ .unwrap();
+ cx.background_executor.run_until_parked();
+ window.update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert_eq!(
+ search_view
+ .results_editor
+ .update(cx, |editor, cx| editor.display_text(cx)),
+ "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
+ "Search view results should match the query"
+ );
+ assert!(
+ search_view.results_editor.focus_handle(cx).is_focused(cx),
+ "Search view with mismatching query should be focused after search results are available",
+ );
+ });
+ }).unwrap();
+ cx.spawn(|mut cx| async move {
+ window
+ .update(&mut cx, |_, cx| {
+ cx.dispatch_action(ToggleFocus.boxed_clone())
+ })
+ .unwrap();
+ })
+ .detach();
+ cx.background_executor.run_until_parked();
+ window.update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert!(
+ search_view.results_editor.focus_handle(cx).is_focused(cx),
+ "Search view with matching query should still have its results editor focused after the toggle focus event",
+ );
+ });
+ }).unwrap();
+
+ workspace
+ .update(cx, |workspace, cx| {
+ ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
+ })
+ .unwrap();
+ window.update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
+ assert_eq!(
+ search_view
+ .results_editor
+ .update(cx, |editor, cx| editor.display_text(cx)),
+ "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
+ "Results should be unchanged after search view 2nd open in a row"
+ );
+ assert!(
+ search_view.query_editor.focus_handle(cx).is_focused(cx),
+ "Focus should be moved into query editor again after search view 2nd open in a row"
+ );
+ });
+ }).unwrap();
+
+ cx.spawn(|mut cx| async move {
+ window
+ .update(&mut cx, |_, cx| {
+ cx.dispatch_action(ToggleFocus.boxed_clone())
+ })
+ .unwrap();
+ })
+ .detach();
+ cx.background_executor.run_until_parked();
+ window.update(cx, |_, cx| {
+ search_view.update(cx, |search_view, cx| {
+ assert!(
+ search_view.results_editor.focus_handle(cx).is_focused(cx),
+ "Search view with matching query should switch focus to the results editor after the toggle focus event",
+ );
+ });
+ }).unwrap();
+ }
+
+ #[gpui::test]
+ async fn test_new_project_search_focus(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ "/dir",
+ json!({
+ "one.rs": "const ONE: usize = 1;",
+ "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+ "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+ "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+ }),
+ )
+ .await;
+ let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.clone();
+ let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
+
+ let active_item = cx.read(|cx| {
+ workspace
+ .read(cx)
+ .unwrap()
+ .active_pane()
+ .read(cx)
+ .active_item()
+ .and_then(|item| item.downcast::())
+ });
+ assert!(
+ active_item.is_none(),
+ "Expected no search panel to be active"
+ );
+
+ window
+ .update(cx, move |workspace, cx| {
+ assert_eq!(workspace.panes().len(), 1);
+ workspace.panes()[0].update(cx, move |pane, cx| {
+ pane.toolbar()
+ .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
+ });
+
+ ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
})
.unwrap();
@@ -2252,7 +2536,7 @@ pub mod tests {
workspace
.update(cx, |workspace, cx| {
- ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+ ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
})
.unwrap();
cx.background_executor.run_until_parked();
@@ -2538,7 +2822,7 @@ pub mod tests {
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
- ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+ ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
}
})
.unwrap();
diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs
index 590079c51b..3a43e3f9dd 100644
--- a/crates/settings/src/settings_file.rs
+++ b/crates/settings/src/settings_file.rs
@@ -1,4 +1,4 @@
-use crate::{settings_store::SettingsStore, KeymapFile, Settings};
+use crate::{settings_store::SettingsStore, Settings};
use anyhow::Result;
use fs::Fs;
use futures::{channel::mpsc, StreamExt};
@@ -77,7 +77,6 @@ pub fn handle_settings_file_changes(
});
cx.spawn(move |mut cx| async move {
while let Some(user_settings_content) = user_settings_file_rx.next().await {
- eprintln!("settings file changed");
let result = cx.update_global(|store: &mut SettingsStore, cx| {
store
.set_user_settings(&user_settings_content, cx)
@@ -121,14 +120,3 @@ pub fn update_settings_file(
})
.detach_and_log_err(cx);
}
-
-pub fn load_default_keymap(cx: &mut AppContext) {
- for path in ["keymaps/default.json", "keymaps/vim.json"] {
- KeymapFile::load_asset(path, cx).unwrap();
- }
-
- // todo!()
- // if let Some(asset_path) = settings::get::(cx).asset_path() {
- // KeymapFile::load_asset(asset_path, cx).unwrap();
- // }
-}
diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs
index a32d83d28d..af3e6b640f 100644
--- a/crates/terminal/src/mappings/mouse.rs
+++ b/crates/terminal/src/mappings/mouse.rs
@@ -158,39 +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.floor();
- 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 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
- {
+ 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 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 b15bd7c6d6..d43508bdbe 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;
@@ -61,15 +62,7 @@ use lazy_static::lazy_static;
actions!(
terminal,
- [
- Clear,
- Copy,
- Paste,
- ShowCharacterPalette,
- SearchTest,
- SendText,
- SendKeystroke,
- ]
+ [Clear, Copy, Paste, ShowCharacterPalette, SearchTest,]
);
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
@@ -704,14 +697,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 +1079,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 +1165,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),
diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml
index 110199a8d4..d848460183 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_element.rs b/crates/terminal_view/src/terminal_element.rs
index 8be10f9469..ffdca7d813 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;
@@ -421,7 +422,7 @@ impl TerminalElement {
let rem_size = cx.rem_size();
let font_pixels = text_style.font_size.to_pixels(rem_size);
let line_height = font_pixels * line_height.to_pixels(rem_size);
- let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
+ let font_id = cx.text_system().resolve_font(&text_style.font());
// todo!(do we need to keep this unwrap?)
let cell_width = text_system
@@ -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| {
diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs
index 9e193e83b7..32bad620ba 100644
--- a/crates/terminal_view/src/terminal_panel.rs
+++ b/crates/terminal_view/src/terminal_panel.rs
@@ -3,11 +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, Entity, EventEmitter, ExternalPaths,
+ 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::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
@@ -52,7 +53,7 @@ pub struct TerminalPanel {
impl TerminalPanel {
fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self {
- let terminal_panel = cx.view().clone();
+ let terminal_panel = cx.view().downgrade();
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
@@ -76,14 +77,17 @@ impl TerminalPanel {
pane.set_can_navigate(false, cx);
pane.display_nav_history_buttons(false);
pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
+ let terminal_panel = terminal_panel.clone();
h_stack()
.gap_2()
.child(
IconButton::new("plus", Icon::Plus)
.icon_size(IconSize::Small)
- .on_click(cx.listener_for(&terminal_panel, |terminal_panel, _, cx| {
- terminal_panel.add_terminal(None, cx);
- }))
+ .on_click(move |_, cx| {
+ terminal_panel
+ .update(cx, |panel, cx| panel.add_terminal(None, cx))
+ .log_err();
+ })
.tooltip(|cx| Tooltip::text("New Terminal", cx)),
)
.child({
@@ -101,9 +105,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.new_view(search::BufferSearchBar::new);
+ pane.toolbar()
+ .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
pane
});
let subscriptions = vec![
@@ -329,8 +333,20 @@ impl TerminalPanel {
impl EventEmitter for TerminalPanel {}
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