From 20d8a2a1ec8f01f71fc1402b04f5d14014b0c688 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 27 Jun 2023 11:52:04 -0600 Subject: [PATCH 001/162] vim: indent in visual mode uses only one < Fixes: zed-industries/community#1562 --- assets/keymaps/vim.json | 4 ++-- crates/vim/src/test.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 639daef614332a7103b2195e298fe0fcf8f2eef3..84f9689c2cbb346e7b89d03dea1e0389c5ca6632 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -314,8 +314,8 @@ "vim::SwitchMode", "Normal" ], - "> >": "editor::Indent", - "< <": "editor::Outdent" + ">": "editor::Indent", + "<": "editor::Outdent" } }, { diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index a6efbd4da1de34447d7165040bd6da32a79faa9c..95962f85b892b1dfa376bb081dd5e1c22dd9d5fc 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -137,7 +137,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("aa\nbˇb\ncc"); // works in visuial mode - cx.simulate_keystrokes(["shift-v", "down", ">", ">"]); + cx.simulate_keystrokes(["shift-v", "down", ">"]); cx.assert_editor_state("aa\n b«b\n cˇ»c"); } From 75fe77c11dc85a2f8ecbe42e9c6039eecc3f5f6c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 27 Jun 2023 21:46:08 -0600 Subject: [PATCH 002/162] search: Allow running a search with different options Refactor search options to use bitflags so that we can represent the entire set of settings in one place. --- Cargo.lock | 1 + crates/search/Cargo.toml | 1 + crates/search/src/buffer_search.rs | 181 +++++++++++++++++++++------- crates/search/src/project_search.rs | 70 ++++------- crates/search/src/search.rs | 43 ++++--- 5 files changed, 196 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60ed830683a5f91d0c8f4806b5d6758b2afbc31e..c6f5e70c9f0be82bb5d355ebc2a23015495e0f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6428,6 +6428,7 @@ name = "search" version = "0.1.0" dependencies = [ "anyhow", + "bitflags", "client", "collections", "editor", diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 7ef388f7c087638c1ee3f5c2002ab3d2c3371dc7..b37d0a46adb7c5a7142e5b1c4e9ee5516bee3072 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -9,6 +9,7 @@ path = "src/search.rs" doctest = false [dependencies] +bitflags = "1" collections = { path = "../collections" } editor = { path = "../editor" } gpui = { path = "../gpui" } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 59d25c2659f5ebbcad2b0a7ca4825c8a5bbf0d37..140f5accabe7180ad978c77006cc2f3f170602d7 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,5 +1,5 @@ use crate::{ - SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, + SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord, }; use collections::HashMap; @@ -42,12 +42,12 @@ pub fn init(cx: &mut AppContext) { cx.add_action(BufferSearchBar::select_next_match_on_pane); cx.add_action(BufferSearchBar::select_prev_match_on_pane); cx.add_action(BufferSearchBar::handle_editor_cancel); - add_toggle_option_action::(SearchOption::CaseSensitive, cx); - add_toggle_option_action::(SearchOption::WholeWord, cx); - add_toggle_option_action::(SearchOption::Regex, cx); + add_toggle_option_action::(SearchOptions::CASE_SENSITIVE, cx); + add_toggle_option_action::(SearchOptions::WHOLE_WORD, cx); + add_toggle_option_action::(SearchOptions::REGEX, cx); } -fn add_toggle_option_action(option: SearchOption, cx: &mut AppContext) { +fn add_toggle_option_action(option: SearchOptions, cx: &mut AppContext) { cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { if search_bar.update(cx, |search_bar, cx| search_bar.show(false, false, cx)) { @@ -69,9 +69,8 @@ pub struct BufferSearchBar { seachable_items_with_matches: HashMap, Vec>>, pending_search: Option>, - case_sensitive: bool, - whole_word: bool, - regex: bool, + search_options: SearchOptions, + default_options: SearchOptions, query_contains_error: bool, dismissed: bool, } @@ -153,19 +152,19 @@ impl View for BufferSearchBar { .with_children(self.render_search_option( supported_options.case, "Case", - SearchOption::CaseSensitive, + SearchOptions::CASE_SENSITIVE, cx, )) .with_children(self.render_search_option( supported_options.word, "Word", - SearchOption::WholeWord, + SearchOptions::WHOLE_WORD, cx, )) .with_children(self.render_search_option( supported_options.regex, "Regex", - SearchOption::Regex, + SearchOptions::REGEX, cx, )) .contained() @@ -250,9 +249,8 @@ impl BufferSearchBar { active_searchable_item_subscription: None, active_match_index: None, seachable_items_with_matches: Default::default(), - case_sensitive: false, - whole_word: false, - regex: false, + default_options: SearchOptions::NONE, + search_options: SearchOptions::NONE, pending_search: None, query_contains_error: false, dismissed: true, @@ -280,6 +278,17 @@ impl BufferSearchBar { } pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext) -> bool { + self.show_with_options(focus, suggest_query, self.default_options, cx) + } + + pub fn show_with_options( + &mut self, + focus: bool, + suggest_query: bool, + search_option: SearchOptions, + cx: &mut ViewContext, + ) -> bool { + self.search_options = search_option; let searchable_item = if let Some(searchable_item) = &self.active_searchable_item { SearchableItemHandle::boxed_clone(searchable_item.as_ref()) } else { @@ -320,7 +329,7 @@ impl BufferSearchBar { &self, option_supported: bool, icon: &'static str, - option: SearchOption, + option: SearchOptions, cx: &mut ViewContext, ) -> Option> { if !option_supported { @@ -328,9 +337,9 @@ impl BufferSearchBar { } let tooltip_style = theme::current(cx).tooltip.clone(); - let is_active = self.is_search_option_enabled(option); + let is_active = self.search_options.contains(option); Some( - MouseEventHandler::::new(option as usize, cx, |state, cx| { + MouseEventHandler::::new(option.bits as usize, cx, |state, cx| { let theme = theme::current(cx); let style = theme .search @@ -346,7 +355,7 @@ impl BufferSearchBar { }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( - option as usize, + option.bits as usize, format!("Toggle {}", option.label()), Some(option.to_toggle_action()), tooltip_style, @@ -461,21 +470,10 @@ impl BufferSearchBar { } } - fn is_search_option_enabled(&self, search_option: SearchOption) -> bool { - match search_option { - SearchOption::WholeWord => self.whole_word, - SearchOption::CaseSensitive => self.case_sensitive, - SearchOption::Regex => self.regex, - } - } + fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext) { + self.search_options.toggle(search_option); + self.default_options = self.search_options; - fn toggle_search_option(&mut self, search_option: SearchOption, cx: &mut ViewContext) { - let value = match search_option { - SearchOption::WholeWord => &mut self.whole_word, - SearchOption::CaseSensitive => &mut self.case_sensitive, - SearchOption::Regex => &mut self.regex, - }; - *value = !*value; self.update_matches(false, cx); cx.notify(); } @@ -571,11 +569,11 @@ impl BufferSearchBar { self.active_match_index.take(); active_searchable_item.clear_matches(cx); } else { - let query = if self.regex { + let query = if self.search_options.contains(SearchOptions::REGEX) { match SearchQuery::regex( query, - self.whole_word, - self.case_sensitive, + self.search_options.contains(SearchOptions::WHOLE_WORD), + self.search_options.contains(SearchOptions::CASE_SENSITIVE), Vec::new(), Vec::new(), ) { @@ -589,8 +587,8 @@ impl BufferSearchBar { } else { SearchQuery::text( query, - self.whole_word, - self.case_sensitive, + self.search_options.contains(SearchOptions::WHOLE_WORD), + self.search_options.contains(SearchOptions::CASE_SENSITIVE), Vec::new(), Vec::new(), ) @@ -656,8 +654,7 @@ mod tests { use language::Buffer; use unindent::Unindent as _; - #[gpui::test] - async fn test_search_simple(cx: &mut TestAppContext) { + fn init_test(cx: &mut TestAppContext) -> (ViewHandle, ViewHandle) { crate::project_search::tests::init_test(cx); let buffer = cx.add_model(|cx| { @@ -684,6 +681,13 @@ mod tests { search_bar }); + (editor, search_bar) + } + + #[gpui::test] + async fn test_search_simple(cx: &mut TestAppContext) { + let (editor, search_bar) = init_test(cx); + // Search for a string that appears with different casing. // By default, search is case-insensitive. search_bar.update(cx, |search_bar, cx| { @@ -708,7 +712,7 @@ mod tests { // Switch to a case sensitive search. search_bar.update(cx, |search_bar, cx| { - search_bar.toggle_search_option(SearchOption::CaseSensitive, cx); + search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); }); editor.next_notification(cx).await; editor.update(cx, |editor, cx| { @@ -765,7 +769,7 @@ mod tests { // Switch to a whole word search. search_bar.update(cx, |search_bar, cx| { - search_bar.toggle_search_option(SearchOption::WholeWord, cx); + search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx); }); editor.next_notification(cx).await; editor.update(cx, |editor, cx| { @@ -966,4 +970,99 @@ mod tests { assert_eq!(search_bar.active_match_index, Some(2)); }); } + + #[gpui::test] + async fn test_search_with_options(cx: &mut TestAppContext) { + let (editor, search_bar) = init_test(cx); + + // show with options should make current search case sensitive + search_bar.update(cx, |search_bar, cx| { + search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx); + search_bar.set_query("us", cx); + }); + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + assert_eq!( + editor.all_background_highlights(cx), + &[( + DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45), + Color::red(), + )] + ); + }); + + // show should return to the default options (case insensitive) + search_bar.update(cx, |search_bar, cx| { + search_bar.show(true, true, cx); + }); + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + assert_eq!( + editor.all_background_highlights(cx), + &[ + ( + DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19), + Color::red(), + ), + ( + DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45), + Color::red(), + ) + ] + ); + }); + + // toggling a search option (even in show_with_options mode) should update the defaults + search_bar.update(cx, |search_bar, cx| { + search_bar.set_query("regex", cx); + search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx); + search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx) + }); + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + assert_eq!( + editor.all_background_highlights(cx), + &[( + DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40), + Color::red(), + ),] + ); + }); + + // defaults should still include whole word + search_bar.update(cx, |search_bar, cx| { + search_bar.show(true, true, cx); + }); + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + assert_eq!( + editor.all_background_highlights(cx), + &[( + DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40), + Color::red(), + ),] + ); + }); + + // removing whole word changes the search again + search_bar.update(cx, |search_bar, cx| { + search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx) + }); + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + assert_eq!( + editor.all_background_highlights(cx), + &[ + ( + DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40), + Color::red(), + ), + ( + DisplayPoint::new(0, 44)..DisplayPoint::new(0, 49), + Color::red() + ) + ] + ); + }); + } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 135194df6a2f5a22c1ed001a725488ae2c003437..4e485eaaabb3d03906e62a48804468303cc832fd 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1,5 +1,5 @@ use crate::{ - SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, + SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord, }; use anyhow::Result; @@ -51,12 +51,12 @@ pub fn init(cx: &mut AppContext) { cx.add_action(ProjectSearchBar::select_prev_match); cx.capture_action(ProjectSearchBar::tab); cx.capture_action(ProjectSearchBar::tab_previous); - add_toggle_option_action::(SearchOption::CaseSensitive, cx); - add_toggle_option_action::(SearchOption::WholeWord, cx); - add_toggle_option_action::(SearchOption::Regex, cx); + add_toggle_option_action::(SearchOptions::CASE_SENSITIVE, cx); + add_toggle_option_action::(SearchOptions::WHOLE_WORD, cx); + add_toggle_option_action::(SearchOptions::REGEX, cx); } -fn add_toggle_option_action(option: SearchOption, cx: &mut AppContext) { +fn add_toggle_option_action(option: SearchOptions, cx: &mut AppContext) { cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { if search_bar.update(cx, |search_bar, cx| { @@ -89,9 +89,7 @@ pub struct ProjectSearchView { model: ModelHandle, query_editor: ViewHandle, results_editor: ViewHandle, - case_sensitive: bool, - whole_word: bool, - regex: bool, + search_options: SearchOptions, panels_with_errors: HashSet, active_match_index: Option, search_id: usize, @@ -408,9 +406,7 @@ impl ProjectSearchView { let project; let excerpts; let mut query_text = String::new(); - let mut regex = false; - let mut case_sensitive = false; - let mut whole_word = false; + let mut options = SearchOptions::NONE; { let model = model.read(cx); @@ -418,9 +414,7 @@ impl ProjectSearchView { excerpts = model.excerpts.clone(); if let Some(active_query) = model.active_query.as_ref() { query_text = active_query.as_str().to_string(); - regex = active_query.is_regex(); - case_sensitive = active_query.case_sensitive(); - whole_word = active_query.whole_word(); + options = SearchOptions::from_query(active_query); } } cx.observe(&model, |this, _, cx| this.model_changed(cx)) @@ -496,9 +490,7 @@ impl ProjectSearchView { model, query_editor, results_editor, - case_sensitive, - whole_word, - regex, + search_options: options, panels_with_errors: HashSet::new(), active_match_index: None, query_editor_was_focused: false, @@ -594,11 +586,11 @@ impl ProjectSearchView { return None; } }; - if self.regex { + if self.search_options.contains(SearchOptions::REGEX) { match SearchQuery::regex( text, - self.whole_word, - self.case_sensitive, + self.search_options.contains(SearchOptions::WHOLE_WORD), + self.search_options.contains(SearchOptions::CASE_SENSITIVE), included_files, excluded_files, ) { @@ -615,8 +607,8 @@ impl ProjectSearchView { } else { Some(SearchQuery::text( text, - self.whole_word, - self.case_sensitive, + self.search_options.contains(SearchOptions::WHOLE_WORD), + self.search_options.contains(SearchOptions::CASE_SENSITIVE), included_files, excluded_files, )) @@ -765,9 +757,7 @@ impl ProjectSearchBar { search_view.query_editor.update(cx, |editor, cx| { editor.set_text(old_query.as_str(), cx); }); - search_view.regex = old_query.is_regex(); - search_view.whole_word = old_query.whole_word(); - search_view.case_sensitive = old_query.case_sensitive(); + search_view.search_options = SearchOptions::from_query(&old_query); } } new_query @@ -855,15 +845,10 @@ impl ProjectSearchBar { }); } - fn toggle_search_option(&mut self, option: SearchOption, cx: &mut ViewContext) -> bool { + fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext) -> bool { if let Some(search_view) = self.active_project_search.as_ref() { search_view.update(cx, |search_view, cx| { - let value = match option { - SearchOption::WholeWord => &mut search_view.whole_word, - SearchOption::CaseSensitive => &mut search_view.case_sensitive, - SearchOption::Regex => &mut search_view.regex, - }; - *value = !*value; + search_view.search_options.toggle(option); search_view.search(cx); }); cx.notify(); @@ -920,12 +905,12 @@ impl ProjectSearchBar { fn render_option_button( &self, icon: &'static str, - option: SearchOption, + option: SearchOptions, cx: &mut ViewContext, ) -> AnyElement { let tooltip_style = theme::current(cx).tooltip.clone(); let is_active = self.is_option_enabled(option, cx); - MouseEventHandler::::new(option as usize, cx, |state, cx| { + MouseEventHandler::::new(option.bits as usize, cx, |state, cx| { let theme = theme::current(cx); let style = theme .search @@ -941,7 +926,7 @@ impl ProjectSearchBar { }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( - option as usize, + option.bits as usize, format!("Toggle {}", option.label()), Some(option.to_toggle_action()), tooltip_style, @@ -950,14 +935,9 @@ impl ProjectSearchBar { .into_any() } - fn is_option_enabled(&self, option: SearchOption, cx: &AppContext) -> bool { + fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool { if let Some(search) = self.active_project_search.as_ref() { - let search = search.read(cx); - match option { - SearchOption::WholeWord => search.whole_word, - SearchOption::CaseSensitive => search.case_sensitive, - SearchOption::Regex => search.regex, - } + search.read(cx).search_options.contains(option) } else { false } @@ -1048,17 +1028,17 @@ impl View for ProjectSearchBar { Flex::row() .with_child(self.render_option_button( "Case", - SearchOption::CaseSensitive, + SearchOptions::CASE_SENSITIVE, cx, )) .with_child(self.render_option_button( "Word", - SearchOption::WholeWord, + SearchOptions::WHOLE_WORD, cx, )) .with_child(self.render_option_button( "Regex", - SearchOption::Regex, + SearchOptions::REGEX, cx, )) .contained() diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 90ea508cc61ad59b64606bede16c35882452d2b7..efec4c2516fcffdcc3326f1f32774dc5c2626450 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -1,5 +1,7 @@ +use bitflags::bitflags; pub use buffer_search::BufferSearchBar; use gpui::{actions, Action, AppContext}; +use project::search::SearchQuery; pub use project_search::{ProjectSearchBar, ProjectSearchView}; pub mod buffer_search; @@ -21,27 +23,40 @@ actions!( ] ); -#[derive(Clone, Copy, PartialEq)] -pub enum SearchOption { - WholeWord, - CaseSensitive, - Regex, +bitflags! { + #[derive(Default)] + pub struct SearchOptions: u8 { + const NONE = 0b000; + const WHOLE_WORD = 0b001; + const CASE_SENSITIVE = 0b010; + const REGEX = 0b100; + } } -impl SearchOption { +impl SearchOptions { pub fn label(&self) -> &'static str { - match self { - SearchOption::WholeWord => "Match Whole Word", - SearchOption::CaseSensitive => "Match Case", - SearchOption::Regex => "Use Regular Expression", + match *self { + SearchOptions::WHOLE_WORD => "Match Whole Word", + SearchOptions::CASE_SENSITIVE => "Match Case", + SearchOptions::REGEX => "Use Regular Expression", + _ => panic!("{:?} is not a named SearchOption", self), } } pub fn to_toggle_action(&self) -> Box { - match self { - SearchOption::WholeWord => Box::new(ToggleWholeWord), - SearchOption::CaseSensitive => Box::new(ToggleCaseSensitive), - SearchOption::Regex => Box::new(ToggleRegex), + match *self { + SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord), + SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive), + SearchOptions::REGEX => Box::new(ToggleRegex), + _ => panic!("{:?} is not a named SearchOption", self), } } + + pub fn from_query(query: &SearchQuery) -> SearchOptions { + let mut options = SearchOptions::NONE; + options.set(SearchOptions::WHOLE_WORD, query.whole_word()); + options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive()); + options.set(SearchOptions::REGEX, query.is_regex()); + options + } } From 2ffce24ef09715e754e078f61f4cba6ffdc29f9a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 28 Jun 2023 11:47:14 -0600 Subject: [PATCH 003/162] vim: Don't enter visual mode in search/go to definition Fixes: zed-industries/community#1514 Contributes: zed-industries/community#1284 --- crates/editor/src/editor.rs | 15 +++++++++++++++ crates/editor/src/items.rs | 5 ++--- crates/vim/src/vim.rs | 4 ++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8d7b8ffad6d2804a59affb0935d06306695693e3..259037c3ff93de0212e794ee58397866cf6ae37f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -544,6 +544,7 @@ pub struct Editor { pending_rename: Option, searchable: bool, cursor_shape: CursorShape, + collapse_matches: bool, workspace: Option<(WeakViewHandle, i64)>, keymap_context_layers: BTreeMap, input_enabled: bool, @@ -1376,6 +1377,7 @@ impl Editor { searchable: true, override_text_style: None, cursor_shape: Default::default(), + collapse_matches: false, workspace: None, keymap_context_layers: Default::default(), input_enabled: true, @@ -1515,6 +1517,17 @@ impl Editor { cx.notify(); } + pub fn set_collapse_matches(&mut self, collapse_matches: bool) { + self.collapse_matches = collapse_matches; + } + + fn range_for_match(&self, range: &Range) -> Range { + if self.collapse_matches { + return range.start..range.start; + } + range.clone() + } + pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext) { if self.display_map.read(cx).clip_at_line_ends != clip { self.display_map @@ -6233,6 +6246,7 @@ impl Editor { .to_offset(definition.target.buffer.read(cx)); if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() { + let range = self.range_for_match(&range); self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); @@ -6245,6 +6259,7 @@ impl Editor { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. pane.update(cx, |pane, _| pane.disable_history()); + let range = target_editor.range_for_match(&range); target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 74b8e0ddb68d81f3d00fafa518cfbff4bc4c71b6..86f26bcf944dc9675efa253783f9638d9a9915a0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -936,9 +936,8 @@ impl SearchableItem for Editor { cx: &mut ViewContext, ) { self.unfold_ranges([matches[index].clone()], false, true, cx); - self.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select_ranges([matches[index].clone()]) - }); + let range = self.range_for_match(&matches[index]); + self.change_selections(Some(Autoscroll::fit()), cx, |s| s.select_ranges([range])); } fn match_index_for_direction( diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 2bcc2254eed1d75dd88ec446428c306cd1d677ba..ada8f2c1defdd5014d673239b9c3f57d5e8124d9 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -295,11 +295,15 @@ impl Vim { if self.enabled && editor.mode() == EditorMode::Full { editor.set_cursor_shape(cursor_shape, cx); editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); + editor.set_collapse_matches(true); editor.set_input_enabled(!state.vim_controlled()); editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true }); let context_layer = state.keymap_context_layer(); editor.set_keymap_context_layer::(context_layer, cx); } else { + // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur, + // but we need collapse_matches to persist when the search bar is focused. + editor.set_collapse_matches(false); Self::unhook_vim_settings(editor, cx); } }); From 96ce0bb78338d185bf0e2c223696f748e3c319a1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 28 Jun 2023 12:48:15 -0600 Subject: [PATCH 004/162] vim: Enter/n/N to navigate search results --- assets/keymaps/vim.json | 8 ++++++++ crates/vim/src/test.rs | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 84f9689c2cbb346e7b89d03dea1e0389c5ca6632..72aa7194080f20a1215867822f4cba90f6129523 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -60,6 +60,8 @@ "ignorePunctuation": true } ], + "n": "search::SelectNextMatch", + "shift-n": "search::SelectPrevMatch", "%": "vim::Matching", "f": [ "vim::PushOperator", @@ -335,5 +337,11 @@ "Normal" ] } + }, + { + "context": "BufferSearchBar", + "bindings": { + "enter": "buffer_search::FocusEditor" + } } ] diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 95962f85b892b1dfa376bb081dd5e1c22dd9d5fc..d9d24ec30eeb19577cadf743c7ba84db874210ff 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -5,6 +5,7 @@ mod vim_binding_test_context; mod vim_test_context; use command_palette::CommandPalette; +use editor::DisplayPoint; pub use neovim_backed_binding_test_context::*; pub use neovim_backed_test_context::*; pub use vim_binding_test_context::*; @@ -153,3 +154,44 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) { assert!(!cx.workspace(|workspace, _| workspace.modal::().is_some())); cx.assert_state("aˇbc\n", Mode::Insert); } + +#[gpui::test] +async fn test_selection_on_search(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal); + cx.simulate_keystrokes(["/", "c", "c"]); + + let search_bar = cx.workspace(|workspace, cx| { + workspace + .active_pane() + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .expect("Buffer search bar should be deployed") + }); + + search_bar.read_with(cx.cx, |bar, cx| { + assert_eq!(bar.query_editor.read(cx).text(cx), "cc"); + }); + + // wait for the query editor change event to fire. + search_bar.next_notification(&cx).await; + + cx.update_editor(|editor, cx| { + let highlights = editor.all_background_highlights(cx); + assert_eq!(3, highlights.len()); + assert_eq!( + DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2), + highlights[0].0 + ) + }); + cx.simulate_keystrokes(["enter"]); + + cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal); + cx.simulate_keystrokes(["n"]); + cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal); + cx.simulate_keystrokes(["shift-n"]); + cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal); +} From dbec2ed1f137dc36dfd359f548da2f10d768dfeb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 28 Jun 2023 13:13:02 -0600 Subject: [PATCH 005/162] vim: add */#/g*/g# for jumping to next word As in vim, this toggles the normal search experience. --- assets/keymaps/vim.json | 16 ++++- crates/search/src/buffer_search.rs | 28 +++++++- crates/vim/src/normal.rs | 4 ++ crates/vim/src/normal/search.rs | 108 +++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 crates/vim/src/normal/search.rs diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 72aa7194080f20a1215867822f4cba90f6129523..20b831b0e525e3abbf3d53352ba187ceccce3367 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -101,6 +101,8 @@ "vim::SwitchMode", "Normal" ], + "*": "vim::MoveToNext", + "#": "vim::MoveToPrev", "0": "vim::StartOfLine", // When no number operator present, use start of line motion "1": [ "vim::Number", @@ -240,7 +242,19 @@ "vim::SwitchMode", "Normal" ], - "d": "editor::GoToDefinition" + "d": "editor::GoToDefinition", + "*": [ + "vim::MoveToNext", + { + "partialWord": true + } + ], + "#": [ + "vim::MoveToPrev", + { + "partialWord": true + } + ] } }, { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 140f5accabe7180ad978c77006cc2f3f170602d7..fb4d5da7648dd387a4904b28ec95ab28c7fefdb3 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -65,6 +65,7 @@ pub struct BufferSearchBar { pub query_editor: ViewHandle, active_searchable_item: Option>, active_match_index: Option, + pending_match_direction: Option, active_searchable_item_subscription: Option, seachable_items_with_matches: HashMap, Vec>>, @@ -252,6 +253,7 @@ impl BufferSearchBar { default_options: SearchOptions::NONE, search_options: SearchOptions::NONE, pending_search: None, + pending_match_direction: None, query_contains_error: false, dismissed: true, } @@ -285,10 +287,10 @@ impl BufferSearchBar { &mut self, focus: bool, suggest_query: bool, - search_option: SearchOptions, + search_options: SearchOptions, cx: &mut ViewContext, ) -> bool { - self.search_options = search_option; + self.search_options = search_options; let searchable_item = if let Some(searchable_item) = &self.active_searchable_item { SearchableItemHandle::boxed_clone(searchable_item.as_ref()) } else { @@ -486,6 +488,17 @@ impl BufferSearchBar { self.select_match(Direction::Prev, cx); } + pub fn select_word_under_cursor( + &mut self, + direction: Direction, + options: SearchOptions, + cx: &mut ViewContext, + ) { + self.active_match_index = None; + self.pending_match_direction = Some(direction); + self.show_with_options(false, true, options, cx); + } + pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { if let Some(searchable_item) = self.active_searchable_item.as_ref() { @@ -567,6 +580,7 @@ impl BufferSearchBar { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if query.is_empty() { self.active_match_index.take(); + self.pending_match_direction.take(); active_searchable_item.clear_matches(cx); } else { let query = if self.search_options.contains(SearchOptions::REGEX) { @@ -614,7 +628,15 @@ impl BufferSearchBar { .unwrap(); active_searchable_item.update_matches(matches, cx); if select_closest_match { - if let Some(match_ix) = this.active_match_index { + if let Some(mut match_ix) = this.active_match_index { + if let Some(direction) = this.pending_match_direction.take() + { + match_ix += match direction { + Direction::Next => 1, + Direction::Prev => matches.len() - 1, + }; + match_ix = match_ix % matches.len(); + } active_searchable_item .activate_match(match_ix, matches, cx); } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 1227afbb85e4f8d78b5dab54d9b9f5410f588863..c382a08b5c03ea173cb6dd27a70ab2abe7301644 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -2,6 +2,7 @@ mod case; mod change; mod delete; mod scroll; +mod search; mod substitute; mod yank; @@ -27,6 +28,7 @@ use self::{ case::change_case, change::{change_motion, change_object}, delete::{delete_motion, delete_object}, + search::{move_to_next, move_to_prev}, substitute::substitute, yank::{yank_motion, yank_object}, }; @@ -57,6 +59,8 @@ pub fn init(cx: &mut AppContext) { cx.add_action(insert_line_above); cx.add_action(insert_line_below); cx.add_action(change_case); + cx.add_action(move_to_next); + cx.add_action(move_to_prev); cx.add_action(|_: &mut Workspace, _: &Substitute, cx| { Vim::update(cx, |vim, cx| { let times = vim.pop_number_operator(cx); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs new file mode 100644 index 0000000000000000000000000000000000000000..49eb97c9acb595d52e1aa8f962b5313d2cba1c70 --- /dev/null +++ b/crates/vim/src/normal/search.rs @@ -0,0 +1,108 @@ +use gpui::{impl_actions, ViewContext}; +use search::{BufferSearchBar, SearchOptions}; +use serde_derive::Deserialize; +use workspace::{searchable::Direction, Workspace}; + +use crate::Vim; + +#[derive(Clone, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub(crate) struct MoveToNext { + #[serde(default)] + partial_word: bool, +} + +#[derive(Clone, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub(crate) struct MoveToPrev { + #[serde(default)] + partial_word: bool, +} + +impl_actions!(vim, [MoveToNext, MoveToPrev]); + +pub(crate) fn move_to_next( + workspace: &mut Workspace, + action: &MoveToNext, + cx: &mut ViewContext, +) { + move_to_internal(workspace, Direction::Next, !action.partial_word, cx) +} + +pub(crate) fn move_to_prev( + workspace: &mut Workspace, + action: &MoveToPrev, + cx: &mut ViewContext, +) { + move_to_internal(workspace, Direction::Prev, !action.partial_word, cx) +} + +fn move_to_internal( + workspace: &mut Workspace, + direction: Direction, + whole_word: bool, + cx: &mut ViewContext, +) { + Vim::update(cx, |vim, cx| { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, cx| { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |search_bar, cx| { + let mut options = SearchOptions::CASE_SENSITIVE; + options.set(SearchOptions::WHOLE_WORD, whole_word); + search_bar.select_word_under_cursor(direction, options, cx); + }); + } + }); + vim.clear_operator(cx); + }); +} + +#[cfg(test)] +mod test { + use search::BufferSearchBar; + + use crate::{state::Mode, test::VimTestContext}; + + #[gpui::test] + async fn test_move_to_next(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + let search_bar = cx.workspace(|workspace, cx| { + workspace + .active_pane() + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .expect("Buffer search bar should be deployed") + }); + cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal); + + cx.simulate_keystrokes(["*"]); + search_bar.next_notification(&cx).await; + cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); + + cx.simulate_keystrokes(["*"]); + search_bar.next_notification(&cx).await; + cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); + + cx.simulate_keystrokes(["#"]); + search_bar.next_notification(&cx).await; + cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); + + cx.simulate_keystrokes(["#"]); + search_bar.next_notification(&cx).await; + cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); + + cx.simulate_keystrokes(["g", "*"]); + search_bar.next_notification(&cx).await; + cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); + + cx.simulate_keystrokes(["n"]); + cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); + + cx.simulate_keystrokes(["g", "#"]); + search_bar.next_notification(&cx).await; + cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); + } +} From d70f415e8ea81c40083623b976cec2120cee44c7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 28 Jun 2023 13:17:48 -0600 Subject: [PATCH 006/162] vim: add gD to go to type definition --- assets/keymaps/vim.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 20b831b0e525e3abbf3d53352ba187ceccce3367..b9497394ab99c5fcc3f43913a9aabfa863362233 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -243,6 +243,7 @@ "Normal" ], "d": "editor::GoToDefinition", + "shift-d": "editor::GoToTypeDefinition", "*": [ "vim::MoveToNext", { From 6cf13c62d11334dbe145c248d816a1831f08be87 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 6 Jul 2023 13:21:01 -0600 Subject: [PATCH 007/162] vim: ? to search backwards, and / to repeat search --- assets/keymaps/vim.json | 10 ++- crates/vim/src/normal.rs | 4 +- crates/vim/src/normal/search.rs | 110 +++++++++++++++++++++--- crates/vim/src/test.rs | 2 +- crates/vim/src/test/vim_test_context.rs | 1 + 5 files changed, 106 insertions(+), 21 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index b9497394ab99c5fcc3f43913a9aabfa863362233..4a215dcef3116d3609a3445c8f5be34ad9f2b7d9 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -197,10 +197,11 @@ "p": "vim::Paste", "u": "editor::Undo", "ctrl-r": "editor::Redo", - "/": [ - "buffer_search::Deploy", + "/": "vim::Search", + "?": [ + "vim::Search", { - "focus": true + "backwards": true, } ], "ctrl-f": "vim::PageDown", @@ -356,7 +357,8 @@ { "context": "BufferSearchBar", "bindings": { - "enter": "buffer_search::FocusEditor" + "enter": "buffer_search::FocusEditor", + "escape": "buffer_search::Dismiss" } } ] diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index c382a08b5c03ea173cb6dd27a70ab2abe7301644..8dcaa5008ef60f1e184ff2b251569d20a5b68943 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -28,7 +28,6 @@ use self::{ case::change_case, change::{change_motion, change_object}, delete::{delete_motion, delete_object}, - search::{move_to_next, move_to_prev}, substitute::substitute, yank::{yank_motion, yank_object}, }; @@ -59,8 +58,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(insert_line_above); cx.add_action(insert_line_below); cx.add_action(change_case); - cx.add_action(move_to_next); - cx.add_action(move_to_prev); + search::init(cx); cx.add_action(|_: &mut Workspace, _: &Substitute, cx| { Vim::update(cx, |vim, cx| { let times = vim.pop_number_operator(cx); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 49eb97c9acb595d52e1aa8f962b5313d2cba1c70..5f0ed9e7b989e954fa15a646fb8cdb6befc61843 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -1,4 +1,4 @@ -use gpui::{impl_actions, ViewContext}; +use gpui::{impl_actions, AppContext, ViewContext}; use search::{BufferSearchBar, SearchOptions}; use serde_derive::Deserialize; use workspace::{searchable::Direction, Workspace}; @@ -19,25 +19,47 @@ pub(crate) struct MoveToPrev { partial_word: bool, } -impl_actions!(vim, [MoveToNext, MoveToPrev]); +#[derive(Clone, Deserialize, PartialEq)] +pub(crate) struct Search { + #[serde(default)] + backwards: bool, +} -pub(crate) fn move_to_next( - workspace: &mut Workspace, - action: &MoveToNext, - cx: &mut ViewContext, -) { +impl_actions!(vim, [MoveToNext, MoveToPrev, Search]); + +pub(crate) fn init(cx: &mut AppContext) { + cx.add_action(move_to_next); + cx.add_action(move_to_prev); + cx.add_action(search); +} + +fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext) { move_to_internal(workspace, Direction::Next, !action.partial_word, cx) } -pub(crate) fn move_to_prev( - workspace: &mut Workspace, - action: &MoveToPrev, - cx: &mut ViewContext, -) { +fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext) { move_to_internal(workspace, Direction::Prev, !action.partial_word, cx) } -fn move_to_internal( +fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext) { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, cx| { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |search_bar, cx| { + let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX; + let direction = if action.backwards { + Direction::Prev + } else { + Direction::Next + }; + search_bar.select_match(direction, cx); + search_bar.show_with_options(true, false, options, cx); + }) + } + }) +} + +pub fn move_to_internal( workspace: &mut Workspace, direction: Direction, whole_word: bool, @@ -60,6 +82,7 @@ fn move_to_internal( #[cfg(test)] mod test { + use editor::DisplayPoint; use search::BufferSearchBar; use crate::{state::Mode, test::VimTestContext}; @@ -105,4 +128,65 @@ mod test { search_bar.next_notification(&cx).await; cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); } + + #[gpui::test] + async fn test_search(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); + cx.simulate_keystrokes(["/", "c", "c"]); + + let search_bar = cx.workspace(|workspace, cx| { + workspace + .active_pane() + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .expect("Buffer search bar should be deployed") + }); + + search_bar.read_with(cx.cx, |bar, cx| { + assert_eq!(bar.query_editor.read(cx).text(cx), "cc"); + }); + + // wait for the query editor change event to fire. + search_bar.next_notification(&cx).await; + + cx.update_editor(|editor, cx| { + let highlights = editor.all_background_highlights(cx); + assert_eq!(3, highlights.len()); + assert_eq!( + DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2), + highlights[0].0 + ) + }); + + cx.simulate_keystrokes(["enter"]); + + // n to go to next/N to go to previous + cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); + cx.simulate_keystrokes(["n"]); + cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal); + cx.simulate_keystrokes(["shift-n"]); + + // ? to go to previous + cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); + cx.simulate_keystrokes(["?", "enter"]); + cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal); + cx.simulate_keystrokes(["?", "enter"]); + + // / to go to next + cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal); + cx.simulate_keystrokes(["/", "enter"]); + cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal); + + // ?{search} to search backwards + cx.simulate_keystrokes(["?", "b", "enter"]); + + // wait for the query editor change event to fire. + search_bar.next_notification(&cx).await; + + cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); + } } diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index d9d24ec30eeb19577cadf743c7ba84db874210ff..8ed649e61bef510d8cf9fa76eb8d2edf3eca4e69 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -97,7 +97,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { }); search_bar.read_with(cx.cx, |bar, cx| { - assert_eq!(bar.query_editor.read(cx).text(cx), "jumps"); + assert_eq!(bar.query_editor.read(cx).text(cx), ""); }) } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index f9ba577231f5de6317f1194e14a6ad4cf139e06b..56ca654644c5277fc8eca5e8fcd769815a6a356c 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -90,6 +90,7 @@ impl<'a> VimTestContext<'a> { self.cx.set_state(text) } + #[track_caller] pub fn assert_state(&mut self, text: &str, mode: Mode) { self.assert_editor_state(text); assert_eq!(self.mode(), mode, "{}", self.assertion_context()); From dea728a7e58f746d5a1429894451286febd85d66 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 7 Jul 2023 09:10:11 -0600 Subject: [PATCH 008/162] Better waiting in tests --- crates/vim/src/normal/search.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 5f0ed9e7b989e954fa15a646fb8cdb6befc61843..bb57045add719ff42a834fbdcde513ab000b21ed 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -82,13 +82,18 @@ pub fn move_to_internal( #[cfg(test)] mod test { + use std::sync::Arc; + use editor::DisplayPoint; use search::BufferSearchBar; use crate::{state::Mode, test::VimTestContext}; #[gpui::test] - async fn test_move_to_next(cx: &mut gpui::TestAppContext) { + async fn test_move_to_next( + cx: &mut gpui::TestAppContext, + deterministic: Arc, + ) { let mut cx = VimTestContext::new(cx, true).await; let search_bar = cx.workspace(|workspace, cx| { workspace @@ -102,30 +107,30 @@ mod test { cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal); cx.simulate_keystrokes(["*"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); cx.simulate_keystrokes(["*"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); cx.simulate_keystrokes(["#"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); cx.simulate_keystrokes(["#"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); cx.simulate_keystrokes(["g", "*"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); cx.simulate_keystrokes(["n"]); cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal); cx.simulate_keystrokes(["g", "#"]); - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); } From 232d14a3ae6a86f027edcf907fbf74f3bd44e8e1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 7 Jul 2023 10:55:32 -0600 Subject: [PATCH 009/162] Make search less magic Co-Authored-By: Antonio --- crates/ai/src/assistant.rs | 18 ++- crates/search/src/buffer_search.rs | 189 ++++++++++++++++------------- crates/vim/src/normal/search.rs | 13 +- 3 files changed, 129 insertions(+), 91 deletions(-) diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 4d300230e16109558ac7f2d8850132912c76f67e..3cc97468c32f177e037af618da98da14d7e7f32e 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -298,12 +298,22 @@ impl AssistantPanel { } fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext) { + let mut propagate_action = true; if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() { - if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) { - return; - } + search_bar.update(cx, |search_bar, cx| { + if search_bar.show(cx) { + search_bar.search_suggested(cx); + if action.focus { + search_bar.select_query(cx); + cx.focus_self(); + } + propagate_action = false + } + }); + } + if propagate_action { + cx.propagate_action(); } - cx.propagate_action(); } fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext) { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index fb4d5da7648dd387a4904b28ec95ab28c7fefdb3..c8d1c58b6f265e0a15201fc1033b61eea58b5de6 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -4,12 +4,14 @@ use crate::{ }; use collections::HashMap; use editor::Editor; +use futures::channel::oneshot; use gpui::{ actions, elements::*, impl_actions, platform::{CursorStyle, MouseButton}, Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle, + WindowContext, }; use project::search::SearchQuery; use serde::Deserialize; @@ -50,12 +52,11 @@ pub fn init(cx: &mut AppContext) { fn add_toggle_option_action(option: SearchOptions, cx: &mut AppContext) { cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - if search_bar.update(cx, |search_bar, cx| search_bar.show(false, false, cx)) { - search_bar.update(cx, |search_bar, cx| { + search_bar.update(cx, |search_bar, cx| { + if search_bar.show(cx) { search_bar.toggle_search_option(option, cx); - }); - return; - } + } + }); } cx.propagate_action(); }); @@ -209,7 +210,7 @@ impl ToolbarItemView for BufferSearchBar { )); self.active_searchable_item = Some(searchable_item_handle); - self.update_matches(false, cx); + let _ = self.update_matches(cx); if !self.dismissed { return ToolbarItemLocation::Secondary; } @@ -279,54 +280,75 @@ impl BufferSearchBar { cx.notify(); } - pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext) -> bool { - self.show_with_options(focus, suggest_query, self.default_options, cx) - } - - pub fn show_with_options( - &mut self, - focus: bool, - suggest_query: bool, - search_options: SearchOptions, - cx: &mut ViewContext, - ) -> bool { - self.search_options = search_options; + pub fn show(&mut self, cx: &mut ViewContext) -> bool { let searchable_item = if let Some(searchable_item) = &self.active_searchable_item { SearchableItemHandle::boxed_clone(searchable_item.as_ref()) } else { return false; }; - if suggest_query { - let text = searchable_item.query_suggestion(cx); - if !text.is_empty() { - self.set_query(&text, cx); - } - } - - if focus { - let query_editor = self.query_editor.clone(); - query_editor.update(cx, |query_editor, cx| { - query_editor.select_all(&editor::SelectAll, cx); - }); - cx.focus_self(); - } - self.dismissed = false; cx.notify(); cx.emit(Event::UpdateLocation); true } - fn set_query(&mut self, query: &str, cx: &mut ViewContext) { + pub fn search_suggested(&mut self, cx: &mut ViewContext) { + let search = self + .query_suggestion(cx) + .map(|suggestion| self.search(&suggestion, self.default_options, cx)); + + if let Some(search) = search { + cx.spawn(|this, mut cx| async move { + search.await?; + this.update(&mut cx, |this, cx| { + if let Some(match_ix) = this.active_match_index { + if let Some(active_searchable_item) = this.active_searchable_item.as_ref() { + if let Some(matches) = this + .seachable_items_with_matches + .get(&active_searchable_item.downgrade()) + { + active_searchable_item.activate_match(match_ix, matches, cx); + } + } + } + }) + }) + .detach_and_log_err(cx); + } + } + + pub fn select_query(&mut self, cx: &mut ViewContext) { self.query_editor.update(cx, |query_editor, cx| { - query_editor.buffer().update(cx, |query_buffer, cx| { - let len = query_buffer.len(cx); - query_buffer.edit([(0..len, query)], None, cx); - }); + query_editor.select_all(&Default::default(), cx); }); } + pub fn query_suggestion(&self, cx: &mut ViewContext) -> Option { + Some(self.active_searchable_item.as_ref()?.query_suggestion(cx)) + } + + fn search( + &mut self, + query: &str, + options: SearchOptions, + cx: &mut ViewContext, + ) -> oneshot::Receiver<()> { + if query != self.query_editor.read(cx).text(cx) || self.search_options != options { + self.query_editor.update(cx, |query_editor, cx| { + query_editor.buffer().update(cx, |query_buffer, cx| { + let len = query_buffer.len(cx); + query_buffer.edit([(0..len, query)], None, cx); + }); + }); + self.search_options = options; + self.query_contains_error = false; + self.clear_matches(cx); + cx.notify(); + } + self.update_matches(cx) + } + fn render_search_option( &self, option_supported: bool, @@ -448,12 +470,23 @@ impl BufferSearchBar { } fn deploy(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext) { + let mut propagate_action = true; if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) { - return; - } + search_bar.update(cx, |search_bar, cx| { + if search_bar.show(cx) { + search_bar.search_suggested(cx); + if action.focus { + search_bar.select_query(cx); + cx.focus_self(); + } + propagate_action = false; + } + }); + } + + if propagate_action { + cx.propagate_action(); } - cx.propagate_action(); } fn handle_editor_cancel(pane: &mut Pane, _: &editor::Cancel, cx: &mut ViewContext) { @@ -475,8 +508,7 @@ impl BufferSearchBar { fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext) { self.search_options.toggle(search_option); self.default_options = self.search_options; - - self.update_matches(false, cx); + let _ = self.update_matches(cx); cx.notify(); } @@ -488,17 +520,6 @@ impl BufferSearchBar { self.select_match(Direction::Prev, cx); } - pub fn select_word_under_cursor( - &mut self, - direction: Direction, - options: SearchOptions, - cx: &mut ViewContext, - ) { - self.active_match_index = None; - self.pending_match_direction = Some(direction); - self.show_with_options(false, true, options, cx); - } - pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { if let Some(searchable_item) = self.active_searchable_item.as_ref() { @@ -541,17 +562,26 @@ impl BufferSearchBar { event: &editor::Event, cx: &mut ViewContext, ) { - if let editor::Event::BufferEdited { .. } = event { + if let editor::Event::Edited { .. } = event { + let query = self.query_editor.read(cx).text(cx); + let search = self.search(&query, self.search_options, cx); self.query_contains_error = false; self.clear_matches(cx); - self.update_matches(true, cx); - cx.notify(); + let search = self.update_matches(cx); + cx.spawn(|this, mut cx| async move { + search.await?; + this.update(&mut cx, |this, cx| this.select_match(Direction::Next, cx))?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } fn on_active_searchable_item_event(&mut self, event: SearchEvent, cx: &mut ViewContext) { match event { - SearchEvent::MatchesInvalidated => self.update_matches(false, cx), + SearchEvent::MatchesInvalidated => { + let _ = self.update_matches(cx); + } SearchEvent::ActiveMatchChanged => self.update_match_index(cx), } } @@ -574,7 +604,8 @@ impl BufferSearchBar { .extend(active_item_matches); } - fn update_matches(&mut self, select_closest_match: bool, cx: &mut ViewContext) { + fn update_matches(&mut self, cx: &mut ViewContext) -> oneshot::Receiver<()> { + let (done_tx, done_rx) = oneshot::channel(); let query = self.query_editor.read(cx).text(cx); self.pending_search.take(); if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { @@ -582,6 +613,7 @@ impl BufferSearchBar { self.active_match_index.take(); self.pending_match_direction.take(); active_searchable_item.clear_matches(cx); + let _ = done_tx.send(()); } else { let query = if self.search_options.contains(SearchOptions::REGEX) { match SearchQuery::regex( @@ -595,7 +627,7 @@ impl BufferSearchBar { Err(_) => { self.query_contains_error = true; cx.notify(); - return; + return done_rx; } } } else { @@ -627,20 +659,7 @@ impl BufferSearchBar { .get(&active_searchable_item.downgrade()) .unwrap(); active_searchable_item.update_matches(matches, cx); - if select_closest_match { - if let Some(mut match_ix) = this.active_match_index { - if let Some(direction) = this.pending_match_direction.take() - { - match_ix += match direction { - Direction::Next => 1, - Direction::Prev => matches.len() - 1, - }; - match_ix = match_ix % matches.len(); - } - active_searchable_item - .activate_match(match_ix, matches, cx); - } - } + let _ = done_tx.send(()); } cx.notify(); } @@ -649,6 +668,7 @@ impl BufferSearchBar { })); } } + done_rx } fn update_match_index(&mut self, cx: &mut ViewContext) { @@ -699,7 +719,7 @@ mod tests { let search_bar = cx.add_view(window_id, |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); - search_bar.show(false, true, cx); + search_bar.show(cx); search_bar }); @@ -712,10 +732,11 @@ mod tests { // Search for a string that appears with different casing. // By default, search is case-insensitive. - search_bar.update(cx, |search_bar, cx| { - search_bar.set_query("us", cx); - }); - editor.next_notification(cx).await; + search_bar + .update(cx, |search_bar, cx| { + search_bar.search("us", search_bar.default_options, cx) + }) + .await; editor.update(cx, |editor, cx| { assert_eq!( editor.all_background_highlights(cx), @@ -750,7 +771,7 @@ mod tests { // Search for a string that appears both as a whole word and // within other words. By default, all results are found. search_bar.update(cx, |search_bar, cx| { - search_bar.set_query("or", cx); + search_bar.search("or", search_bar.default_options, cx); }); editor.next_notification(cx).await; editor.update(cx, |editor, cx| { @@ -993,6 +1014,7 @@ mod tests { }); } + /* #[gpui::test] async fn test_search_with_options(cx: &mut TestAppContext) { let (editor, search_bar) = init_test(cx); @@ -1000,7 +1022,7 @@ mod tests { // show with options should make current search case sensitive search_bar.update(cx, |search_bar, cx| { search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx); - search_bar.set_query("us", cx); + search_bar.search("us", cx); }); editor.next_notification(cx).await; editor.update(cx, |editor, cx| { @@ -1036,7 +1058,7 @@ mod tests { // toggling a search option (even in show_with_options mode) should update the defaults search_bar.update(cx, |search_bar, cx| { - search_bar.set_query("regex", cx); + search_bar.search("regex", cx); search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx); search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx) }); @@ -1087,4 +1109,5 @@ mod tests { ); }); } + */ } diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index bb57045add719ff42a834fbdcde513ab000b21ed..f23de53c37be20e93b6d90e7344cd3ff2a6ee8db 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -53,7 +53,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext() { search_bar.update(cx, |search_bar, cx| { - let mut options = SearchOptions::CASE_SENSITIVE; - options.set(SearchOptions::WHOLE_WORD, whole_word); - search_bar.select_word_under_cursor(direction, options, cx); + // let mut options = SearchOptions::CASE_SENSITIVE; + // options.set(SearchOptions::WHOLE_WORD, whole_word); + // search_bar.show(false, false, cx); + // let word = search_bar.query_suggestion(); + // search_bar.show() + // search_bar.search(word, options) + + // search_bar.select_word_under_cursor(direction, options, cx); }); } }); From b4b0f622de531a58a6fa6b1f8c80743105e6ec32 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 7 Jul 2023 11:33:15 -0600 Subject: [PATCH 010/162] Rebuild vim search experience on refactored code --- assets/keymaps/vim.json | 2 +- crates/ai/src/assistant.rs | 4 +- crates/editor/src/items.rs | 22 ++-- crates/search/src/buffer_search.rs | 99 +++++++++------- crates/search/src/project_search.rs | 2 +- crates/vim/src/normal/search.rs | 170 +++++++++++++++++++++------- crates/vim/src/state.rs | 18 +++ crates/workspace/src/searchable.rs | 23 ++-- 8 files changed, 225 insertions(+), 115 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 4a215dcef3116d3609a3445c8f5be34ad9f2b7d9..40ebe13558b22991c511840ba02d4c99e8bbb855 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -357,7 +357,7 @@ { "context": "BufferSearchBar", "bindings": { - "enter": "buffer_search::FocusEditor", + "enter": "vim::SearchSubmit", "escape": "buffer_search::Dismiss" } } diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 3cc97468c32f177e037af618da98da14d7e7f32e..4ca771ebcb8027f2ba5aa3aa9cca24fe160a64e6 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -330,13 +330,13 @@ impl AssistantPanel { fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext) { if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() { - search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx)); + search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, None, cx)); } } fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext) { if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() { - search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx)); + search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, None, cx)); } } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 86f26bcf944dc9675efa253783f9638d9a9915a0..6b2cdacaa2746964266a2c1f8721188ce719b51a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -937,7 +937,9 @@ impl SearchableItem for Editor { ) { self.unfold_ranges([matches[index].clone()], false, true, cx); let range = self.range_for_match(&matches[index]); - self.change_selections(Some(Autoscroll::fit()), cx, |s| s.select_ranges([range])); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }) } fn match_index_for_direction( @@ -945,11 +947,12 @@ impl SearchableItem for Editor { matches: &Vec>, mut current_index: usize, direction: Direction, + count: Option, cx: &mut ViewContext, ) -> usize { let buffer = self.buffer().read(cx).snapshot(cx); let cursor = self.selections.newest_anchor().head(); - if matches[current_index].start.cmp(&cursor, &buffer).is_gt() { + if count.is_none() && matches[current_index].start.cmp(&cursor, &buffer).is_gt() { if direction == Direction::Prev { if current_index == 0 { current_index = matches.len() - 1; @@ -957,22 +960,19 @@ impl SearchableItem for Editor { current_index -= 1; } } - } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() { + } else if count.is_none() && matches[current_index].end.cmp(&cursor, &buffer).is_lt() { if direction == Direction::Next { current_index = 0; } } else if direction == Direction::Prev { - if current_index == 0 { - current_index = matches.len() - 1; + let count = count.unwrap_or(1) % matches.len(); + if current_index >= count { + current_index = current_index - count; } else { - current_index -= 1; + current_index = matches.len() - (count - current_index); } } else if direction == Direction::Next { - if current_index == matches.len() - 1 { - current_index = 0 - } else { - current_index += 1; - } + current_index = (current_index + count.unwrap_or(1)) % matches.len() }; current_index } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c8d1c58b6f265e0a15201fc1033b61eea58b5de6..2bd765f8bbd780b3c5177252ad1dbe5862a055c5 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -66,7 +66,6 @@ pub struct BufferSearchBar { pub query_editor: ViewHandle, active_searchable_item: Option>, active_match_index: Option, - pending_match_direction: Option, active_searchable_item_subscription: Option, seachable_items_with_matches: HashMap, Vec>>, @@ -254,7 +253,6 @@ impl BufferSearchBar { default_options: SearchOptions::NONE, search_options: SearchOptions::NONE, pending_search: None, - pending_match_direction: None, query_contains_error: false, dismissed: true, } @@ -281,12 +279,9 @@ impl BufferSearchBar { } pub fn show(&mut self, cx: &mut ViewContext) -> bool { - let searchable_item = if let Some(searchable_item) = &self.active_searchable_item { - SearchableItemHandle::boxed_clone(searchable_item.as_ref()) - } else { + if self.active_searchable_item.is_none() { return false; - }; - + } self.dismissed = false; cx.notify(); cx.emit(Event::UpdateLocation); @@ -296,44 +291,53 @@ impl BufferSearchBar { pub fn search_suggested(&mut self, cx: &mut ViewContext) { let search = self .query_suggestion(cx) - .map(|suggestion| self.search(&suggestion, self.default_options, cx)); + .map(|suggestion| self.search(&suggestion, Some(self.default_options), cx)); if let Some(search) = search { cx.spawn(|this, mut cx| async move { search.await?; - this.update(&mut cx, |this, cx| { - if let Some(match_ix) = this.active_match_index { - if let Some(active_searchable_item) = this.active_searchable_item.as_ref() { - if let Some(matches) = this - .seachable_items_with_matches - .get(&active_searchable_item.downgrade()) - { - active_searchable_item.activate_match(match_ix, matches, cx); - } - } - } - }) + this.update(&mut cx, |this, cx| this.activate_current_match(cx)) }) .detach_and_log_err(cx); } } + pub fn activate_current_match(&mut self, cx: &mut ViewContext) { + if let Some(match_ix) = self.active_match_index { + if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { + if let Some(matches) = self + .seachable_items_with_matches + .get(&active_searchable_item.downgrade()) + { + active_searchable_item.activate_match(match_ix, matches, cx) + } + } + } + } + pub fn select_query(&mut self, cx: &mut ViewContext) { self.query_editor.update(cx, |query_editor, cx| { query_editor.select_all(&Default::default(), cx); }); } - pub fn query_suggestion(&self, cx: &mut ViewContext) -> Option { - Some(self.active_searchable_item.as_ref()?.query_suggestion(cx)) + pub fn query(&self, cx: &WindowContext) -> String { + self.query_editor.read(cx).text(cx) } - fn search( + pub fn query_suggestion(&mut self, cx: &mut ViewContext) -> Option { + self.active_searchable_item + .as_ref() + .map(|searchable_item| searchable_item.query_suggestion(cx)) + } + + pub fn search( &mut self, query: &str, - options: SearchOptions, + options: Option, cx: &mut ViewContext, ) -> oneshot::Receiver<()> { + let options = options.unwrap_or(self.default_options); if query != self.query_editor.read(cx).text(cx) || self.search_options != options { self.query_editor.update(cx, |query_editor, cx| { query_editor.buffer().update(cx, |query_buffer, cx| { @@ -499,7 +503,7 @@ impl BufferSearchBar { cx.propagate_action(); } - fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext) { + pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext) { if let Some(active_editor) = self.active_searchable_item.as_ref() { cx.focus(active_editor.as_any()); } @@ -512,23 +516,37 @@ impl BufferSearchBar { cx.notify(); } + pub fn set_search_options( + &mut self, + search_options: SearchOptions, + cx: &mut ViewContext, + ) { + self.search_options = search_options; + cx.notify(); + } + fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext) { - self.select_match(Direction::Next, cx); + self.select_match(Direction::Next, None, cx); } fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext) { - self.select_match(Direction::Prev, cx); + self.select_match(Direction::Prev, None, cx); } - pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { + pub fn select_match( + &mut self, + direction: Direction, + count: Option, + cx: &mut ViewContext, + ) { if let Some(index) = self.active_match_index { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self .seachable_items_with_matches .get(&searchable_item.downgrade()) { - let new_match_index = - searchable_item.match_index_for_direction(matches, index, direction, cx); + let new_match_index = searchable_item + .match_index_for_direction(matches, index, direction, count, cx); searchable_item.update_matches(matches, cx); searchable_item.activate_match(new_match_index, matches, cx); } @@ -563,15 +581,12 @@ impl BufferSearchBar { cx: &mut ViewContext, ) { if let editor::Event::Edited { .. } = event { - let query = self.query_editor.read(cx).text(cx); - let search = self.search(&query, self.search_options, cx); self.query_contains_error = false; self.clear_matches(cx); let search = self.update_matches(cx); cx.spawn(|this, mut cx| async move { search.await?; - this.update(&mut cx, |this, cx| this.select_match(Direction::Next, cx))?; - anyhow::Ok(()) + this.update(&mut cx, |this, cx| this.activate_current_match(cx)) }) .detach_and_log_err(cx); } @@ -611,7 +626,6 @@ impl BufferSearchBar { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if query.is_empty() { self.active_match_index.take(); - self.pending_match_direction.take(); active_searchable_item.clear_matches(cx); let _ = done_tx.send(()); } else { @@ -733,10 +747,9 @@ mod tests { // Search for a string that appears with different casing. // By default, search is case-insensitive. search_bar - .update(cx, |search_bar, cx| { - search_bar.search("us", search_bar.default_options, cx) - }) - .await; + .update(cx, |search_bar, cx| search_bar.search("us", None, cx)) + .await + .unwrap(); editor.update(cx, |editor, cx| { assert_eq!( editor.all_background_highlights(cx), @@ -770,10 +783,10 @@ mod tests { // Search for a string that appears both as a whole word and // within other words. By default, all results are found. - search_bar.update(cx, |search_bar, cx| { - search_bar.search("or", search_bar.default_options, cx); - }); - editor.next_notification(cx).await; + search_bar + .update(cx, |search_bar, cx| search_bar.search("or", None, cx)) + .await + .unwrap(); editor.update(cx, |editor, cx| { assert_eq!( editor.all_background_highlights(cx), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 4e485eaaabb3d03906e62a48804468303cc832fd..76350f1812fd1b574ef13544739dd9c7c5022063 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -627,7 +627,7 @@ impl ProjectSearchView { if let Some(index) = self.active_match_index { let match_ranges = self.model.read(cx).match_ranges.clone(); let new_index = self.results_editor.update(cx, |editor, cx| { - editor.match_index_for_direction(&match_ranges, index, direction, cx) + editor.match_index_for_direction(&match_ranges, index, direction, None, cx) }); let range_to_select = match_ranges[new_index].clone(); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index f23de53c37be20e93b6d90e7344cd3ff2a6ee8db..70e397bcb0b88081fdbb1c86f2b881302af27f90 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -1,9 +1,9 @@ -use gpui::{impl_actions, AppContext, ViewContext}; -use search::{BufferSearchBar, SearchOptions}; +use gpui::{actions, impl_actions, AppContext, ViewContext}; +use search::{buffer_search, BufferSearchBar, SearchOptions}; use serde_derive::Deserialize; -use workspace::{searchable::Direction, Workspace}; +use workspace::{searchable::Direction, Pane, Workspace}; -use crate::Vim; +use crate::{state::SearchState, Vim}; #[derive(Clone, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -26,11 +26,14 @@ pub(crate) struct Search { } impl_actions!(vim, [MoveToNext, MoveToPrev, Search]); +actions!(vim, [SearchSubmit]); pub(crate) fn init(cx: &mut AppContext) { cx.add_action(move_to_next); cx.add_action(move_to_prev); cx.add_action(search); + cx.add_action(search_submit); + cx.add_action(search_deploy); } fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext) { @@ -43,19 +46,68 @@ fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewCon fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext) { let pane = workspace.active_pane().clone(); - pane.update(cx, |pane, cx| { - if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - search_bar.update(cx, |search_bar, cx| { - let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX; - let direction = if action.backwards { - Direction::Prev - } else { - Direction::Next - }; - search_bar.select_match(direction, cx); - // search_bar.show_with_options(true, false, options, cx); - }) - } + let direction = if action.backwards { + Direction::Prev + } else { + Direction::Next + }; + Vim::update(cx, |vim, cx| { + let count = vim.pop_number_operator(cx).unwrap_or(1); + pane.update(cx, |pane, cx| { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |search_bar, cx| { + if !search_bar.show(cx) { + return; + } + let query = search_bar.query(cx); + + search_bar.select_query(cx); + cx.focus_self(); + + if query.is_empty() { + search_bar.set_search_options( + SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX, + cx, + ); + } + vim.state.search = SearchState { + direction, + count, + initial_query: query, + }; + }); + } + }) + }) +} + +fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext) { + Vim::update(cx, |vim, _| vim.state.search = Default::default()); + cx.propagate_action(); +} + +fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext) { + Vim::update(cx, |vim, cx| { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, cx| { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |search_bar, cx| { + let mut state = &mut vim.state.search; + let mut count = state.count; + + // in the case that the query has changed, the search bar + // will have selected the next match already. + if (search_bar.query(cx) != state.initial_query) + && state.direction == Direction::Next + { + count = count.saturating_sub(1); + } + search_bar.select_match(state.direction, Some(count), cx); + state.count = 1; + search_bar.focus_editor(&Default::default(), cx); + }); + } + }); }) } @@ -67,18 +119,32 @@ pub fn move_to_internal( ) { Vim::update(cx, |vim, cx| { let pane = workspace.active_pane().clone(); + let count = vim.pop_number_operator(cx).unwrap_or(1); pane.update(cx, |pane, cx| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - search_bar.update(cx, |search_bar, cx| { - // let mut options = SearchOptions::CASE_SENSITIVE; - // options.set(SearchOptions::WHOLE_WORD, whole_word); - // search_bar.show(false, false, cx); - // let word = search_bar.query_suggestion(); - // search_bar.show() - // search_bar.search(word, options) - - // search_bar.select_word_under_cursor(direction, options, cx); + let search = search_bar.update(cx, |search_bar, cx| { + let mut options = SearchOptions::CASE_SENSITIVE; + options.set(SearchOptions::WHOLE_WORD, whole_word); + if search_bar.show(cx) { + search_bar + .query_suggestion(cx) + .map(|query| search_bar.search(&query, Some(options), cx)) + } else { + None + } }); + + if let Some(search) = search { + let search_bar = search_bar.downgrade(); + cx.spawn(|_, mut cx| async move { + search.await?; + search_bar.update(&mut cx, |search_bar, cx| { + search_bar.select_match(direction, Some(count), cx) + })?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } }); vim.clear_operator(cx); @@ -100,15 +166,6 @@ mod test { deterministic: Arc, ) { let mut cx = VimTestContext::new(cx, true).await; - let search_bar = cx.workspace(|workspace, cx| { - workspace - .active_pane() - .read(cx) - .toolbar() - .read(cx) - .item_of_type::() - .expect("Buffer search bar should be deployed") - }); cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal); cx.simulate_keystrokes(["*"]); @@ -127,6 +184,10 @@ mod test { deterministic.run_until_parked(); cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); + cx.simulate_keystrokes(["2", "*"]); + deterministic.run_until_parked(); + cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal); + cx.simulate_keystrokes(["g", "*"]); deterministic.run_until_parked(); cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); @@ -140,7 +201,10 @@ mod test { } #[gpui::test] - async fn test_search(cx: &mut gpui::TestAppContext) { + async fn test_search( + cx: &mut gpui::TestAppContext, + deterministic: Arc, + ) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); @@ -160,8 +224,7 @@ mod test { assert_eq!(bar.query_editor.read(cx).text(cx), "cc"); }); - // wait for the query editor change event to fire. - search_bar.next_notification(&cx).await; + deterministic.run_until_parked(); cx.update_editor(|editor, cx| { let highlights = editor.all_background_highlights(cx); @@ -173,30 +236,49 @@ mod test { }); cx.simulate_keystrokes(["enter"]); + cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); // n to go to next/N to go to previous - cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); cx.simulate_keystrokes(["n"]); cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal); cx.simulate_keystrokes(["shift-n"]); + cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); // ? to go to previous - cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal); cx.simulate_keystrokes(["?", "enter"]); + deterministic.run_until_parked(); cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal); cx.simulate_keystrokes(["?", "enter"]); + deterministic.run_until_parked(); + cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal); // / to go to next - cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal); cx.simulate_keystrokes(["/", "enter"]); + deterministic.run_until_parked(); cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal); // ?{search} to search backwards cx.simulate_keystrokes(["?", "b", "enter"]); + deterministic.run_until_parked(); + cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); - // wait for the query editor change event to fire. - search_bar.next_notification(&cx).await; + // works with counts + cx.simulate_keystrokes(["4", "/", "c"]); + deterministic.run_until_parked(); + cx.simulate_keystrokes(["enter"]); + cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal); - cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); + // check that searching resumes from cursor, not previous match + cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal); + cx.simulate_keystrokes(["/", "d"]); + deterministic.run_until_parked(); + cx.simulate_keystrokes(["enter"]); + cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal); + cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx)); + cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal); + cx.simulate_keystrokes(["/", "b"]); + deterministic.run_until_parked(); + cx.simulate_keystrokes(["enter"]); + cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal); } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index e1a06fce59d6ff5f9529552e422fbd0643418de1..6434b710b2a37f75bc0687069bec9b69431b6405 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -1,6 +1,7 @@ use gpui::keymap_matcher::KeymapContext; use language::CursorShape; use serde::{Deserialize, Serialize}; +use workspace::searchable::Direction; #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum Mode { @@ -38,6 +39,23 @@ pub enum Operator { pub struct VimState { pub mode: Mode, pub operator_stack: Vec, + pub search: SearchState, +} + +pub struct SearchState { + pub direction: Direction, + pub count: usize, + pub initial_query: String, +} + +impl Default for SearchState { + fn default() -> Self { + Self { + direction: Direction::Next, + count: 1, + initial_query: "".to_string(), + } + } } impl VimState { diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 7e3f7227b022f72bec64af3c765ab2b79cdafb49..bdbed072b0062dcadec26462de460a8ff258bf06 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -50,26 +50,21 @@ pub trait SearchableItem: Item { fn match_index_for_direction( &mut self, matches: &Vec, - mut current_index: usize, + current_index: usize, direction: Direction, + count: Option, _: &mut ViewContext, ) -> usize { match direction { Direction::Prev => { - if current_index == 0 { - matches.len() - 1 - } else { - current_index - 1 - } - } - Direction::Next => { - current_index += 1; - if current_index == matches.len() { - 0 + let count = count.unwrap_or(1) % matches.len(); + if current_index >= count { + current_index - count } else { - current_index + matches.len() - (count - current_index) } } + Direction::Next => (current_index + count.unwrap_or(1)) % matches.len(), } } fn find_matches( @@ -107,6 +102,7 @@ pub trait SearchableItemHandle: ItemHandle { matches: &Vec>, current_index: usize, direction: Direction, + count: Option, cx: &mut WindowContext, ) -> usize; fn find_matches( @@ -170,11 +166,12 @@ impl SearchableItemHandle for ViewHandle { matches: &Vec>, current_index: usize, direction: Direction, + count: Option, cx: &mut WindowContext, ) -> usize { let matches = downcast_matches(matches); self.update(cx, |this, cx| { - this.match_index_for_direction(&matches, current_index, direction, cx) + this.match_index_for_direction(&matches, current_index, direction, count, cx) }) } fn find_matches( From a1fe5abeaf500b1e4fb484f0733fdbb9114d68e8 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:31:20 +0200 Subject: [PATCH 011/162] Add rudimentary PHP syntax highlighting --- Cargo.lock | 10 ++ crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/php/config.toml | 11 ++ crates/zed/src/languages/php/highlights.scm | 122 ++++++++++++++++++++ crates/zed/src/languages/php/injections.scm | 3 + crates/zed/src/languages/php/outline.scm | 8 ++ crates/zed/src/languages/php/tags.scm | 40 +++++++ crates/zed/src/languages/python/outline.scm | 2 +- 9 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 crates/zed/src/languages/php/config.toml create mode 100644 crates/zed/src/languages/php/highlights.scm create mode 100644 crates/zed/src/languages/php/injections.scm create mode 100644 crates/zed/src/languages/php/outline.scm create mode 100644 crates/zed/src/languages/php/tags.scm diff --git a/Cargo.lock b/Cargo.lock index 60ed830683a5f91d0c8f4806b5d6758b2afbc31e..9806c86c96e00f3a05e7e038a24d3a2aec721897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8004,6 +8004,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-php" +version = "0.19.1" +source = "git+https://github.com/tree-sitter/tree-sitter-php?rev=d38adb26304d9b9d38e9a3b4aae0ec4b29bf9462#d38adb26304d9b9d38e9a3b4aae0ec4b29bf9462" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-python" version = "0.20.2" @@ -9432,6 +9441,7 @@ dependencies = [ "tree-sitter-json 0.20.0", "tree-sitter-lua", "tree-sitter-markdown", + "tree-sitter-php", "tree-sitter-python", "tree-sitter-racket", "tree-sitter-ruby", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d016525af40a9e247517dc1aebd64fa2c99c703c..0e92b2e3ea58b03fd2fe2e0e39795bff5edffb25 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -114,6 +114,7 @@ tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", re tree-sitter-rust = "0.20.3" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-python = "0.20.2" +tree-sitter-php = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "d38adb26304d9b9d38e9a3b4aae0ec4b29bf9462" } tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } tree-sitter-ruby = "0.20.0" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 44e144e89b37695e792b0b1f43fdf88f7e2b674d..81edd92d374b56e5d656a649cb3da96459b33e0f 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -137,6 +137,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { tree_sitter_yaml::language(), vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime))], ); + language("php", tree_sitter_php::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/php/config.toml b/crates/zed/src/languages/php/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..e9de52745a153b63df0ed99c260d64b9aea65aab --- /dev/null +++ b/crates/zed/src/languages/php/config.toml @@ -0,0 +1,11 @@ +name = "PHP" +path_suffixes = ["php"] +first_line_pattern = '^#!.*php' +line_comment = "// " +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, +] diff --git a/crates/zed/src/languages/php/highlights.scm b/crates/zed/src/languages/php/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..666a49be2a4795bfc923695417fdecf9a5100740 --- /dev/null +++ b/crates/zed/src/languages/php/highlights.scm @@ -0,0 +1,122 @@ +(php_tag) @tag +"?>" @tag + +; Types + +(primitive_type) @type.builtin +(cast_type) @type.builtin +(named_type (name) @type) @type +(named_type (qualified_name) @type) @type + +; Functions + +(array_creation_expression "array" @function.builtin) +(list_literal "list" @function.builtin) + +(method_declaration + name: (name) @function.method) + +(function_call_expression + function: [(qualified_name (name)) (name)] @function) + +(scoped_call_expression + name: (name) @function) + +(member_call_expression + name: (name) @function.method) + +(function_definition + name: (name) @function) + +; Member + +(property_element + (variable_name) @property) + +(member_access_expression + name: (variable_name (name)) @property) +(member_access_expression + name: (name) @property) + +; Variables + +(relative_scope) @variable.builtin + +((name) @constant + (#match? @constant "^_?[A-Z][A-Z\\d_]+$")) +((name) @constant.builtin + (#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$")) + +((name) @constructor + (#match? @constructor "^[A-Z]")) + +((name) @variable.builtin + (#eq? @variable.builtin "this")) + +(variable_name) @variable + +; Basic tokens +[ + (string) + (string_value) + (encapsed_string) + (heredoc) + (heredoc_body) + (nowdoc_body) +] @string +(boolean) @constant.builtin +(null) @constant.builtin +(integer) @number +(float) @number +(comment) @comment + +"$" @operator + +; Keywords + +"abstract" @keyword +"as" @keyword +"break" @keyword +"case" @keyword +"catch" @keyword +"class" @keyword +"const" @keyword +"continue" @keyword +"declare" @keyword +"default" @keyword +"do" @keyword +"echo" @keyword +"else" @keyword +"elseif" @keyword +"enddeclare" @keyword +"endforeach" @keyword +"endif" @keyword +"endswitch" @keyword +"endwhile" @keyword +"extends" @keyword +"final" @keyword +"finally" @keyword +"foreach" @keyword +"function" @keyword +"global" @keyword +"if" @keyword +"implements" @keyword +"include_once" @keyword +"include" @keyword +"insteadof" @keyword +"interface" @keyword +"namespace" @keyword +"new" @keyword +"private" @keyword +"protected" @keyword +"public" @keyword +"require_once" @keyword +"require" @keyword +"return" @keyword +"static" @keyword +"switch" @keyword +"throw" @keyword +"trait" @keyword +"try" @keyword +"use" @keyword +"while" @keyword diff --git a/crates/zed/src/languages/php/injections.scm b/crates/zed/src/languages/php/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..16d5736bebad153cece89b21f09dfae951e7457f --- /dev/null +++ b/crates/zed/src/languages/php/injections.scm @@ -0,0 +1,3 @@ +((text) @injection.content + (#set! injection.language "html") + (#set! injection.combined)) diff --git a/crates/zed/src/languages/php/outline.scm b/crates/zed/src/languages/php/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..57ea2ae33485c4b8390c62e8d6aa11c3fb639015 --- /dev/null +++ b/crates/zed/src/languages/php/outline.scm @@ -0,0 +1,8 @@ +(class_declaration + "class" @context + name: (name) @name + ) @item + +(function_definition + "function" @context + name: (_) @name) @item diff --git a/crates/zed/src/languages/php/tags.scm b/crates/zed/src/languages/php/tags.scm new file mode 100644 index 0000000000000000000000000000000000000000..66d594c254748c26623ce40456bab1304305be43 --- /dev/null +++ b/crates/zed/src/languages/php/tags.scm @@ -0,0 +1,40 @@ +(namespace_definition + name: (namespace_name) @name) @module + +(interface_declaration + name: (name) @name) @definition.interface + +(trait_declaration + name: (name) @name) @definition.interface + +(class_declaration + name: (name) @name) @definition.class + +(class_interface_clause [(name) (qualified_name)] @name) @impl + +(property_declaration + (property_element (variable_name (name) @name))) @definition.field + +(function_definition + name: (name) @name) @definition.function + +(method_declaration + name: (name) @name) @definition.function + +(object_creation_expression + [ + (qualified_name (name) @name) + (variable_name (name) @name) + ]) @reference.class + +(function_call_expression + function: [ + (qualified_name (name) @name) + (variable_name (name)) @name + ]) @reference.call + +(scoped_call_expression + name: (name) @name) @reference.call + +(member_call_expression + name: (name) @name) @reference.call diff --git a/crates/zed/src/languages/python/outline.scm b/crates/zed/src/languages/python/outline.scm index 373c7c7c68b3b2a97e753c4fca09b6c2e42c68e6..e3efb3dbf67c28083f6311b2337ecfe97ccebe27 100644 --- a/crates/zed/src/languages/python/outline.scm +++ b/crates/zed/src/languages/python/outline.scm @@ -6,4 +6,4 @@ (function_definition "async"? @context "def" @context - name: (_) @name) @item \ No newline at end of file + name: (_) @name) @item From 1cc8ecad1215e7d9e8b7411cb2e03f7d6b1c52ed Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 12 Jul 2023 19:33:09 +0200 Subject: [PATCH 012/162] Fix HTML injections (Thanks Max!) Co-authored-by: Max --- crates/zed/src/languages/php/injections.scm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zed/src/languages/php/injections.scm b/crates/zed/src/languages/php/injections.scm index 16d5736bebad153cece89b21f09dfae951e7457f..57abd8ea2b0576e7b936788b4a9880bc57fea798 100644 --- a/crates/zed/src/languages/php/injections.scm +++ b/crates/zed/src/languages/php/injections.scm @@ -1,3 +1,3 @@ -((text) @injection.content - (#set! injection.language "html") - (#set! injection.combined)) +((text) @content + (#set! "language" "html") + (#set! "combined")) From 608c16342c0ecdec4373e9d848a88ab1c37e5214 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:23:49 +0200 Subject: [PATCH 013/162] Update outline queries; add enum as a highlighted keyword --- crates/zed/src/languages/php/highlights.scm | 1 + crates/zed/src/languages/php/outline.scm | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/languages/php/highlights.scm b/crates/zed/src/languages/php/highlights.scm index 666a49be2a4795bfc923695417fdecf9a5100740..fcb087c47d14dbc036ed79a50be7ff1b57ebc4e8 100644 --- a/crates/zed/src/languages/php/highlights.scm +++ b/crates/zed/src/languages/php/highlights.scm @@ -88,6 +88,7 @@ "echo" @keyword "else" @keyword "elseif" @keyword +"enum" @keyword "enddeclare" @keyword "endforeach" @keyword "endif" @keyword diff --git a/crates/zed/src/languages/php/outline.scm b/crates/zed/src/languages/php/outline.scm index 57ea2ae33485c4b8390c62e8d6aa11c3fb639015..4934bc494d0ae709ec52ea3fb46db518b8dc35d8 100644 --- a/crates/zed/src/languages/php/outline.scm +++ b/crates/zed/src/languages/php/outline.scm @@ -5,4 +5,22 @@ (function_definition "function" @context - name: (_) @name) @item + name: (_) @name + ) @item + + + +(method_declaration + "function" @context + name: (_) @name + ) @item + +(interface_declaration + "interface" @context + name: (_) @name + ) @item + +(enum_declaration + "enum" @context + name: (_) @name + ) @item From 9a1a9813cbc5936f0da02088b3b70d5f5c445083 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 13 Jul 2023 11:56:53 -0400 Subject: [PATCH 014/162] WIP --- Cargo.lock | 10 ++++ crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/bash/brackets.scm | 3 ++ crates/zed/src/languages/bash/config.toml | 7 +++ crates/zed/src/languages/bash/highlights.scm | 56 ++++++++++++++++++++ 6 files changed, 78 insertions(+) create mode 100644 crates/zed/src/languages/bash/brackets.scm create mode 100644 crates/zed/src/languages/bash/config.toml create mode 100644 crates/zed/src/languages/bash/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index 0ac6a2ee890418104d6851a961d321f1ef7e8f36..ec674aee2ce721077e3f5b9f6054afa9ac92d2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7953,6 +7953,15 @@ dependencies = [ "regex", ] +[[package]] +name = "tree-sitter-bash" +version = "0.19.0" +source = "git+https://github.com/tree-sitter/tree-sitter-bash?rev=1b0321ee85701d5036c334a6f04761cdc672e64c#1b0321ee85701d5036c334a6f04761cdc672e64c" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-c" version = "0.20.2" @@ -9539,6 +9548,7 @@ dependencies = [ "tiny_http", "toml", "tree-sitter", + "tree-sitter-bash", "tree-sitter-c", "tree-sitter-cpp", "tree-sitter-css", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 597e40161fb029eee16cf53208ce0e20d0c0a603..fb7cd6addab2d164bdfd60600ed582cb1f838f6c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -104,6 +104,7 @@ thiserror.workspace = true tiny_http = "0.8" toml.workspace = true tree-sitter.workspace = true +tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" } tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 820f5641510d8cc0f0d0eb216a8c2056e4916428..225bc4d0bce8f735a5f87cf5232c0787eb987445 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -38,6 +38,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { languages.register(name, load_config(name), grammar, adapters, load_queries) }; + language("bash", tree_sitter_bash::language(), vec![]); language( "c", tree_sitter_c::language(), diff --git a/crates/zed/src/languages/bash/brackets.scm b/crates/zed/src/languages/bash/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..191fd9c084a52eced37428281971ff9e569a4932 --- /dev/null +++ b/crates/zed/src/languages/bash/brackets.scm @@ -0,0 +1,3 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) diff --git a/crates/zed/src/languages/bash/config.toml b/crates/zed/src/languages/bash/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..f3dcee9b73ecc4ea272febfc40128a1bad4d5a2a --- /dev/null +++ b/crates/zed/src/languages/bash/config.toml @@ -0,0 +1,7 @@ +name = "Shell Script" +path_suffixes = [".sh", ".bash", ".bashrc", ".bash_profile", ".bash_aliases", ".bash_logout", ".profile", ".zsh", ".zshrc", ".zshenv", ".zsh_profile", ".zsh_aliases", ".zsh_histfile", ".zlogin"] +brackets = [ + { start = "[", end = "]", close = true, newline = false }, + { start = "(", end = ")", close = true, newline = false }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] }, +] diff --git a/crates/zed/src/languages/bash/highlights.scm b/crates/zed/src/languages/bash/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..f33a7c2d3ae8c63364845dbd8cef1242ffa07f04 --- /dev/null +++ b/crates/zed/src/languages/bash/highlights.scm @@ -0,0 +1,56 @@ +[ + (string) + (raw_string) + (heredoc_body) + (heredoc_start) +] @string + +(command_name) @function + +(variable_name) @property + +[ + "case" + "do" + "done" + "elif" + "else" + "esac" + "export" + "fi" + "for" + "function" + "if" + "in" + "select" + "then" + "unset" + "until" + "while" +] @keyword + +(comment) @comment + +(function_definition name: (word) @function) + +(file_descriptor) @number + +[ + (command_substitution) + (process_substitution) + (expansion) +]@embedded + +[ + "$" + "&&" + ">" + ">>" + "<" + "|" +] @operator + +( + (command (_) @constant) + (#match? @constant "^-") +) From ef7aa66959ca44fe99293d8352360f94d569e33c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 13 Jul 2023 12:09:43 -0400 Subject: [PATCH 015/162] Add first line pattern --- crates/zed/src/languages/bash/config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/languages/bash/config.toml b/crates/zed/src/languages/bash/config.toml index f3dcee9b73ecc4ea272febfc40128a1bad4d5a2a..80b8753d80105f91ea9c0b802a2fe11ea61557c4 100644 --- a/crates/zed/src/languages/bash/config.toml +++ b/crates/zed/src/languages/bash/config.toml @@ -1,5 +1,6 @@ name = "Shell Script" path_suffixes = [".sh", ".bash", ".bashrc", ".bash_profile", ".bash_aliases", ".bash_logout", ".profile", ".zsh", ".zshrc", ".zshenv", ".zsh_profile", ".zsh_aliases", ".zsh_histfile", ".zlogin"] +first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b" brackets = [ { start = "[", end = "]", close = true, newline = false }, { start = "(", end = ")", close = true, newline = false }, From 29cbeb39bddd67b8846da75241595f809ff2c767 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 13 Jul 2023 14:29:02 +0300 Subject: [PATCH 016/162] Allow selecting all search matches in buffer --- assets/keymaps/default.json | 4 +- crates/editor/src/items.rs | 5 + crates/editor/src/selections_collection.rs | 4 +- crates/feedback/src/feedback_editor.rs | 5 + crates/language_tools/src/lsp_log.rs | 5 + crates/search/src/buffer_search.rs | 106 ++++++++++++++++++--- crates/search/src/search.rs | 3 +- crates/terminal/src/terminal.rs | 15 +++ crates/terminal_view/src/terminal_view.rs | 7 ++ crates/workspace/src/searchable.rs | 8 ++ 10 files changed, 146 insertions(+), 16 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 4726c67aea3f7836febece7546430660d57672ae..006719e5f5ac4b78df1e376e613d1f43e918d164 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -221,7 +221,8 @@ "escape": "buffer_search::Dismiss", "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" + "shift-enter": "search::SelectPrevMatch", + "cmd-shift-k": "search::CaretsToAllMatches" } }, { @@ -242,6 +243,7 @@ "cmd-f": "project_search::ToggleFocus", "cmd-g": "search::SelectNextMatch", "cmd-shift-g": "search::SelectPrevMatch", + "cmd-shift-k": "search::CaretsToAllMatches", "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex" diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 431ccf0bfe44f2c1370fd07e8f88962fc82b44da..cc24cd35da2b3e5eb05f35e5cfec2de2866a6afb 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -941,6 +941,11 @@ impl SearchableItem for Editor { }); } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.unfold_ranges(matches.clone(), false, false, cx); + self.change_selections(None, cx, |s| s.select_ranges(matches)); + } + fn match_index_for_direction( &mut self, matches: &Vec>, diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index d82ce5e21637fde58d1e19c866befe53270f2e9f..a22506f751b1ab36d518cdc13f60dc61dfecf6c7 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -16,13 +16,13 @@ use crate::{ Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, }; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct PendingSelection { pub selection: Selection, pub mode: SelectMode, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct SelectionsCollection { display_map: ModelHandle, buffer: ModelHandle, diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 5a4f912e3a063068adba31d787a21d1df42368b2..663164dd0770ebf0253c99a86ac84b984a450ae4 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -391,6 +391,11 @@ impl SearchableItem for FeedbackEditor { .update(cx, |editor, cx| editor.activate_match(index, matches, cx)) } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |e, cx| e.select_matches(matches, cx)) + } + fn find_matches( &mut self, query: project::search::SearchQuery, diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 12d8c6b34d3abd93e25e1b18e03656de477d417a..b27349f412e59e4f36b7dd21071cfad2de7f42e4 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -494,6 +494,11 @@ impl SearchableItem for LspLogView { .update(cx, |e, cx| e.activate_match(index, matches, cx)) } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |e, cx| e.select_matches(matches, cx)) + } + fn find_matches( &mut self, query: project::search::SearchQuery, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 59d25c2659f5ebbcad2b0a7ca4825c8a5bbf0d37..22778f85e81bb4468fb3cfc772ed3c598221b9da 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,6 +1,6 @@ use crate::{ - SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, - ToggleWholeWord, + CaretsToAllMatches, SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, + ToggleRegex, ToggleWholeWord, }; use collections::HashMap; use editor::Editor; @@ -39,8 +39,10 @@ pub fn init(cx: &mut AppContext) { cx.add_action(BufferSearchBar::focus_editor); cx.add_action(BufferSearchBar::select_next_match); cx.add_action(BufferSearchBar::select_prev_match); + cx.add_action(BufferSearchBar::carets_to_all_matches); cx.add_action(BufferSearchBar::select_next_match_on_pane); cx.add_action(BufferSearchBar::select_prev_match_on_pane); + cx.add_action(BufferSearchBar::carets_to_all_matches_on_pane); cx.add_action(BufferSearchBar::handle_editor_cancel); add_toggle_option_action::(SearchOption::CaseSensitive, cx); add_toggle_option_action::(SearchOption::WholeWord, cx); @@ -66,7 +68,7 @@ pub struct BufferSearchBar { active_searchable_item: Option>, active_match_index: Option, active_searchable_item_subscription: Option, - seachable_items_with_matches: + searchable_items_with_matches: HashMap, Vec>>, pending_search: Option>, case_sensitive: bool, @@ -118,7 +120,7 @@ impl View for BufferSearchBar { .with_children(self.active_searchable_item.as_ref().and_then( |searchable_item| { let matches = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade())?; let message = if let Some(match_ix) = self.active_match_index { format!("{}/{}", match_ix + 1, matches.len()) @@ -249,7 +251,7 @@ impl BufferSearchBar { active_searchable_item: None, active_searchable_item_subscription: None, active_match_index: None, - seachable_items_with_matches: Default::default(), + searchable_items_with_matches: Default::default(), case_sensitive: false, whole_word: false, regex: false, @@ -265,7 +267,7 @@ impl BufferSearchBar { pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext) { self.dismissed = true; - for searchable_item in self.seachable_items_with_matches.keys() { + for searchable_item in self.searchable_items_with_matches.keys() { if let Some(searchable_item) = WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) { @@ -488,11 +490,25 @@ impl BufferSearchBar { self.select_match(Direction::Prev, cx); } + fn carets_to_all_matches(&mut self, _: &CaretsToAllMatches, cx: &mut ViewContext) { + if !self.dismissed { + if let Some(searchable_item) = self.active_searchable_item.as_ref() { + if let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + { + searchable_item.select_matches(matches, cx); + self.focus_editor(&FocusEditor, cx); + } + } + } + } + pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade()) { let new_match_index = @@ -524,6 +540,16 @@ impl BufferSearchBar { } } + fn carets_to_all_matches_on_pane( + pane: &mut Pane, + action: &CaretsToAllMatches, + cx: &mut ViewContext, + ) { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |bar, cx| bar.carets_to_all_matches(action, cx)); + } + } + fn on_query_editor_event( &mut self, _: ViewHandle, @@ -547,7 +573,7 @@ impl BufferSearchBar { fn clear_matches(&mut self, cx: &mut ViewContext) { let mut active_item_matches = None; - for (searchable_item, matches) in self.seachable_items_with_matches.drain() { + for (searchable_item, matches) in self.searchable_items_with_matches.drain() { if let Some(searchable_item) = WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) { @@ -559,7 +585,7 @@ impl BufferSearchBar { } } - self.seachable_items_with_matches + self.searchable_items_with_matches .extend(active_item_matches); } @@ -605,13 +631,13 @@ impl BufferSearchBar { if let Some(active_searchable_item) = WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx) { - this.seachable_items_with_matches + this.searchable_items_with_matches .insert(active_searchable_item.downgrade(), matches); this.update_match_index(cx); if !this.dismissed { let matches = this - .seachable_items_with_matches + .searchable_items_with_matches .get(&active_searchable_item.downgrade()) .unwrap(); active_searchable_item.update_matches(matches, cx); @@ -637,7 +663,7 @@ impl BufferSearchBar { .as_ref() .and_then(|searchable_item| { let matches = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade())?; searchable_item.active_match_index(matches, cx) }); @@ -966,4 +992,60 @@ mod tests { assert_eq!(search_bar.active_match_index, Some(2)); }); } + + #[gpui::test] + async fn test_search_carets_to_all_matches(cx: &mut TestAppContext) { + crate::project_search::tests::init_test(cx); + + let buffer_text = r#" + A regular expression (shortened as regex or regexp;[1] also referred to as + rational expression[2][3]) is a sequence of characters that specifies a search + pattern in text. Usually such patterns are used by string-searching algorithms + for "find" or "find and replace" operations on strings, or for input validation. + "# + .unindent(); + let expected_query_matches_count = buffer_text + .chars() + .filter(|c| c.to_ascii_lowercase() == 'a') + .count(); + assert!( + expected_query_matches_count > 1, + "Should pick a query with multiple results" + ); + let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); + let (window_id, _root_view) = cx.add_window(|_| EmptyView); + + let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); + + let search_bar = cx.add_view(window_id, |cx| { + let mut search_bar = BufferSearchBar::new(cx); + search_bar.set_active_pane_item(Some(&editor), cx); + search_bar.show(false, true, cx); + search_bar + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.set_query("a", cx); + }); + + editor.next_notification(cx).await; + editor.update(cx, |editor, cx| { + let initial_selections = editor.selections.display_ranges(cx); + assert_eq!( + initial_selections.len(), 1, + "Expected to have only one selection before adding carets to all matches, but got: {initial_selections:?}", + ) + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.carets_to_all_matches(&CaretsToAllMatches, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + expected_query_matches_count, + "Should select all `a` characters in the buffer, but got: {all_selections:?}" + ); + }); + } } diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 90ea508cc61ad59b64606bede16c35882452d2b7..da679d191ee06fc5a5691a5def621397507531f3 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -17,7 +17,8 @@ actions!( ToggleCaseSensitive, ToggleRegex, SelectNextMatch, - SelectPrevMatch + SelectPrevMatch, + CaretsToAllMatches, ] ); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d14118bb186d9631958e01bfac74d56d52052f25..39e77b590b1077fd9cc917d2538ec0e563df453a 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -908,6 +908,21 @@ impl Terminal { } } + pub fn select_matches(&mut self, matches: Vec>) { + let matches_to_select = self + .matches + .iter() + .filter(|self_match| matches.contains(self_match)) + .cloned() + .collect::>(); + for match_to_select in matches_to_select { + self.set_selection(Some(( + make_selection(&match_to_select), + *match_to_select.end(), + ))); + } + } + fn set_selection(&mut self, selection: Option<(Selection, Point)>) { self.events .push_back(InternalEvent::SetSelection(selection)); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 36be6bee7f0460e0f64fca13784dc91db612c8b1..8e1e4ad62f0abe5b48d094b93612e4ed4f19c62c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -682,6 +682,13 @@ impl SearchableItem for TerminalView { cx.notify(); } + /// Add selections for all matches given. + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.terminal() + .update(cx, |term, _| term.select_matches(matches)); + cx.notify(); + } + /// Get all of the matches for this query, should be done on the background fn find_matches( &mut self, diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 7e3f7227b022f72bec64af3c765ab2b79cdafb49..4ebfe69c2181b74b555e6a799ad2c46effd8d7b9 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -47,6 +47,7 @@ pub trait SearchableItem: Item { matches: Vec, cx: &mut ViewContext, ); + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn match_index_for_direction( &mut self, matches: &Vec, @@ -102,6 +103,7 @@ pub trait SearchableItemHandle: ItemHandle { matches: &Vec>, cx: &mut WindowContext, ); + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext); fn match_index_for_direction( &self, matches: &Vec>, @@ -165,6 +167,12 @@ impl SearchableItemHandle for ViewHandle { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.activate_match(index, matches, cx)); } + + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.select_matches(matches, cx)); + } + fn match_index_for_direction( &self, matches: &Vec>, From 2053418f21b48c52f4b29be22a6c562398021ac3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 13 Jul 2023 14:45:42 +0300 Subject: [PATCH 017/162] Use VSCode-like shortcuts by default --- assets/keymaps/default.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 006719e5f5ac4b78df1e376e613d1f43e918d164..611d3633e39840e630ea3a916d036a294dc3968b 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -194,8 +194,8 @@ { "context": "Editor && mode == auto_height", "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" + "shift-enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineBelow" } }, { @@ -222,7 +222,7 @@ "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", "shift-enter": "search::SelectPrevMatch", - "cmd-shift-k": "search::CaretsToAllMatches" + "alt-enter": "search::CaretsToAllMatches" } }, { @@ -243,7 +243,7 @@ "cmd-f": "project_search::ToggleFocus", "cmd-g": "search::SelectNextMatch", "cmd-shift-g": "search::SelectPrevMatch", - "cmd-shift-k": "search::CaretsToAllMatches", + "alt-enter": "search::CaretsToAllMatches", "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex" From f710efca3bceef3b3373987274929b718d8c7749 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 13 Jul 2023 15:39:00 +0300 Subject: [PATCH 018/162] Use a better name --- assets/keymaps/default.json | 4 ++-- crates/search/src/buffer_search.rs | 18 +++++++++--------- crates/search/src/search.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 611d3633e39840e630ea3a916d036a294dc3968b..c044f4b6004f85a43218f4944dff6d4959fb9f96 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -222,7 +222,7 @@ "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", "shift-enter": "search::SelectPrevMatch", - "alt-enter": "search::CaretsToAllMatches" + "alt-enter": "search::SelectAllMatches" } }, { @@ -243,7 +243,7 @@ "cmd-f": "project_search::ToggleFocus", "cmd-g": "search::SelectNextMatch", "cmd-shift-g": "search::SelectPrevMatch", - "alt-enter": "search::CaretsToAllMatches", + "alt-enter": "search::SelectAllMatches", "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex" diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 22778f85e81bb4468fb3cfc772ed3c598221b9da..a87587a92f2b3992cdf9a83ab23bffd2f1d6f6da 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,5 +1,5 @@ use crate::{ - CaretsToAllMatches, SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, + SearchOption, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord, }; use collections::HashMap; @@ -39,10 +39,10 @@ pub fn init(cx: &mut AppContext) { cx.add_action(BufferSearchBar::focus_editor); cx.add_action(BufferSearchBar::select_next_match); cx.add_action(BufferSearchBar::select_prev_match); - cx.add_action(BufferSearchBar::carets_to_all_matches); + cx.add_action(BufferSearchBar::select_all_matches); cx.add_action(BufferSearchBar::select_next_match_on_pane); cx.add_action(BufferSearchBar::select_prev_match_on_pane); - cx.add_action(BufferSearchBar::carets_to_all_matches_on_pane); + cx.add_action(BufferSearchBar::select_all_matches_on_pane); cx.add_action(BufferSearchBar::handle_editor_cancel); add_toggle_option_action::(SearchOption::CaseSensitive, cx); add_toggle_option_action::(SearchOption::WholeWord, cx); @@ -490,7 +490,7 @@ impl BufferSearchBar { self.select_match(Direction::Prev, cx); } - fn carets_to_all_matches(&mut self, _: &CaretsToAllMatches, cx: &mut ViewContext) { + fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext) { if !self.dismissed { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self @@ -540,13 +540,13 @@ impl BufferSearchBar { } } - fn carets_to_all_matches_on_pane( + fn select_all_matches_on_pane( pane: &mut Pane, - action: &CaretsToAllMatches, + action: &SelectAllMatches, cx: &mut ViewContext, ) { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - search_bar.update(cx, |bar, cx| bar.carets_to_all_matches(action, cx)); + search_bar.update(cx, |bar, cx| bar.select_all_matches(action, cx)); } } @@ -994,7 +994,7 @@ mod tests { } #[gpui::test] - async fn test_search_carets_to_all_matches(cx: &mut TestAppContext) { + async fn test_search_select_all_matches(cx: &mut TestAppContext) { crate::project_search::tests::init_test(cx); let buffer_text = r#" @@ -1038,7 +1038,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.carets_to_all_matches(&CaretsToAllMatches, cx); + search_bar.select_all_matches(&SelectAllMatches, cx); let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index da679d191ee06fc5a5691a5def621397507531f3..7080b4c07e26496fb0c5f2059fd058a031163cb0 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -18,7 +18,7 @@ actions!( ToggleRegex, SelectNextMatch, SelectPrevMatch, - CaretsToAllMatches, + SelectAllMatches, ] ); From c130dd6b47c024ec9fa9b2b7a750010744d998ac Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 13 Jul 2023 09:48:27 -0400 Subject: [PATCH 019/162] Add styles for an `action_button` ahead of the "Select all matches" UI button --- styles/src/style_tree/search.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index 5c16d03233ee7af3f22fb3c67bf9b5b4a69f524d..28940a836756a8e0e4492e8fac432e9ec493d5e3 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -83,6 +83,35 @@ export default function search(): any { }, }, }), + action_button: interactive({ + base: { + ...text(theme.highest, "mono", "on"), + background: background(theme.highest, "on"), + corner_radius: 6, + border: border(theme.highest, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + }, + state: { + hovered: { + ...text(theme.highest, "mono", "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", "hovered"), + }, + clicked: { + ...text(theme.highest, "mono", "on", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: border(theme.highest, "on", "pressed"), + }, + }, + }), editor, invalid_editor: { ...editor, From ccc78000bd796105b2d9cd2d7fa69b7323fefcf4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 14 Jul 2023 14:33:26 +0300 Subject: [PATCH 020/162] Preserve serach index for multicaret selection editor events --- crates/editor/src/items.rs | 32 +++++++-- crates/feedback/src/feedback_editor.rs | 9 ++- crates/language_tools/src/lsp_log.rs | 9 ++- crates/search/src/buffer_search.rs | 79 ++++++++++++++++++++++- crates/terminal_view/src/terminal_view.rs | 6 +- crates/workspace/src/searchable.rs | 11 +++- 6 files changed, 130 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index cc24cd35da2b3e5eb05f35e5cfec2de2866a6afb..9498be184480f4b740ff15a1b6c2c08210d0aad1 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -887,10 +887,20 @@ pub(crate) enum BufferSearchHighlights {} impl SearchableItem for Editor { type Match = Range; - fn to_search_event(event: &Self::Event) -> Option { + fn to_search_event( + &mut self, + event: &Self::Event, + _: &mut ViewContext, + ) -> Option { match event { Event::BufferEdited => Some(SearchEvent::MatchesInvalidated), - Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged), + Event::SelectionsChanged { .. } => { + if self.selections.disjoint_anchors().len() == 1 { + Some(SearchEvent::ActiveMatchChanged) + } else { + None + } + } _ => None, } } @@ -954,8 +964,16 @@ impl SearchableItem for Editor { cx: &mut ViewContext, ) -> usize { let buffer = self.buffer().read(cx).snapshot(cx); - let cursor = self.selections.newest_anchor().head(); - if matches[current_index].start.cmp(&cursor, &buffer).is_gt() { + let current_index_position = if self.selections.disjoint_anchors().len() == 1 { + self.selections.newest_anchor().head() + } else { + matches[current_index].start + }; + if matches[current_index] + .start + .cmp(¤t_index_position, &buffer) + .is_gt() + { if direction == Direction::Prev { if current_index == 0 { current_index = matches.len() - 1; @@ -963,7 +981,11 @@ impl SearchableItem for Editor { current_index -= 1; } } - } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() { + } else if matches[current_index] + .end + .cmp(¤t_index_position, &buffer) + .is_lt() + { if direction == Direction::Next { current_index = 0; } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 663164dd0770ebf0253c99a86ac84b984a450ae4..bea398d3eb14a1630632f1d4726bd74fa3bdc39f 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -362,8 +362,13 @@ impl Item for FeedbackEditor { impl SearchableItem for FeedbackEditor { type Match = Range; - fn to_search_event(event: &Self::Event) -> Option { - Editor::to_search_event(event) + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.to_search_event(event, cx)) } fn clear_matches(&mut self, cx: &mut ViewContext) { diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index b27349f412e59e4f36b7dd21071cfad2de7f42e4..0dc594a13f47898c4d5960941c594091e02bd5d0 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -467,8 +467,13 @@ impl Item for LspLogView { impl SearchableItem for LspLogView { type Match = ::Match; - fn to_search_event(event: &Self::Event) -> Option { - Editor::to_search_event(event) + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.to_search_event(event, cx)) } fn clear_matches(&mut self, cx: &mut ViewContext) { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a87587a92f2b3992cdf9a83ab23bffd2f1d6f6da..c3790116d348f7b6dca7b832ad5417304d0a81b3 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1029,12 +1029,16 @@ mod tests { }); editor.next_notification(cx).await; - editor.update(cx, |editor, cx| { - let initial_selections = editor.selections.display_ranges(cx); + let initial_selections = editor.update(cx, |editor, cx| { + let initial_selections = editor.selections.display_ranges(cx); assert_eq!( initial_selections.len(), 1, "Expected to have only one selection before adding carets to all matches, but got: {initial_selections:?}", - ) + ); + initial_selections + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!(search_bar.active_match_index, Some(0)); }); search_bar.update(cx, |search_bar, cx| { @@ -1047,5 +1051,74 @@ mod tests { "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(0), + "Match index should not change after selecting all matches" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_next_match(&SelectNextMatch, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + 1, + "On next match, should deselect items and select the next match" + ); + assert_ne!( + all_selections, initial_selections, + "Next match should be different from the first selection" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(1), + "Match index should be updated to the next one" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + expected_query_matches_count, + "Should select all `a` characters in the buffer, but got: {all_selections:?}" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(1), + "Match index should not change after selecting all matches" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_prev_match(&SelectPrevMatch, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + 1, + "On previous match, should deselect items and select the previous item" + ); + assert_eq!( + all_selections, initial_selections, + "Previous match should be the same as the first selection" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(0), + "Match index should be updated to the previous one" + ); + }); } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 8e1e4ad62f0abe5b48d094b93612e4ed4f19c62c..3dd401e392c32481f2f5da76cd7acebed1d79cd3 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -647,7 +647,11 @@ impl SearchableItem for TerminalView { } /// Convert events raised by this item into search-relevant events (if applicable) - fn to_search_event(event: &Self::Event) -> Option { + fn to_search_event( + &mut self, + event: &Self::Event, + _: &mut ViewContext, + ) -> Option { match event { Event::Wakeup => Some(SearchEvent::MatchesInvalidated), Event::SelectionsChanged => Some(SearchEvent::ActiveMatchChanged), diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 4ebfe69c2181b74b555e6a799ad2c46effd8d7b9..3a3ba02e06653361e90f9136c70b4047ac82d031 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -37,7 +37,11 @@ pub trait SearchableItem: Item { regex: true, } } - fn to_search_event(event: &Self::Event) -> Option; + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option; fn clear_matches(&mut self, cx: &mut ViewContext); fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; @@ -141,8 +145,9 @@ impl SearchableItemHandle for ViewHandle { cx: &mut WindowContext, handler: Box, ) -> Subscription { - cx.subscribe(self, move |_, event, cx| { - if let Some(search_event) = T::to_search_event(event) { + cx.subscribe(self, move |handle, event, cx| { + let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx)); + if let Some(search_event) = search_event { handler(search_event, cx) } }) From b14cd5f56d3029f986a848a971c96cbb1f5f00e9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 14 Jul 2023 15:50:45 +0300 Subject: [PATCH 021/162] Add a new button for the action --- crates/search/src/buffer_search.rs | 32 ++++++++++++++++++++++++++++++ crates/theme/src/theme.rs | 1 + 2 files changed, 33 insertions(+) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c3790116d348f7b6dca7b832ad5417304d0a81b3..f6466c85af3100b6160c6b978e269af59b85af85 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -148,6 +148,7 @@ impl View for BufferSearchBar { Flex::row() .with_child(self.render_nav_button("<", Direction::Prev, cx)) .with_child(self.render_nav_button(">", Direction::Next, cx)) + .with_child(self.render_action_button("Select All", cx)) .aligned(), ) .with_child( @@ -403,6 +404,37 @@ impl BufferSearchBar { .into_any() } + fn render_action_button( + &self, + icon: &'static str, + cx: &mut ViewContext, + ) -> AnyElement { + let tooltip = "Select All Matches"; + let tooltip_style = theme::current(cx).tooltip.clone(); + let action_type_id = 0_usize; + + enum ActionButton {} + MouseEventHandler::::new(action_type_id, cx, |state, cx| { + let theme = theme::current(cx); + let style = theme.search.action_button.style_for(state); + Label::new(icon, style.text.clone()) + .contained() + .with_style(style.container) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.select_all_matches(&SelectAllMatches, cx) + }) + .with_cursor_style(CursorStyle::PointingHand) + .with_tooltip::( + action_type_id, + tooltip.to_string(), + Some(Box::new(SelectAllMatches)), + tooltip_style, + cx, + ) + .into_any() + } + fn render_close_button( &self, theme: &theme::Search, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4e8ece1c8fcf3e838431b1fc6cd6bbef2f798248..cdf3cadf594b68e29a6ce45451a1a4229d7eba7e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -379,6 +379,7 @@ pub struct Search { pub invalid_include_exclude_editor: ContainerStyle, pub include_exclude_inputs: ContainedText, pub option_button: Toggleable>, + pub action_button: Interactive, pub match_background: Color, pub match_index: ContainedText, pub results_status: TextStyle, From 2e2333107a21aa34c983089502c2133eca4bbfdb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 11 Jul 2023 14:15:45 -0700 Subject: [PATCH 022/162] Find the layer with the smallest enclosing node in language_scope_at --- crates/language/src/buffer.rs | 34 ++--- crates/language/src/buffer_tests.rs | 154 ++++++++++++++++------ crates/language/src/syntax_map.rs | 27 ++-- crates/project/src/project.rs | 2 + crates/project/src/worktree.rs | 1 + crates/zed/src/languages/heex/config.toml | 2 +- 6 files changed, 151 insertions(+), 69 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5041ab759d1fe0aa892feec2b472086f62a01242..0b10432a9f4747d93ff974ac72ddbbb6783fe676 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2145,23 +2145,27 @@ impl BufferSnapshot { pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); + let mut range = 0..self.len(); + let mut scope = self.language.clone().map(|language| LanguageScope { + language, + override_id: None, + }); - if let Some(layer_info) = self - .syntax - .layers_for_range(offset..offset, &self.text) - .filter(|l| l.node().end_byte() > offset) - .last() - { - Some(LanguageScope { - language: layer_info.language.clone(), - override_id: layer_info.override_id(offset, &self.text), - }) - } else { - self.language.clone().map(|language| LanguageScope { - language, - override_id: None, - }) + // Use the layer that has the smallest node intersecting the given point. + for layer in self.syntax.layers_for_range(offset..offset, &self.text) { + let mut cursor = layer.node().walk(); + while cursor.goto_first_child_for_byte(offset).is_some() {} + let node_range = cursor.node().byte_range(); + if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() { + range = node_range; + scope = Some(LanguageScope { + language: layer.language.clone(), + override_id: layer.override_id(offset, &self.text), + }); + } } + + scope } pub fn surrounding_word(&self, start: T) -> (Range, Option) { diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 38cefbcef9393ef577f67829febca7bbf03a6358..db9693a5306492f51a818c61bece0f0baa1f9fec 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { ]) }); - let html_language = Arc::new( - Language::new( - LanguageConfig { - name: "HTML".into(), - ..Default::default() - }, - Some(tree_sitter_html::language()), - ) - .with_indents_query( - " - (element - (start_tag) @start - (end_tag)? @end) @indent - ", - ) - .unwrap() - .with_injection_query( - r#" - (script_element - (raw_text) @content - (#set! "language" "javascript")) - "#, - ) - .unwrap(), - ); + let html_language = Arc::new(html_lang()); - let javascript_language = Arc::new( - Language::new( - LanguageConfig { - name: "JavaScript".into(), - ..Default::default() - }, - Some(tree_sitter_javascript::language()), - ) - .with_indents_query( - r#" - (object "}" @end) @indent - "#, - ) - .unwrap(), - ); + let javascript_language = Arc::new(javascript_lang()); let language_registry = Arc::new(LanguageRegistry::test()); language_registry.add(html_language.clone()); @@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { } #[gpui::test] -fn test_language_config_at(cx: &mut AppContext) { +fn test_language_scope_at(cx: &mut AppContext) { init_settings(cx, |_| {}); cx.add_model(|cx| { @@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) { }); } +#[gpui::test] +fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.add_model(|cx| { + let text = r#" +
    + <% people.each do |person| %> +
  1. + <%= person.name %> +
  2. + <% end %> +
+ "# + .unindent(); + + let language_registry = Arc::new(LanguageRegistry::test()); + language_registry.add(Arc::new(ruby_lang())); + language_registry.add(Arc::new(html_lang())); + language_registry.add(Arc::new(erb_lang())); + + let mut buffer = Buffer::new(0, text, cx); + buffer.set_language_registry(language_registry.clone()); + buffer.set_language( + language_registry + .language_for_name("ERB") + .now_or_never() + .unwrap() + .ok(), + cx, + ); + + let snapshot = buffer.snapshot(); + let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); + assert_eq!(html_config.line_comment_prefix(), None); + assert_eq!( + html_config.block_comment_delimiters(), + Some((&"".into())) + ); + + let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); + assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); + assert_eq!(ruby_config.block_comment_delimiters(), None); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::AppContext) { let mut now = Instant::now(); @@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language { LanguageConfig { name: "Ruby".into(), path_suffixes: vec!["rb".to_string()], + line_comment: Some("# ".into()), ..Default::default() }, Some(tree_sitter_ruby::language()), @@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language { .unwrap() } +fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + block_comment: Some(("".into())), + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_indents_query( + " + (element + (start_tag) @start + (end_tag)? @end) @indent + ", + ) + .unwrap() + .with_injection_query( + r#" + (script_element + (raw_text) @content + (#set! "language" "javascript")) + "#, + ) + .unwrap() +} + +fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + block_comment: Some(("<%#".into(), "%>".into())), + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_injection_query( + r#" + ( + (code) @content + (#set! "language" "ruby") + (#set! "combined") + ) + + ( + (content) @content + (#set! "language" "html") + (#set! "combined") + ) + "#, + ) + .unwrap() +} + fn rust_lang() -> Language { Language::new( LanguageConfig { @@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language { "#, ) .unwrap() + .with_indents_query( + r#" + (object "}" @end) @indent + "#, + ) + .unwrap() } fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index b6431c228674f6076b5d4bec2a3fa59bb7795d96..526f99ec95f8180915261608070d3f480165777f 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -771,8 +771,10 @@ impl SyntaxSnapshot { range: Range, buffer: &'a BufferSnapshot, ) -> impl 'a + Iterator { - let start = buffer.anchor_before(range.start.to_offset(buffer)); - let end = buffer.anchor_after(range.end.to_offset(buffer)); + let start_offset = range.start.to_offset(buffer); + let end_offset = range.end.to_offset(buffer); + let start = buffer.anchor_before(start_offset); + let end = buffer.anchor_after(end_offset); let mut cursor = self.layers.filter::<_, ()>(move |summary| { if summary.max_depth > summary.min_depth { @@ -787,20 +789,21 @@ impl SyntaxSnapshot { cursor.next(buffer); iter::from_fn(move || { while let Some(layer) = cursor.item() { + let mut info = None; if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { - let info = SyntaxLayerInfo { + let layer_start_offset = layer.range.start.to_offset(buffer); + let layer_start_point = layer.range.start.to_point(buffer).to_ts_point(); + + info = Some(SyntaxLayerInfo { tree, language, depth: layer.depth, - offset: ( - layer.range.start.to_offset(buffer), - layer.range.start.to_point(buffer).to_ts_point(), - ), - }; - cursor.next(buffer); - return Some(info); - } else { - cursor.next(buffer); + offset: (layer_start_offset, layer_start_point), + }); + } + cursor.next(buffer); + if info.is_some() { + return info; } } None diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3bb5457b1ca50572322f372cf5ff95c664c83eee..fded9ec309e7b38b4c563eaf15b312cb00483baf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3045,6 +3045,8 @@ impl Project { ) -> Task<(Option, Vec)> { let key = (worktree_id, adapter_name); if let Some(server_id) = self.language_server_ids.remove(&key) { + log::info!("stopping language server {}", key.1 .0); + // Remove other entries for this language server as well let mut orphaned_worktrees = vec![worktree_id]; let other_keys = self.language_server_ids.keys().cloned().collect::>(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2c3c9d53047e48b55f556038504bf3546a4ad284..b113af34ad40c9f06d5fa2989aee94219c2091b6 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -397,6 +397,7 @@ impl Worktree { })) } + // abcdefghi pub fn remote( project_remote_id: u64, replica_id: ReplicaId, diff --git a/crates/zed/src/languages/heex/config.toml b/crates/zed/src/languages/heex/config.toml index fafd75dc8db3a94ba732406348218a51bf3b8b70..b5d74f80f319d85feccbe5055576066a65ad0958 100644 --- a/crates/zed/src/languages/heex/config.toml +++ b/crates/zed/src/languages/heex/config.toml @@ -4,4 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] -block_comment = ["<%#", "%>"] +block_comment = ["<%!--", "--%>"] From 2f2ef7c165370dce683f78aea3e9aad00705864e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 13 Jul 2023 18:09:32 -0700 Subject: [PATCH 023/162] Use workspace dependencies for tree-sitter grammars --- Cargo.lock | 36 ++++++------------------- Cargo.toml | 21 +++++++++++++++ crates/editor/Cargo.toml | 17 ++++++------ crates/editor/src/editor_tests.rs | 4 +-- crates/language/Cargo.toml | 26 +++++++++--------- crates/language/src/buffer_tests.rs | 4 +-- crates/language/src/language.rs | 2 +- crates/zed/Cargo.toml | 41 +++++++++++++++-------------- 8 files changed, 77 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a4562954f67a28df7200b02848820a189c9331f..57604d28b8ec025eb615bc643cf49c0401c73679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2253,9 +2253,8 @@ dependencies = [ "theme", "tree-sitter", "tree-sitter-html", - "tree-sitter-javascript", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", + "tree-sitter-typescript", "unindent", "util", "workspace", @@ -3750,15 +3749,16 @@ dependencies = [ "text", "theme", "tree-sitter", + "tree-sitter-elixir", "tree-sitter-embedded-template", + "tree-sitter-heex", "tree-sitter-html", - "tree-sitter-javascript", - "tree-sitter-json 0.19.0", + "tree-sitter-json 0.20.0", "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tree-sitter-typescript", "unicase", "unindent", "util", @@ -8029,16 +8029,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "tree-sitter-javascript" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "tree-sitter-json" version = "0.19.0" @@ -8118,8 +8108,8 @@ dependencies = [ [[package]] name = "tree-sitter-scheme" -version = "0.5.0" -source = "git+https://github.com/6cdh/tree-sitter-scheme?rev=ca8af220aaf2a80aaf609bfb0df193817e4f064b#ca8af220aaf2a80aaf609bfb0df193817e4f064b" +version = "0.2.0" +source = "git+https://github.com/6cdh/tree-sitter-scheme?rev=af0fd1fa452cb2562dc7b5c8a8c55551c39273b9#af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" dependencies = [ "cc", "tree-sitter", @@ -8143,16 +8133,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "tree-sitter-typescript" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "tree-sitter-typescript" version = "0.20.2" @@ -9566,7 +9546,7 @@ dependencies = [ "tree-sitter-scheme", "tree-sitter-svelte", "tree-sitter-toml", - "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", + "tree-sitter-typescript", "tree-sitter-yaml", "unindent", "url", diff --git a/Cargo.toml b/Cargo.toml index 529f297f700d5006df3c169f3e663144bc24f9d1..48a9a51cd108d6ff85bacc8d3b6f621a552b5e50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,27 @@ tree-sitter = "0.20" unindent = { version = "0.1.7" } pretty_assertions = "1.3.0" +tree-sitter-c = "0.20.1" +tree-sitter-cpp = "0.20.0" +tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } +tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } +tree-sitter-embedded-template = "0.20.0" +tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } +tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } +tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } +tree-sitter-rust = "0.20.3" +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +tree-sitter-python = "0.20.2" +tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } +tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } +tree-sitter-ruby = "0.20.0" +tree-sitter-html = "0.19.0" +tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} +tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"} +tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} +tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} +tree-sitter-lua = "0.0.14" + [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "49226023693107fba9a1191136a4f47f38cdca73" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index dcc22202273a5d087a5892f279e1d153b3d9770e..087ce81c268ffcbc11f598031b22d9ee272a114e 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -57,16 +57,16 @@ ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } -rand = { workspace = true, optional = true } schemars.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true -tree-sitter-rust = { version = "*", optional = true } -tree-sitter-html = { version = "*", optional = true } -tree-sitter-javascript = { version = "*", optional = true } -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true } + +rand = { workspace = true, optional = true } +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } [dev-dependencies] copilot = { path = "../copilot", features = ["test-support"] } @@ -84,7 +84,6 @@ env_logger.workspace = true rand.workspace = true unindent.workspace = true tree-sitter.workspace = true -tree-sitter-rust = "0.20" -tree-sitter-html = "0.19" -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } -tree-sitter-javascript = "0.20" +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 260b0ccc40bde32816e8c6d5e10bb8be9075b2a1..247a7b021d77cac7d2386f67fc676d7723b037fc 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3836,7 +3836,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { autoclose_before: "})]>".into(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), )); let registry = Arc::new(LanguageRegistry::test()); @@ -5383,7 +5383,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { line_comment: Some("// ".into()), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), )); let registry = Arc::new(LanguageRegistry::test()); diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index c1f7e79d5819761b4dbcc34830636b01130d3c71..4771fc70833660ccb018d9ffd45362f018901e4a 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,7 +46,6 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true -rand = { workspace = true, optional = true } regex.workspace = true schemars.workspace = true serde.workspace = true @@ -56,10 +55,12 @@ similar = "1.3" smallvec.workspace = true smol.workspace = true tree-sitter.workspace = true -tree-sitter-rust = { version = "*", optional = true } -tree-sitter-typescript = { version = "*", optional = true } unicase = "2.6" +rand = { workspace = true, optional = true } +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + [dev-dependencies] client = { path = "../client", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } @@ -74,12 +75,13 @@ indoc.workspace = true rand.workspace = true unindent.workspace = true -tree-sitter-embedded-template = "*" -tree-sitter-html = "*" -tree-sitter-javascript = "*" -tree-sitter-json = "*" -tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -tree-sitter-rust = "*" -tree-sitter-python = "*" -tree-sitter-typescript = "*" -tree-sitter-ruby = "*" +tree-sitter-embedded-template.workspace = true +tree-sitter-html.workspace = true +tree-sitter-json.workspace = true +tree-sitter-markdown.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-python.workspace = true +tree-sitter-typescript.workspace = true +tree-sitter-ruby.workspace = true +tree-sitter-elixir.workspace = true +tree-sitter-heex.workspace = true diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index db9693a5306492f51a818c61bece0f0baa1f9fec..399ca85e56a20233125bf244f3723549f70df199 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1671,7 +1671,7 @@ fn test_language_scope_at(cx: &mut AppContext) { .collect(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), ) .with_override_query( r#" @@ -2293,7 +2293,7 @@ fn javascript_lang() -> Language { name: "JavaScript".into(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), ) .with_brackets_query( r#" diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index dbd35f0e87bc602ac91aaf8196b81a4a017fff93..af80069e15024336f10c004dc6d20428fe52eb89 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1791,7 +1791,7 @@ mod tests { first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()), ..Default::default() }, - tree_sitter_javascript::language(), + tree_sitter_typescript::language_tsx(), vec![], |_| Default::default(), ); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 28970b1a0fa5182dd3bd2f20129d2205734681ea..848c07d500ffcbfeb1a355318221eec1055889ba 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -104,26 +104,27 @@ thiserror.workspace = true tiny_http = "0.8" toml.workspace = true tree-sitter.workspace = true -tree-sitter-c = "0.20.1" -tree-sitter-cpp = "0.20.0" -tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } -tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } -tree-sitter-embedded-template = "0.20.0" -tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } -tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } -tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } -tree-sitter-rust = "0.20.3" -tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -tree-sitter-python = "0.20.2" -tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } -tree-sitter-ruby = "0.20.0" -tree-sitter-html = "0.19.0" -tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "ca8af220aaf2a80aaf609bfb0df193817e4f064b"} -tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} -tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} -tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"} -tree-sitter-lua = "0.0.14" +tree-sitter-c.workspace = true +tree-sitter-cpp.workspace = true +tree-sitter-css.workspace = true +tree-sitter-elixir.workspace = true +tree-sitter-embedded-template.workspace = true +tree-sitter-go.workspace = true +tree-sitter-heex.workspace = true +tree-sitter-json.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-markdown.workspace = true +tree-sitter-python.workspace = true +tree-sitter-toml.workspace = true +tree-sitter-typescript.workspace = true +tree-sitter-ruby.workspace = true +tree-sitter-html.workspace = true +tree-sitter-scheme.workspace = true +tree-sitter-svelte.workspace = true +tree-sitter-racket.workspace = true +tree-sitter-yaml.workspace = true +tree-sitter-lua.workspace = true + url = "2.2" urlencoding = "2.1.2" uuid = { version = "1.1.2", features = ["v4"] } From 21e7e35e7356be6cc7a7c870c7a0bbec294d7fa3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 13 Jul 2023 18:38:29 -0700 Subject: [PATCH 024/162] Include newlines in between combined injection ranges on different lines --- crates/language/src/syntax_map.rs | 90 ++++++++++++- .../src/syntax_map/syntax_map_tests.rs | 120 ++++++++++++++++-- crates/zed/src/languages/heex/config.toml | 2 +- crates/zed/src/languages/heex/highlights.scm | 6 +- 4 files changed, 201 insertions(+), 17 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 526f99ec95f8180915261608070d3f480165777f..1590294b1a16d8b98652bd583539b7fdc991a06e 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -569,11 +569,19 @@ impl SyntaxSnapshot { range.end = range.end.saturating_sub(step_start_byte); } - included_ranges = splice_included_ranges( + let changed_indices; + (included_ranges, changed_indices) = splice_included_ranges( old_tree.included_ranges(), &parent_layer_changed_ranges, &included_ranges, ); + insert_newlines_between_ranges( + changed_indices, + &mut included_ranges, + &text, + step_start_byte, + step_start_point, + ); } if included_ranges.is_empty() { @@ -586,7 +594,7 @@ impl SyntaxSnapshot { } log::trace!( - "update layer. language:{}, start:{:?}, ranges:{:?}", + "update layer. language:{}, start:{:?}, included_ranges:{:?}", language.name(), LogAnchorRange(&step.range, text), LogIncludedRanges(&included_ranges), @@ -608,6 +616,16 @@ impl SyntaxSnapshot { }), ); } else { + if matches!(step.mode, ParseMode::Combined { .. }) { + insert_newlines_between_ranges( + 0..included_ranges.len(), + &mut included_ranges, + text, + step_start_byte, + step_start_point, + ); + } + if included_ranges.is_empty() { included_ranges.push(tree_sitter::Range { start_byte: 0, @@ -1275,14 +1293,20 @@ fn get_injections( } } +/// Update the given list of included `ranges`, removing any ranges that intersect +/// `removed_ranges`, and inserting the given `new_ranges`. +/// +/// Returns a new vector of ranges, and the range of the vector that was changed, +/// from the previous `ranges` vector. pub(crate) fn splice_included_ranges( mut ranges: Vec, removed_ranges: &[Range], new_ranges: &[tree_sitter::Range], -) -> Vec { +) -> (Vec, Range) { let mut removed_ranges = removed_ranges.iter().cloned().peekable(); let mut new_ranges = new_ranges.into_iter().cloned().peekable(); let mut ranges_ix = 0; + let mut changed_portion = usize::MAX..0; loop { let next_new_range = new_ranges.peek(); let next_removed_range = removed_ranges.peek(); @@ -1344,11 +1368,69 @@ pub(crate) fn splice_included_ranges( } } + changed_portion.start = changed_portion.start.min(start_ix); + changed_portion.end = changed_portion.end.max(if insert.is_some() { + start_ix + 1 + } else { + start_ix + }); + ranges.splice(start_ix..end_ix, insert); ranges_ix = start_ix; } - ranges + if changed_portion.end < changed_portion.start { + changed_portion = 0..0; + } + + (ranges, changed_portion) +} + +/// Ensure there are newline ranges in between content range that appear on +/// different lines. For performance, only iterate through the given range of +/// indices. All of the ranges in the array are relative to a given start byte +/// and point. +fn insert_newlines_between_ranges( + indices: Range, + ranges: &mut Vec, + text: &text::BufferSnapshot, + start_byte: usize, + start_point: Point, +) { + let mut ix = indices.end + 1; + while ix > indices.start { + ix -= 1; + if 0 == ix || ix == ranges.len() { + continue; + } + + let range_b = ranges[ix].clone(); + let range_a = &mut ranges[ix - 1]; + if range_a.end_point.column == 0 { + continue; + } + + if range_a.end_point.row < range_b.start_point.row { + let end_point = start_point + Point::from_ts_point(range_a.end_point); + let line_end = Point::new(end_point.row, text.line_len(end_point.row)); + if end_point.column as u32 >= line_end.column { + range_a.end_byte += 1; + range_a.end_point.row += 1; + range_a.end_point.column = 0; + } else { + let newline_offset = text.point_to_offset(line_end); + ranges.insert( + ix, + tree_sitter::Range { + start_byte: newline_offset - start_byte, + end_byte: newline_offset - start_byte + 1, + start_point: (line_end - start_point).to_ts_point(), + end_point: ((line_end - start_point) + Point::new(1, 0)).to_ts_point(), + }, + ) + } + } + } } impl OwnedSyntaxLayerInfo { diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 272501f2d08a1cafe0957a67c31f57e19d63204b..a6d35d0e2f15baed1bc34094ee860d99ba673522 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -11,7 +11,7 @@ use util::test::marked_text_ranges; fn test_splice_included_ranges() { let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)]; - let new_ranges = splice_included_ranges( + let (new_ranges, change) = splice_included_ranges( ranges.clone(), &[54..56, 58..68], &[ts_range(50..54), ts_range(59..67)], @@ -25,14 +25,16 @@ fn test_splice_included_ranges() { ts_range(80..90), ] ); + assert_eq!(change, 1..3); - let new_ranges = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); + let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); assert_eq!( new_ranges, &[ts_range(20..30), ts_range(50..60), ts_range(80..90)] ); + assert_eq!(change, 2..3); - let new_ranges = + let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]); assert_eq!( new_ranges, @@ -44,16 +46,21 @@ fn test_splice_included_ranges() { ts_range(80..90) ] ); + assert_eq!(change, 0..4); - let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]); + assert_eq!(change, 0..1); // does not create overlapping ranges - let new_ranges = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); assert_eq!( new_ranges, &[ts_range(20..32), ts_range(50..60), ts_range(80..90)] ); + assert_eq!(change, 0..1); fn ts_range(range: Range) -> tree_sitter::Range { tree_sitter::Range { @@ -511,7 +518,7 @@ fn test_removing_injection_by_replacing_across_boundary() { } #[gpui::test] -fn test_combined_injections() { +fn test_combined_injections_simple() { let (buffer, syntax_map) = test_edit_sequence( "ERB", &[ @@ -653,33 +660,78 @@ fn test_combined_injections_editing_after_last_injection() { #[gpui::test] fn test_combined_injections_inside_injections() { - let (_buffer, _syntax_map) = test_edit_sequence( + let (buffer, syntax_map) = test_edit_sequence( "Markdown", &[ r#" - here is some ERB code: + here is + some + ERB code: ```erb
    <% people.each do |person| %>
  • <%= person.name %>
  • +
  • <%= person.age %>
  • <% end %>
``` "#, r#" - here is some ERB code: + here is + some + ERB code: ```erb
    <% people«2».each do |person| %>
  • <%= person.name %>
  • +
  • <%= person.age %>
  • + <% end %> +
+ ``` + "#, + // Inserting a comment character inside one code directive + // does not cause the other code directive to become a comment, + // because newlines are included in between each injection range. + r#" + here is + some + ERB code: + + ```erb +
    + <% people2.each do |person| %> +
  • <%= «# »person.name %>
  • +
  • <%= person.age %>
  • <% end %>
``` "#, ], ); + + // Check that the code directive below the ruby comment is + // not parsed as a comment. + assert_capture_ranges( + &syntax_map, + &buffer, + &["method"], + " + here is + some + ERB code: + + ```erb +
    + <% people2.«each» do |person| %> +
  • <%= # person.name %>
  • +
  • <%= person.«age» %>
  • + <% end %> +
+ ``` + ", + ); } #[gpui::test] @@ -984,11 +1036,14 @@ fn check_interpolation( fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) { let registry = Arc::new(LanguageRegistry::test()); + registry.add(Arc::new(elixir_lang())); + registry.add(Arc::new(heex_lang())); registry.add(Arc::new(rust_lang())); registry.add(Arc::new(ruby_lang())); registry.add(Arc::new(html_lang())); registry.add(Arc::new(erb_lang())); registry.add(Arc::new(markdown_lang())); + let language = registry .language_for_name(language_name) .now_or_never() @@ -1074,6 +1129,7 @@ fn ruby_lang() -> Language { r#" ["if" "do" "else" "end"] @keyword (instance_variable) @ivar + (call method: (identifier) @method) "#, ) .unwrap() @@ -1158,6 +1214,52 @@ fn markdown_lang() -> Language { .unwrap() } +fn elixir_lang() -> Language { + Language::new( + LanguageConfig { + name: "Elixir".into(), + path_suffixes: vec!["ex".into()], + ..Default::default() + }, + Some(tree_sitter_elixir::language()), + ) + .with_highlights_query( + r#" + + "#, + ) + .unwrap() +} + +fn heex_lang() -> Language { + Language::new( + LanguageConfig { + name: "HEEx".into(), + path_suffixes: vec!["heex".into()], + ..Default::default() + }, + Some(tree_sitter_heex::language()), + ) + .with_injection_query( + r#" + ( + (directive + [ + (partial_expression_value) + (expression_value) + (ending_expression_value) + ] @content) + (#set! language "elixir") + (#set! combined) + ) + + ((expression (expression_value) @content) + (#set! language "elixir")) + "#, + ) + .unwrap() +} + fn range_for_text(buffer: &Buffer, text: &str) -> Range { let start = buffer.as_rope().to_string().find(text).unwrap(); start..start + text.len() diff --git a/crates/zed/src/languages/heex/config.toml b/crates/zed/src/languages/heex/config.toml index b5d74f80f319d85feccbe5055576066a65ad0958..c9f952ee3c4f2813dcaf0e94fd3d5858e78d0922 100644 --- a/crates/zed/src/languages/heex/config.toml +++ b/crates/zed/src/languages/heex/config.toml @@ -4,4 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] -block_comment = ["<%!--", "--%>"] +block_comment = ["<%!-- ", " --%>"] diff --git a/crates/zed/src/languages/heex/highlights.scm b/crates/zed/src/languages/heex/highlights.scm index 8728110d5826959b893b352dffd8c8e01a4d29ac..5252b71facd533473dae9e1ffda76924fae65eee 100644 --- a/crates/zed/src/languages/heex/highlights.scm +++ b/crates/zed/src/languages/heex/highlights.scm @@ -1,10 +1,7 @@ ; HEEx delimiters [ - "--%>" - "-->" "/>" "" + "--%>" + "-->" + " + anchor_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/archive.svg b/assets/icons/file_icons/quill/archive.svg new file mode 100644 index 0000000000000000000000000000000000000000..c78ca0cff6f18a3396f7ff3e7a414c8cdfb3016c --- /dev/null +++ b/assets/icons/file_icons/quill/archive.svg @@ -0,0 +1,12 @@ + + + + archive_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/book.svg b/assets/icons/file_icons/quill/book.svg new file mode 100644 index 0000000000000000000000000000000000000000..af918b5c612568ca648ea4c647df0f0d0498d774 --- /dev/null +++ b/assets/icons/file_icons/quill/book.svg @@ -0,0 +1,11 @@ + + + + book_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/camera.svg b/assets/icons/file_icons/quill/camera.svg new file mode 100644 index 0000000000000000000000000000000000000000..f861af607c118fda7abfbd1deb026eebe29393cf --- /dev/null +++ b/assets/icons/file_icons/quill/camera.svg @@ -0,0 +1,12 @@ + + + + camera_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/code.svg b/assets/icons/file_icons/quill/code.svg new file mode 100644 index 0000000000000000000000000000000000000000..a844740f1ab90857731d3e50e6c3d6531a911638 --- /dev/null +++ b/assets/icons/file_icons/quill/code.svg @@ -0,0 +1,12 @@ + + + + code_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/database.svg b/assets/icons/file_icons/quill/database.svg new file mode 100644 index 0000000000000000000000000000000000000000..8c98d5ac164bd8a5ba95d545208a25f18a24a6b2 --- /dev/null +++ b/assets/icons/file_icons/quill/database.svg @@ -0,0 +1,11 @@ + + + + database_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/eslint.svg b/assets/icons/file_icons/quill/eslint.svg new file mode 100644 index 0000000000000000000000000000000000000000..880689293a3a0e43948d0a1c8737b44caae717e7 --- /dev/null +++ b/assets/icons/file_icons/quill/eslint.svg @@ -0,0 +1,10 @@ + + + + eslint_dark + Created with Sketch. + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/file.svg b/assets/icons/file_icons/quill/file.svg new file mode 100644 index 0000000000000000000000000000000000000000..492c383ab6139506caf56c2f585c7dbda44c0ddf --- /dev/null +++ b/assets/icons/file_icons/quill/file.svg @@ -0,0 +1,11 @@ + + + + file_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/folder-open.svg b/assets/icons/file_icons/quill/folder-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..00a94c199f9dcea24ba548244ac75b59a794f44f --- /dev/null +++ b/assets/icons/file_icons/quill/folder-open.svg @@ -0,0 +1,12 @@ + + + + folder-open_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/folder.svg b/assets/icons/file_icons/quill/folder.svg new file mode 100644 index 0000000000000000000000000000000000000000..9cc5b4a8c9b20cdde8bf4fafb763cb22aae71df0 --- /dev/null +++ b/assets/icons/file_icons/quill/folder.svg @@ -0,0 +1,11 @@ + + + + folder_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/git.svg b/assets/icons/file_icons/quill/git.svg new file mode 100644 index 0000000000000000000000000000000000000000..830a7f956570da134f4727fde9631937755cb618 --- /dev/null +++ b/assets/icons/file_icons/quill/git.svg @@ -0,0 +1,11 @@ + + + + git_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/hash.svg b/assets/icons/file_icons/quill/hash.svg new file mode 100644 index 0000000000000000000000000000000000000000..36366625fe459476ce954cd5f847897ae90cb21d --- /dev/null +++ b/assets/icons/file_icons/quill/hash.svg @@ -0,0 +1,11 @@ + + + + hash_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/html.svg b/assets/icons/file_icons/quill/html.svg new file mode 100644 index 0000000000000000000000000000000000000000..7704575f24a8bbad804aa89346b8e40925cf0ca5 --- /dev/null +++ b/assets/icons/file_icons/quill/html.svg @@ -0,0 +1,12 @@ + + + + html_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/image.svg b/assets/icons/file_icons/quill/image.svg new file mode 100644 index 0000000000000000000000000000000000000000..0ec9583edd5487becc9d9b48f819c75d4712cd21 --- /dev/null +++ b/assets/icons/file_icons/quill/image.svg @@ -0,0 +1,12 @@ + + + + image_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/info.svg b/assets/icons/file_icons/quill/info.svg new file mode 100644 index 0000000000000000000000000000000000000000..af3fa9d39d92bbb650cbea21eae1fed218bbf22b --- /dev/null +++ b/assets/icons/file_icons/quill/info.svg @@ -0,0 +1,12 @@ + + + + info_dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/lock.svg b/assets/icons/file_icons/quill/lock.svg new file mode 100644 index 0000000000000000000000000000000000000000..a1e36e6c122b5a8b47ff43e90aedda39fdbe61de --- /dev/null +++ b/assets/icons/file_icons/quill/lock.svg @@ -0,0 +1,11 @@ + + + + lock_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/package.svg b/assets/icons/file_icons/quill/package.svg new file mode 100644 index 0000000000000000000000000000000000000000..9bda169cf59af3c591617ee1bd6a25f3b72d691d --- /dev/null +++ b/assets/icons/file_icons/quill/package.svg @@ -0,0 +1,11 @@ + + + + package_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/prettier.svg b/assets/icons/file_icons/quill/prettier.svg new file mode 100644 index 0000000000000000000000000000000000000000..ba7b340654362a375473f913508ce9070e04ab73 --- /dev/null +++ b/assets/icons/file_icons/quill/prettier.svg @@ -0,0 +1,22 @@ + + + + prettier_dark + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/settings.svg b/assets/icons/file_icons/quill/settings.svg new file mode 100644 index 0000000000000000000000000000000000000000..f0209bf3c22ba2f0337878265c40582e1e72d6c5 --- /dev/null +++ b/assets/icons/file_icons/quill/settings.svg @@ -0,0 +1,11 @@ + + + + settings_dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/assets/icons/file_icons/quill/terminal.svg b/assets/icons/file_icons/quill/terminal.svg new file mode 100644 index 0000000000000000000000000000000000000000..964f44251ffa0fc23954aaa7abb266c295344c6b --- /dev/null +++ b/assets/icons/file_icons/quill/terminal.svg @@ -0,0 +1,9 @@ + + + + terminal_dark + Created with Sketch. + + + + \ No newline at end of file diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 60f14d9fa7054b090558c29a67df70029f4ee6e1..50a0b4b161cb2253602d0c28c769e872a331bd2e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -342,8 +342,6 @@ pub struct LanguageConfig { pub block_comment: Option<(Arc, Arc)>, #[serde(default)] pub overrides: HashMap, - #[serde(default)] - pub icon_path: Option>, } #[derive(Debug, Default)] @@ -410,7 +408,6 @@ impl Default for LanguageConfig { line_comment: Default::default(), block_comment: Default::default(), overrides: Default::default(), - icon_path: Default::default(), } } } @@ -755,16 +752,6 @@ impl LanguageRegistry { self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name) } - pub fn icon_for_suffix(self: &Arc, suffix: &str) -> Option> { - let state = self.state.read(); - state - .available_languages - .iter() - .find(|langauge| langauge.config.path_suffixes.iter().any(|s| s == suffix)) - .map(|language| language.config.icon_path.clone()) - .flatten() - } - pub fn language_for_name_or_extension( self: &Arc, string: &str, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 80c098baa6d90117f414d57743e68ed92600702f..5bb8af3f389d9787a55734727e15c8310c3b99fa 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2474,10 +2474,6 @@ impl Project { }) } - pub fn icon_for_path(&self, path: &Path) -> Option> { - self.languages.icon_for_suffix(path.extension()?.to_str()?) - } - fn detect_language_for_buffer( &mut self, buffer_handle: &ModelHandle, diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 33606fccc41854e3ae767fc691cd0edf15f047e2..4fe5372a519ab9e67803c7b5eda89d273f257202 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] context_menu = { path = "../context_menu" } +collections = { path = "../collections" } db = { path = "../db" } drag_and_drop = { path = "../drag_and_drop" } editor = { path = "../editor" } diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs new file mode 100644 index 0000000000000000000000000000000000000000..ea8e59685b8d10c744c35f8d585bade74268a499 --- /dev/null +++ b/crates/project_panel/src/file_associations.rs @@ -0,0 +1,64 @@ +use std::{path::Path, str, sync::Arc}; + +use collections::HashMap; + +use gpui::{AppContext, AssetSource}; +use serde_derive::Deserialize; + +#[derive(Deserialize, Debug)] +struct TypeConfig { + icon: Arc, +} + +#[derive(Deserialize, Debug)] +pub struct FileAssociations { + suffixes: HashMap, + types: HashMap, +} + +pub const TEXT_FILE_ASSET: &'static str = "icons/file_icons/quill/file.svg"; +const DIRECTORY_TYPE: &'static str = "directory"; +const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_directory"; + +pub fn init(assets: impl AssetSource, cx: &mut AppContext) { + cx.set_global(FileAssociations::new(assets)) +} + +impl FileAssociations { + pub fn new(assets: impl AssetSource) -> Self { + let file = assets.load("icons/file_icons/file_types.json").unwrap(); + serde_json::from_str::(str::from_utf8(&file).unwrap()).unwrap() + } + + pub fn get_icon(path: &Path, cx: &AppContext) -> Option> { + if !cx.has_global::() { + return None; + } + + let this = cx.global::(); + let suffix = path.extension()?.to_str()?; + + this.suffixes + .get(suffix) + .and_then(|type_str| this.types.get(type_str)) + .map(|type_config| type_config.icon.clone()) + } + + pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option> { + if !cx.has_global::() { + return None; + } + + let this = cx.global::(); + + let key = if expanded { + EXPANDED_DIRECTORY_TYPE + } else { + DIRECTORY_TYPE + }; + + this.types + .get(key) + .map(|type_config| type_config.icon.clone()) + } +} diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 06c6c1540c5de0a7ebc0272f47fa0c70329ef86e..dd40fdd56130e35ed10bfc8902e79d2558619c34 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,9 +1,11 @@ +mod file_associations; mod project_panel_settings; use context_menu::{ContextMenu, ContextMenuItem}; use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; +use file_associations::{FileAssociations, TEXT_FILE_ASSET}; use futures::stream::StreamExt; use gpui::{ actions, @@ -15,8 +17,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + Action, AnyElement, AppContext, AssetSource, AsyncAppContext, ClipboardItem, Element, Entity, + ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -44,7 +46,6 @@ use workspace::{ const PROJECT_PANEL_KEY: &'static str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; -const TEXT_FILE_ASSET: &'static str = "icons/radix/file-text.svg"; pub struct ProjectPanel { project: ModelHandle, @@ -131,8 +132,9 @@ pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } -pub fn init(cx: &mut AppContext) { +pub fn init(assets: impl AssetSource, cx: &mut AppContext) { init_settings(cx); + file_associations::init(assets, cx); cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::collapse_selected_entry); cx.add_action(ProjectPanel::select_prev); @@ -1184,15 +1186,12 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in visible_worktree_entries[entry_range].iter() { let status = git_status_setting.then(|| entry.git_status).flatten(); - + let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); let icon = show_file_icons .then(|| match entry.kind { - EntryKind::File(_) => self - .project - .read(cx) - .icon_for_path(&entry.path) + EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx) .or_else(|| Some(TEXT_FILE_ASSET.into())), - _ => None, + _ => FileAssociations::get_folder_icon(is_expanded, cx), }) .flatten(); @@ -1208,7 +1207,7 @@ impl ProjectPanel { depth: entry.path.components().count(), kind: entry.kind, is_ignored: entry.is_ignored, - is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(), + is_expanded, is_selected: self.selection.map_or(false, |e| { e.worktree_id == snapshot.id() && e.entry_id == entry.id }), @@ -1271,37 +1270,35 @@ impl ProjectPanel { .unwrap_or(style.text.color); Flex::row() - .with_child(if kind.is_dir() { + .with_child(if let Some(icon) = &details.icon { + Svg::new(icon.to_string()) + .with_color(style.icon_color) + .constrained() + .with_max_width(style.icon_size) + .with_max_height(style.icon_size) + .aligned() + .constrained() + .with_width(style.icon_size) + } else if kind.is_dir() { if details.is_expanded { - Svg::new("icons/chevron_down_8.svg").with_color(style.icon_color) + Svg::new("icons/chevron_down_8.svg").with_color(style.chevron_color) } else { - Svg::new("icons/chevron_right_8.svg").with_color(style.icon_color) + Svg::new("icons/chevron_right_8.svg").with_color(style.chevron_color) } .constrained() - .with_max_width(style.directory_icon_size) - .with_max_height(style.directory_icon_size) + .with_max_width(style.chevron_size) + .with_max_height(style.chevron_size) .aligned() .constrained() - .with_width(style.directory_icon_size) + .with_width(style.chevron_size) } else { - if let Some(icon) = &details.icon { - Svg::new(icon.to_string()) - .with_color(style.icon_color) - .constrained() - .with_max_width(style.file_icon_size) - .with_max_height(style.file_icon_size) - .aligned() - .constrained() - .with_width(style.file_icon_size) - } else { - Empty::new() - .constrained() - .with_max_width(style.directory_icon_size) - .with_max_height(style.directory_icon_size) - .aligned() - .constrained() - .with_width(style.directory_icon_size) - } + Empty::new() + .constrained() + .with_max_width(style.chevron_size) + .with_max_height(style.chevron_size) + .aligned() + .constrained() + .with_width(style.chevron_size) }) .with_child(if show_editor && editor.is_some() { ChildView::new(editor.as_ref().unwrap(), cx) @@ -2613,7 +2610,7 @@ mod tests { theme::init((), cx); language::init(cx); editor::init_settings(cx); - crate::init(cx); + crate::init((), cx); workspace::init_settings(cx); Project::init_settings(cx); }); @@ -2628,7 +2625,7 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); - crate::init(cx); + crate::init((), cx); workspace::init(app_state.clone(), cx); Project::init_settings(cx); }); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 53a0629198c835782ba3ab5120f7c7111481f2b6..29c0d9ce8ebbd515a85dfd149504ed22d2fd7691 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -480,9 +480,10 @@ pub struct ProjectPanelEntry { #[serde(flatten)] pub container: ContainerStyle, pub text: TextStyle, + pub icon_size: f32, pub icon_color: Color, - pub directory_icon_size: f32, - pub file_icon_size: f32, + pub chevron_color: Color, + pub chevron_size: f32, pub icon_spacing: f32, pub status: EntryStatus, } diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index 5a3fc9d8b815678d649706287b0772574fb0d854..4e89f5cabd99fb2faf120b32943be31d20d2a514 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -1,6 +1,5 @@ name = "TOML" path_suffixes = ["toml"] -icon_path = "icons/radix/gear.svg" line_comment = "# " autoclose_before = ",]}" brackets = [ diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ccf381b5b11b21f674c9e068973efe53cfa98af0..e85113d57d747289df38274df167a7f1ab212627 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -154,7 +154,7 @@ fn main() { file_finder::init(cx); outline::init(cx); project_symbols::init(cx); - project_panel::init(cx); + project_panel::init(Assets, cx); diagnostics::init(cx); search::init(cx); vector_store::init(fs.clone(), http.clone(), languages.clone(), cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9dffc644ae7fd63a5a722db94fd4bdb25a979a99..6bbba0bd02b7da005c9711d00591952baf2594ae 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2334,7 +2334,7 @@ mod tests { editor::init(cx); project_panel::init_settings(cx); pane::init(cx); - project_panel::init(cx); + project_panel::init((), cx); terminal_view::init(cx); ai::init(cx); app_state diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index c2719e935ef566324b36a65ff402b2cc501fd2fd..c3e82de8b0274a45e19b3462374bdac67c0c05d0 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -46,9 +46,10 @@ export default function project_panel(): any { const base_properties = { height: 22, background: background(theme.middle), - icon_color: foreground(theme.middle, "variant"), - directory_icon_size: 7, - file_icon_size: 14, + chevron_color: foreground(theme.middle, "variant"), + icon_color: foreground(theme.middle, "active"), + chevron_size: 7, + icon_size: 14, icon_spacing: 5, text: text(theme.middle, "sans", "variant", { size: "sm" }), status: { From 929a9f97b28e0654ad8b9b942f34c66d2a4fd73b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 20:58:26 -0700 Subject: [PATCH 077/162] Fix tests --- crates/project_panel/src/file_associations.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index ea8e59685b8d10c744c35f8d585bade74268a499..e1fb9b3c64fca52fe1f37bed9dd7b5488c5ea2cc 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -26,8 +26,15 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) { impl FileAssociations { pub fn new(assets: impl AssetSource) -> Self { - let file = assets.load("icons/file_icons/file_types.json").unwrap(); - serde_json::from_str::(str::from_utf8(&file).unwrap()).unwrap() + assets + .load("icons/file_icons/file_types.json") + .map(|file| { + serde_json::from_str::(str::from_utf8(&file).unwrap()).unwrap() + }) + .unwrap_or_else(|_| FileAssociations { + suffixes: HashMap::default(), + types: HashMap::default(), + }) } pub fn get_icon(path: &Path, cx: &AppContext) -> Option> { From 96ef6ab3268e9827bdb87fe6bbe81a2990385e62 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 21:04:47 -0700 Subject: [PATCH 078/162] Add willow license --- script/generate-licenses | 9 ++++++++- script/licenses/willow_license.txt | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 script/licenses/willow_license.txt diff --git a/script/generate-licenses b/script/generate-licenses index 14c9d4c79f1646d4889da82d9ee24c7975003532..b08aa0b0245d8163e52a0183a74139a24717edaa 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -6,6 +6,13 @@ OUTPUT_FILE=$(pwd)/assets/licenses.md > $OUTPUT_FILE +echo -e "# ###### ICON LICENSES ######\n" >> $OUTPUT_FILE + +echo "Generating icon licenses" + +cat script/licenses/willow_license.txt >> $OUTPUT_FILE +echo -e "" >> $OUTPUT_FILE + echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo "Generating theme licenses" @@ -26,4 +33,4 @@ sed -i '' 's/'/'\''/g' $OUTPUT_FILE # The ` '\'' ` thing ends the string, a sed -i '' 's/=/=/g' $OUTPUT_FILE sed -i '' 's/`/`/g' $OUTPUT_FILE sed -i '' 's/<//g' $OUTPUT_FILE \ No newline at end of file +sed -i '' 's/>/>/g' $OUTPUT_FILE diff --git a/script/licenses/willow_license.txt b/script/licenses/willow_license.txt new file mode 100644 index 0000000000000000000000000000000000000000..56bbd45a4519f01533b383fee7d64d78fb1491a0 --- /dev/null +++ b/script/licenses/willow_license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Chad Donohue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 8c855680e7813d10b31f2e8ed92d64cbdfea4b4f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 17 Jul 2023 13:09:47 -0700 Subject: [PATCH 079/162] Make file types live reload --- assets/icons/file_icons/file_types.json | 3 + crates/project_panel/src/file_associations.rs | 58 ++++++++++--------- crates/project_panel/src/project_panel.rs | 15 +++-- crates/zed/src/main.rs | 25 ++++++++ 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 8803647857f18f4156f00d54dd57a5cb343f15e9..401e6d168654017b1a8b96d8aec5523881e54d9f 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -67,6 +67,9 @@ "log": "log" }, "types": { + "default": { + "icon": "icons/file_icons/quill/file.svg" + }, "directory": { "icon": "icons/file_icons/quill/folder.svg" }, diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index e1fb9b3c64fca52fe1f37bed9dd7b5488c5ea2cc..2d3413ef3417f756651f60628f14dd33eb86ec6e 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -4,6 +4,7 @@ use collections::HashMap; use gpui::{AppContext, AssetSource}; use serde_derive::Deserialize; +use util::iife; #[derive(Deserialize, Debug)] struct TypeConfig { @@ -16,9 +17,9 @@ pub struct FileAssociations { types: HashMap, } -pub const TEXT_FILE_ASSET: &'static str = "icons/file_icons/quill/file.svg"; const DIRECTORY_TYPE: &'static str = "directory"; const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_directory"; +pub const FILE_TYPES_ASSET: &'static str = "icons/file_icons/file_types.json"; pub fn init(assets: impl AssetSource, cx: &mut AppContext) { cx.set_global(FileAssociations::new(assets)) @@ -28,8 +29,9 @@ impl FileAssociations { pub fn new(assets: impl AssetSource) -> Self { assets .load("icons/file_icons/file_types.json") - .map(|file| { - serde_json::from_str::(str::from_utf8(&file).unwrap()).unwrap() + .and_then(|file| { + serde_json::from_str::(str::from_utf8(&file).unwrap()) + .map_err(Into::into) }) .unwrap_or_else(|_| FileAssociations { suffixes: HashMap::default(), @@ -37,35 +39,37 @@ impl FileAssociations { }) } - pub fn get_icon(path: &Path, cx: &AppContext) -> Option> { - if !cx.has_global::() { - return None; - } + pub fn get_icon(path: &Path, cx: &AppContext) -> Arc { + iife!({ + let this = cx.has_global::().then(|| cx.global::())?; - let this = cx.global::(); - let suffix = path.extension()?.to_str()?; + iife!({ + let suffix = path.extension()?.to_str()?; - this.suffixes - .get(suffix) - .and_then(|type_str| this.types.get(type_str)) - .map(|type_config| type_config.icon.clone()) + this.suffixes + .get(suffix) + .and_then(|type_str| this.types.get(type_str)) + .map(|type_config| type_config.icon.clone()) + }) + .or_else(|| this.types.get("default").map(|config| config.icon.clone())) + }) + .unwrap_or_else(|| Arc::from("".to_string())) } - pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option> { - if !cx.has_global::() { - return None; - } - - let this = cx.global::(); + pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Arc { + iife!({ + let this = cx.has_global::().then(|| cx.global::())?; - let key = if expanded { - EXPANDED_DIRECTORY_TYPE - } else { - DIRECTORY_TYPE - }; + let key = if expanded { + EXPANDED_DIRECTORY_TYPE + } else { + DIRECTORY_TYPE + }; - this.types - .get(key) - .map(|type_config| type_config.icon.clone()) + this.types + .get(key) + .map(|type_config| type_config.icon.clone()) + }) + .unwrap_or_else(|| Arc::from("".to_string())) } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index dd40fdd56130e35ed10bfc8902e79d2558619c34..f8e1b223d36690c88b2f08e242ce4c40957eaef9 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,11 +1,12 @@ -mod file_associations; +pub mod file_associations; mod project_panel_settings; use context_menu::{ContextMenu, ContextMenuItem}; use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; -use file_associations::{FileAssociations, TEXT_FILE_ASSET}; +use file_associations::FileAssociations; + use futures::stream::StreamExt; use gpui::{ actions, @@ -234,6 +235,10 @@ impl ProjectPanel { }) .detach(); + cx.observe_global::(|_, cx| { + cx.notify(); + }).detach(); + let view_id = cx.view_id(); let mut this = Self { project: project.clone(), @@ -1189,11 +1194,9 @@ impl ProjectPanel { let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); let icon = show_file_icons .then(|| match entry.kind { - EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx) - .or_else(|| Some(TEXT_FILE_ASSET.into())), + EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx), _ => FileAssociations::get_folder_icon(is_expanded, cx), - }) - .flatten(); + }); let mut details = EntryDetails { filename: entry diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e85113d57d747289df38274df167a7f1ab212627..87e00a70c27e0f0a5c6140530eeab7746538afc0 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -166,6 +166,7 @@ fn main() { cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach(); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) .detach(); + watch_file_types(fs.clone(), cx); languages.set_theme(theme::current(cx).clone()); cx.observe_global::({ @@ -685,6 +686,25 @@ async fn watch_languages(fs: Arc, languages: Arc) -> O Some(()) } +#[cfg(debug_assertions)] +fn watch_file_types(fs: Arc, cx: &mut AppContext) { + cx.spawn(|mut cx| async move { + let mut events = fs + .watch( + "assets/icons/file_icons/file_types.json".as_ref(), + Duration::from_millis(100), + ) + .await; + while (events.next().await).is_some() { + cx.update(|cx| { + cx.update_global(|file_types, _| { + *file_types = project_panel::file_associations::FileAssociations::new(Assets); + }); + }) + } + }).detach() +} + #[cfg(not(debug_assertions))] async fn watch_themes(_fs: Arc, _cx: AsyncAppContext) -> Option<()> { None @@ -695,6 +715,11 @@ async fn watch_languages(_: Arc, _: Arc) -> Option<()> None } +#[cfg(not(debug_assertions))] +fn watch_file_types(fs: Arc, cx: &mut AppContext) { + None +} + fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { From aacc4bb8b00f3c17975d51ed024841e8bc48db83 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 17 Jul 2023 13:12:14 -0700 Subject: [PATCH 080/162] fmt --- crates/project_panel/src/project_panel.rs | 12 ++++++------ crates/zed/src/main.rs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f8e1b223d36690c88b2f08e242ce4c40957eaef9..d97c47a339dfde546550fc9b70ebc75659f416a6 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -237,7 +237,8 @@ impl ProjectPanel { cx.observe_global::(|_, cx| { cx.notify(); - }).detach(); + }) + .detach(); let view_id = cx.view_id(); let mut this = Self { @@ -1192,11 +1193,10 @@ impl ProjectPanel { for entry in visible_worktree_entries[entry_range].iter() { let status = git_status_setting.then(|| entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); - let icon = show_file_icons - .then(|| match entry.kind { - EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx), - _ => FileAssociations::get_folder_icon(is_expanded, cx), - }); + let icon = show_file_icons.then(|| match entry.kind { + EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx), + _ => FileAssociations::get_folder_icon(is_expanded, cx), + }); let mut details = EntryDetails { filename: entry diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 87e00a70c27e0f0a5c6140530eeab7746538afc0..901724ae890bdc18f4dcb9ec9de9899b4d6676d6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -702,7 +702,8 @@ fn watch_file_types(fs: Arc, cx: &mut AppContext) { }); }) } - }).detach() + }) + .detach() } #[cfg(not(debug_assertions))] From c754c1e9e24a8c8a7b0b0897d68eb8be367e1e2e Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Wed, 19 Jul 2023 11:28:33 -0600 Subject: [PATCH 081/162] Update icons to new zed file icon set --- assets/icons/file_icons/archive.svg | 5 + assets/icons/file_icons/book.svg | 4 + assets/icons/file_icons/camera.svg | 4 + assets/icons/file_icons/code.svg | 4 + assets/icons/file_icons/database.svg | 5 + assets/icons/file_icons/eslint.svg | 4 + assets/icons/file_icons/file.svg | 5 + assets/icons/file_icons/file_types.json | 168 ++++++++++-------- assets/icons/file_icons/folder-open.svg | 4 + assets/icons/file_icons/folder.svg | 4 + assets/icons/file_icons/git.svg | 6 + assets/icons/file_icons/hash.svg | 6 + assets/icons/file_icons/html.svg | 5 + assets/icons/file_icons/image.svg | 6 + assets/icons/file_icons/info.svg | 5 + assets/icons/file_icons/lock.svg | 6 + assets/icons/file_icons/notebook.svg | 6 + assets/icons/file_icons/package.svg | 3 + assets/icons/file_icons/prettier.svg | 12 ++ assets/icons/file_icons/quill/anchor.svg | 11 -- assets/icons/file_icons/quill/archive.svg | 12 -- assets/icons/file_icons/quill/book.svg | 11 -- assets/icons/file_icons/quill/camera.svg | 12 -- assets/icons/file_icons/quill/code.svg | 12 -- assets/icons/file_icons/quill/database.svg | 11 -- assets/icons/file_icons/quill/eslint.svg | 10 -- assets/icons/file_icons/quill/file.svg | 11 -- assets/icons/file_icons/quill/folder-open.svg | 12 -- assets/icons/file_icons/quill/folder.svg | 11 -- assets/icons/file_icons/quill/git.svg | 11 -- assets/icons/file_icons/quill/hash.svg | 11 -- assets/icons/file_icons/quill/html.svg | 12 -- assets/icons/file_icons/quill/image.svg | 12 -- assets/icons/file_icons/quill/info.svg | 12 -- assets/icons/file_icons/quill/lock.svg | 11 -- assets/icons/file_icons/quill/package.svg | 11 -- assets/icons/file_icons/quill/prettier.svg | 22 --- assets/icons/file_icons/quill/settings.svg | 11 -- assets/icons/file_icons/quill/terminal.svg | 9 - assets/icons/file_icons/rust.svg | 4 + assets/icons/file_icons/settings.svg | 4 + assets/icons/file_icons/terminal.svg | 5 + assets/icons/file_icons/typescript.svg | 5 + script/generate-licenses | 7 - script/licenses/willow_license.txt | 21 --- styles/src/style_tree/project_panel.ts | 20 +-- 46 files changed, 218 insertions(+), 345 deletions(-) create mode 100644 assets/icons/file_icons/archive.svg create mode 100644 assets/icons/file_icons/book.svg create mode 100644 assets/icons/file_icons/camera.svg create mode 100644 assets/icons/file_icons/code.svg create mode 100644 assets/icons/file_icons/database.svg create mode 100644 assets/icons/file_icons/eslint.svg create mode 100644 assets/icons/file_icons/file.svg create mode 100644 assets/icons/file_icons/folder-open.svg create mode 100644 assets/icons/file_icons/folder.svg create mode 100644 assets/icons/file_icons/git.svg create mode 100644 assets/icons/file_icons/hash.svg create mode 100644 assets/icons/file_icons/html.svg create mode 100644 assets/icons/file_icons/image.svg create mode 100644 assets/icons/file_icons/info.svg create mode 100644 assets/icons/file_icons/lock.svg create mode 100644 assets/icons/file_icons/notebook.svg create mode 100644 assets/icons/file_icons/package.svg create mode 100644 assets/icons/file_icons/prettier.svg delete mode 100644 assets/icons/file_icons/quill/anchor.svg delete mode 100644 assets/icons/file_icons/quill/archive.svg delete mode 100644 assets/icons/file_icons/quill/book.svg delete mode 100644 assets/icons/file_icons/quill/camera.svg delete mode 100644 assets/icons/file_icons/quill/code.svg delete mode 100644 assets/icons/file_icons/quill/database.svg delete mode 100644 assets/icons/file_icons/quill/eslint.svg delete mode 100644 assets/icons/file_icons/quill/file.svg delete mode 100644 assets/icons/file_icons/quill/folder-open.svg delete mode 100644 assets/icons/file_icons/quill/folder.svg delete mode 100644 assets/icons/file_icons/quill/git.svg delete mode 100644 assets/icons/file_icons/quill/hash.svg delete mode 100644 assets/icons/file_icons/quill/html.svg delete mode 100644 assets/icons/file_icons/quill/image.svg delete mode 100644 assets/icons/file_icons/quill/info.svg delete mode 100644 assets/icons/file_icons/quill/lock.svg delete mode 100644 assets/icons/file_icons/quill/package.svg delete mode 100644 assets/icons/file_icons/quill/prettier.svg delete mode 100644 assets/icons/file_icons/quill/settings.svg delete mode 100644 assets/icons/file_icons/quill/terminal.svg create mode 100644 assets/icons/file_icons/rust.svg create mode 100644 assets/icons/file_icons/settings.svg create mode 100644 assets/icons/file_icons/terminal.svg create mode 100644 assets/icons/file_icons/typescript.svg delete mode 100644 script/licenses/willow_license.txt diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg new file mode 100644 index 0000000000000000000000000000000000000000..f11115cdcee09d1b40317c525cd3d256ea6be557 --- /dev/null +++ b/assets/icons/file_icons/archive.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/book.svg b/assets/icons/file_icons/book.svg new file mode 100644 index 0000000000000000000000000000000000000000..890b8988a30c703d87a9b27e408ab6730c215fdf --- /dev/null +++ b/assets/icons/file_icons/book.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/camera.svg b/assets/icons/file_icons/camera.svg new file mode 100644 index 0000000000000000000000000000000000000000..d8b9cf459c512343d135a5bfe5c8928874cd0862 --- /dev/null +++ b/assets/icons/file_icons/camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/code.svg b/assets/icons/file_icons/code.svg new file mode 100644 index 0000000000000000000000000000000000000000..2733e4b535af74fbf795b51fe8090343396bc953 --- /dev/null +++ b/assets/icons/file_icons/code.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/database.svg b/assets/icons/file_icons/database.svg new file mode 100644 index 0000000000000000000000000000000000000000..9072e091b5da2feb5d728450a2022d72bf674081 --- /dev/null +++ b/assets/icons/file_icons/database.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/eslint.svg b/assets/icons/file_icons/eslint.svg new file mode 100644 index 0000000000000000000000000000000000000000..ec5051d4474957e33034d32e79e646124adbe222 --- /dev/null +++ b/assets/icons/file_icons/eslint.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/file.svg b/assets/icons/file_icons/file.svg new file mode 100644 index 0000000000000000000000000000000000000000..cc422734e77e3e226b2c2452f006163f4de12ea6 --- /dev/null +++ b/assets/icons/file_icons/file.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 401e6d168654017b1a8b96d8aec5523881e54d9f..d1b604f05e62e3fefe4de15c68f4f28dce754855 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -1,110 +1,134 @@ { "suffixes": { - "jpg": "image", - "jpeg": "image", - "tiff": "image", - "svg": "image", - "psd": "image", - "png": "image", - "gif": "image", + "aac": "audio", + "bash": "terminal", "bmp": "image", + "c": "code", + "conf": "settings", + "cpp": "code", + "css": "code", + "doc": "document", + "docx": "document", + "eslintrc": "eslint", + "eslintrc.js": "eslint", + "eslintrc.json": "eslint", + "flac": "audio", + "fish": "terminal", + "gitattributes": "vcs", + "gitignore": "vcs", + "gif": "image", + "go": "code", + "h": "code", + "handlebars": "code", + "hbs": "template", + "htm": "template", + "html": "template", + "hpp": "code", "ico": "image", + "ini": "settings", + "java": "code", + "jpeg": "image", + "jpg": "image", + "js": "code", + "json": "storage", + "lock": "lock", + "log": "log", + "md": "document", + "mp3": "audio", "mp4": "video", - "webm": "video", + "ods": "document", + "odp": "document", + "odt": "document", "ogg": "video", - "mp3": "audio", - "wav": "audio", - "flac": "audio", - "aac": "audio", "pdf": "document", - "doc": "document", - "docx": "document", - "xls": "document", - "xlsx": "document", + "php": "code", + "png": "image", "ppt": "document", "pptx": "document", - "odt": "document", - "ods": "document", - "odp": "document", - "txt": "document", - "rtf": "document", - "md": "document", - "html": "template", - "htm": "template", - "xml": "template", - "hbs": "template", - "handlebars": "template", - "js": "code", - "css": "code", - "php": "code", - "c": "code", - "cpp": "code", - "h": "code", - "hpp": "code", - "java": "code", + "prettierrc": "prettier", + "ps1": "terminal", + "psd": "image", "py": "code", - "swift": "code", - "go": "code", "rb": "code", - "rs": "code", "rkt": "code", + "rs": "rust", + "rtf": "document", "scm": "code", + "sh": "terminal", "sql": "code", - "json": "settings", - "ini": "settings", + "svg": "image", + "swift": "code", + "tiff": "image", + "toml": "settings", + "ts": "typescript", + "tsx": "code", + "txt": "document", + "wav": "audio", + "webm": "video", + "xls": "document", + "xlsx": "document", + "xml": "template", "yaml": "settings", "yml": "settings", - "toml": "settings", - "conf": "settings", - "lock": "settings", - "gitignore": "vcs", - "gitattributes": "vcs", - "ps1": "terminal", - "sh": "terminal", - "bash": "terminal", - "zsh": "terminal", - "fish": "terminal", - "log": "log" + "zsh": "terminal" }, "types": { + "audio": { + "icon": "icons/file_icons/file.svg" + }, + "code": { + "icon": "icons/file_icons/code.svg" + }, "default": { - "icon": "icons/file_icons/quill/file.svg" + "icon": "icons/file_icons/file.svg" }, "directory": { - "icon": "icons/file_icons/quill/folder.svg" + "icon": "icons/file_icons/folder.svg" + }, + "document": { + "icon": "icons/file_icons/book.svg" + }, + "eslint": { + "icon": "icons/file_icons/eslint.svg" }, "expanded_directory": { - "icon": "icons/file_icons/quill/folder-open.svg" + "icon": "icons/file_icons/folder-open.svg" }, "image": { - "icon": "icons/file_icons/quill/image.svg" - }, - "video": { - "icon": "icons/file_icons/quill/file.svg" + "icon": "icons/file_icons/image.svg" }, - "audio": { - "icon": "icons/file_icons/quill/file.svg" + "lock": { + "icon": "icons/file_icons/lock.svg" }, - "document": { - "icon": "icons/file_icons/quill/book.svg" + "log": { + "icon": "icons/file_icons/info.svg" }, - "template": { - "icon": "icons/file_icons/quill/html.svg" + "prettier": { + "icon": "icons/file_icons/prettier.svg" }, - "code": { - "icon": "icons/file_icons/quill/code.svg" + "rust": { + "icon": "icons/file_icons/rust.svg" }, "settings": { - "icon": "icons/file_icons/quill/settings.svg" + "icon": "icons/file_icons/settings.svg" }, - "vcs": { - "icon": "icons/file_icons/quill/git.svg" + "storage": { + "icon": "icons/file_icons/database.svg" + }, + "template": { + "icon": "icons/file_icons/html.svg" }, "terminal": { - "icon": "icons/file_icons/quill/terminal.svg" + "icon": "icons/file_icons/terminal.svg" }, - "log": { - "icon": "icons/file_icons/quill/info.svg" + "typescript": { + "icon": "icons/file_icons/typescript.svg" + }, + "vcs": { + "icon": "icons/file_icons/git.svg" + }, + "video": { + "icon": "icons/file_icons/file.svg" } } } diff --git a/assets/icons/file_icons/folder-open.svg b/assets/icons/file_icons/folder-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..65c5744049b9c3271fc494fc290ec6a88b9519bf --- /dev/null +++ b/assets/icons/file_icons/folder-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg new file mode 100644 index 0000000000000000000000000000000000000000..5157bae839ef47be384f3d33aa4472f2e5609f6f --- /dev/null +++ b/assets/icons/file_icons/folder.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/git.svg b/assets/icons/file_icons/git.svg new file mode 100644 index 0000000000000000000000000000000000000000..82d8c8f57c014e7c2bf679b34dcdf4da0c668049 --- /dev/null +++ b/assets/icons/file_icons/git.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/hash.svg b/assets/icons/file_icons/hash.svg new file mode 100644 index 0000000000000000000000000000000000000000..edd04626782e52bc2f3c1a73a08f2de166828c33 --- /dev/null +++ b/assets/icons/file_icons/hash.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/html.svg b/assets/icons/file_icons/html.svg new file mode 100644 index 0000000000000000000000000000000000000000..ba9ec142995d5abb1ce0a66edcd1bc8467773202 --- /dev/null +++ b/assets/icons/file_icons/html.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg new file mode 100644 index 0000000000000000000000000000000000000000..ee5e49f2d480d0f6f25ecc95b49aff1a5dbf5871 --- /dev/null +++ b/assets/icons/file_icons/image.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/info.svg b/assets/icons/file_icons/info.svg new file mode 100644 index 0000000000000000000000000000000000000000..e84ae7c6282fc21eb627563aa3512f111bfddac9 --- /dev/null +++ b/assets/icons/file_icons/info.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg new file mode 100644 index 0000000000000000000000000000000000000000..3051bbf801d4fe4f6dd14bc71d924fc9c87a32f6 --- /dev/null +++ b/assets/icons/file_icons/lock.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/notebook.svg b/assets/icons/file_icons/notebook.svg new file mode 100644 index 0000000000000000000000000000000000000000..6eaec16d0a34ec83aec053f6387049c2fc7a6f0e --- /dev/null +++ b/assets/icons/file_icons/notebook.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/package.svg b/assets/icons/file_icons/package.svg new file mode 100644 index 0000000000000000000000000000000000000000..2a692ba4b4d1f89bfce502ce9a7354dde7c90459 --- /dev/null +++ b/assets/icons/file_icons/package.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/prettier.svg b/assets/icons/file_icons/prettier.svg new file mode 100644 index 0000000000000000000000000000000000000000..2d2c6ee7193e5d6c6a281f777b13fa4ed55c6bf2 --- /dev/null +++ b/assets/icons/file_icons/prettier.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/file_icons/quill/anchor.svg b/assets/icons/file_icons/quill/anchor.svg deleted file mode 100644 index 4828578ee0818bbedfa98726e26badd82e130d65..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/anchor.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - anchor_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/archive.svg b/assets/icons/file_icons/quill/archive.svg deleted file mode 100644 index c78ca0cff6f18a3396f7ff3e7a414c8cdfb3016c..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/archive.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - archive_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/book.svg b/assets/icons/file_icons/quill/book.svg deleted file mode 100644 index af918b5c612568ca648ea4c647df0f0d0498d774..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/book.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - book_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/camera.svg b/assets/icons/file_icons/quill/camera.svg deleted file mode 100644 index f861af607c118fda7abfbd1deb026eebe29393cf..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/camera.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - camera_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/code.svg b/assets/icons/file_icons/quill/code.svg deleted file mode 100644 index a844740f1ab90857731d3e50e6c3d6531a911638..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/code.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - code_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/database.svg b/assets/icons/file_icons/quill/database.svg deleted file mode 100644 index 8c98d5ac164bd8a5ba95d545208a25f18a24a6b2..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/database.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - database_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/eslint.svg b/assets/icons/file_icons/quill/eslint.svg deleted file mode 100644 index 880689293a3a0e43948d0a1c8737b44caae717e7..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/eslint.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - eslint_dark - Created with Sketch. - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/file.svg b/assets/icons/file_icons/quill/file.svg deleted file mode 100644 index 492c383ab6139506caf56c2f585c7dbda44c0ddf..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/file.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - file_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/folder-open.svg b/assets/icons/file_icons/quill/folder-open.svg deleted file mode 100644 index 00a94c199f9dcea24ba548244ac75b59a794f44f..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/folder-open.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - folder-open_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/folder.svg b/assets/icons/file_icons/quill/folder.svg deleted file mode 100644 index 9cc5b4a8c9b20cdde8bf4fafb763cb22aae71df0..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/folder.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - folder_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/git.svg b/assets/icons/file_icons/quill/git.svg deleted file mode 100644 index 830a7f956570da134f4727fde9631937755cb618..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/git.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - git_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/hash.svg b/assets/icons/file_icons/quill/hash.svg deleted file mode 100644 index 36366625fe459476ce954cd5f847897ae90cb21d..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/hash.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - hash_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/html.svg b/assets/icons/file_icons/quill/html.svg deleted file mode 100644 index 7704575f24a8bbad804aa89346b8e40925cf0ca5..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/html.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - html_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/image.svg b/assets/icons/file_icons/quill/image.svg deleted file mode 100644 index 0ec9583edd5487becc9d9b48f819c75d4712cd21..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/image.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - image_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/info.svg b/assets/icons/file_icons/quill/info.svg deleted file mode 100644 index af3fa9d39d92bbb650cbea21eae1fed218bbf22b..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/info.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - info_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/lock.svg b/assets/icons/file_icons/quill/lock.svg deleted file mode 100644 index a1e36e6c122b5a8b47ff43e90aedda39fdbe61de..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/lock.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - lock_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/package.svg b/assets/icons/file_icons/quill/package.svg deleted file mode 100644 index 9bda169cf59af3c591617ee1bd6a25f3b72d691d..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/package.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - package_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/prettier.svg b/assets/icons/file_icons/quill/prettier.svg deleted file mode 100644 index ba7b340654362a375473f913508ce9070e04ab73..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/prettier.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - prettier_dark - Created with Sketch. - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/settings.svg b/assets/icons/file_icons/quill/settings.svg deleted file mode 100644 index f0209bf3c22ba2f0337878265c40582e1e72d6c5..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/settings.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - settings_dark - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/quill/terminal.svg b/assets/icons/file_icons/quill/terminal.svg deleted file mode 100644 index 964f44251ffa0fc23954aaa7abb266c295344c6b..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/quill/terminal.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - terminal_dark - Created with Sketch. - - - - \ No newline at end of file diff --git a/assets/icons/file_icons/rust.svg b/assets/icons/file_icons/rust.svg new file mode 100644 index 0000000000000000000000000000000000000000..1802f0e190d86041be658afda706b5bc36db1644 --- /dev/null +++ b/assets/icons/file_icons/rust.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/settings.svg b/assets/icons/file_icons/settings.svg new file mode 100644 index 0000000000000000000000000000000000000000..e827055d1972ffd657fcc6129775a66be37d8e5f --- /dev/null +++ b/assets/icons/file_icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/terminal.svg b/assets/icons/file_icons/terminal.svg new file mode 100644 index 0000000000000000000000000000000000000000..939587c53ecc97042821439590ab49ff2f980853 --- /dev/null +++ b/assets/icons/file_icons/terminal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/typescript.svg b/assets/icons/file_icons/typescript.svg new file mode 100644 index 0000000000000000000000000000000000000000..179b3d85724edb25889a62b80d1bd46d510fde5e --- /dev/null +++ b/assets/icons/file_icons/typescript.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/script/generate-licenses b/script/generate-licenses index b08aa0b0245d8163e52a0183a74139a24717edaa..9a2fe8921a8fe06e4716b98bcec9b2b28af4a994 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -6,13 +6,6 @@ OUTPUT_FILE=$(pwd)/assets/licenses.md > $OUTPUT_FILE -echo -e "# ###### ICON LICENSES ######\n" >> $OUTPUT_FILE - -echo "Generating icon licenses" - -cat script/licenses/willow_license.txt >> $OUTPUT_FILE -echo -e "" >> $OUTPUT_FILE - echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo "Generating theme licenses" diff --git a/script/licenses/willow_license.txt b/script/licenses/willow_license.txt deleted file mode 100644 index 56bbd45a4519f01533b383fee7d64d78fb1491a0..0000000000000000000000000000000000000000 --- a/script/licenses/willow_license.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Chad Donohue - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index c3e82de8b0274a45e19b3462374bdac67c0c05d0..4a252572d53cd3c4703b7e9f4be33182ffefe837 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -50,7 +50,7 @@ export default function project_panel(): any { icon_color: foreground(theme.middle, "active"), chevron_size: 7, icon_size: 14, - icon_spacing: 5, + icon_spacing: 6, text: text(theme.middle, "sans", "variant", { size: "sm" }), status: { ...git_status, @@ -64,17 +64,17 @@ export default function project_panel(): any { const unselected_default_style = merge( base_properties, unselected?.default ?? {}, - {} + {}, ) const unselected_hovered_style = merge( base_properties, { background: background(theme.middle, "hovered") }, - unselected?.hovered ?? {} + unselected?.hovered ?? {}, ) const unselected_clicked_style = merge( base_properties, { background: background(theme.middle, "pressed") }, - unselected?.clicked ?? {} + unselected?.clicked ?? {}, ) const selected_default_style = merge( base_properties, @@ -82,7 +82,7 @@ export default function project_panel(): any { background: background(theme.lowest), text: text(theme.lowest, "sans", { size: "sm" }), }, - selected_style?.default ?? {} + selected_style?.default ?? {}, ) const selected_hovered_style = merge( base_properties, @@ -90,7 +90,7 @@ export default function project_panel(): any { background: background(theme.lowest, "hovered"), text: text(theme.lowest, "sans", { size: "sm" }), }, - selected_style?.hovered ?? {} + selected_style?.hovered ?? {}, ) const selected_clicked_style = merge( base_properties, @@ -98,7 +98,7 @@ export default function project_panel(): any { background: background(theme.lowest, "pressed"), text: text(theme.lowest, "sans", { size: "sm" }), }, - selected_style?.clicked ?? {} + selected_style?.clicked ?? {}, ) return toggleable({ @@ -157,7 +157,7 @@ export default function project_panel(): any { }), background: background(theme.middle), padding: { left: 6, right: 6, top: 0, bottom: 6 }, - indent_width: 12, + indent_width: 20, entry: default_entry, dragged_entry: { ...default_entry.inactive.default, @@ -175,7 +175,7 @@ export default function project_panel(): any { default: { icon_color: foreground(theme.middle, "variant"), }, - } + }, ), cut_entry: entry( { @@ -190,7 +190,7 @@ export default function project_panel(): any { size: "sm", }), }, - } + }, ), filename_editor: { background: background(theme.middle, "on"), From f4413b0969b26361a822754f8e00aea9f3fd1229 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Wed, 19 Jul 2023 12:07:00 -0600 Subject: [PATCH 082/162] =?UTF-8?q?Fix=20files=20that=20don=E2=80=99t=20ha?= =?UTF-8?q?ve=20a=20prefix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/file_icons/file_types.json | 1 + crates/project_panel/src/file_associations.rs | 9 ++++++++- styles/src/style_tree/project_panel.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index d1b604f05e62e3fefe4de15c68f4f28dce754855..b1da28d2d638c30396da7e8e96acd10935e7b5a1 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -46,6 +46,7 @@ "ppt": "document", "pptx": "document", "prettierrc": "prettier", + "prettierignore": "prettier", "ps1": "terminal", "psd": "image", "py": "code", diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 2d3413ef3417f756651f60628f14dd33eb86ec6e..e692031704ec1d4d4143ccbf9eb1cae38cc9f36e 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -44,7 +44,14 @@ impl FileAssociations { let this = cx.has_global::().then(|| cx.global::())?; iife!({ - let suffix = path.extension()?.to_str()?; + let suffix = + path.file_name() + .and_then(|os_str| os_str.to_str()) + .and_then(|file_name| { + file_name + .find('.') + .and_then(|dot_index| file_name.get(dot_index + 1..)) + } )?; this.suffixes .get(suffix) diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index 4a252572d53cd3c4703b7e9f4be33182ffefe837..e239f9a84023088d988f74e709c6485ade8a9510 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -47,7 +47,7 @@ export default function project_panel(): any { height: 22, background: background(theme.middle), chevron_color: foreground(theme.middle, "variant"), - icon_color: foreground(theme.middle, "active"), + icon_color: with_opacity(foreground(theme.middle, "active"), 0.3), chevron_size: 7, icon_size: 14, icon_spacing: 6, From e3f9a01f6b22f82f065b9a583854c58426d60ef5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 11:10:01 -0700 Subject: [PATCH 083/162] fmt --- crates/project_panel/src/file_associations.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index e692031704ec1d4d4143ccbf9eb1cae38cc9f36e..3aaa1689e069baea28f4e0f1c9fe1011207aa50a 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -44,14 +44,14 @@ impl FileAssociations { let this = cx.has_global::().then(|| cx.global::())?; iife!({ - let suffix = - path.file_name() - .and_then(|os_str| os_str.to_str()) - .and_then(|file_name| { - file_name - .find('.') - .and_then(|dot_index| file_name.get(dot_index + 1..)) - } )?; + let suffix = path + .file_name() + .and_then(|os_str| os_str.to_str()) + .and_then(|file_name| { + file_name + .find('.') + .and_then(|dot_index| file_name.get(dot_index + 1..)) + })?; this.suffixes .get(suffix) From 9c9ce15afc80ebb125b5b0ab8b92fe74491c890f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 11:14:31 -0700 Subject: [PATCH 084/162] Add a few more spare associations --- assets/icons/file_icons/file_types.json | 14 ++++++++++++++ crates/project_panel/src/file_associations.rs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index b1da28d2d638c30396da7e8e96acd10935e7b5a1..4f3f8160d7e722b23c63c01db387527dd0eb8448 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -6,6 +6,7 @@ "c": "code", "conf": "settings", "cpp": "code", + "cc": "code", "css": "code", "doc": "document", "docx": "document", @@ -23,6 +24,7 @@ "hbs": "template", "htm": "template", "html": "template", + "svelte": "template", "hpp": "code", "ico": "image", "ini": "settings", @@ -34,6 +36,7 @@ "lock": "lock", "log": "log", "md": "document", + "mdx": "document", "mp3": "audio", "mp4": "video", "ods": "document", @@ -56,6 +59,17 @@ "rtf": "document", "scm": "code", "sh": "terminal", + "bashrc": "terminal", + "bash_profile": "terminal", + "bash_aliases": "terminal", + "bash_logout": "terminal", + "profile": "terminal", + "zshrc": "terminal", + "zshenv": "terminal", + "zsh_profile": "terminal", + "zsh_aliases": "terminal", + "zsh_histfile": "terminal", + "zlogin": "terminal", "sql": "code", "svg": "image", "swift": "code", diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 3aaa1689e069baea28f4e0f1c9fe1011207aa50a..6e2e373d766b02c04fb4ecd9cf4a4ba2cc0d3a0c 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -43,6 +43,8 @@ impl FileAssociations { iife!({ let this = cx.has_global::().then(|| cx.global::())?; + // FIXME: Associate a type with the languages and have the file's langauge + // override these associations iife!({ let suffix = path .file_name() From 64d134a0dc1d7ba220591d83b7fa8847b5599df9 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 19 Jul 2023 15:32:41 -0400 Subject: [PATCH 085/162] Update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3ce94d08b918309552c69950671af25c266cd9be..5a3161c888fb15b5dd8f3dbc9538de04bade30ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6470,7 +6470,7 @@ name = "search" version = "0.1.0" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags", "client", "collections", "editor", From 34488ca8631c47c6a65402761ab084b8bba69a03 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 19 Jul 2023 15:33:58 -0400 Subject: [PATCH 086/162] v0.97.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a3161c888fb15b5dd8f3dbc9538de04bade30ab..e2e038fb537ecc5b36831478c429b6886d04fbef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9450,7 +9450,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.96.0" +version = "0.97.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f749fb6e68df122a859f2a983dc2d5fa87afe27a..c5bf313701e76f1ea7084f7299f7596b7b0306df 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.96.0" +version = "0.97.0" publish = false [lib] From 5ceb258b3e9f346909336a75a12479bef786edb0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 12:34:24 -0700 Subject: [PATCH 087/162] Mute mics by default Fix bug when file ends in line with 1 more digit displayed than previous lines Remove stale UI elements from voice call development --- Cargo.lock | 6 ++- assets/settings/default.json | 5 ++ crates/call/Cargo.toml | 4 ++ crates/call/src/call.rs | 4 ++ crates/call/src/call_settings.rs | 27 +++++++++++ crates/call/src/room.rs | 41 +++++++++++++---- crates/collab_ui/src/collab_titlebar_item.rs | 8 ++-- crates/collab_ui/src/collab_ui.rs | 16 +------ crates/editor/src/element.rs | 2 +- .../Sources/LiveKitBridge/LiveKitBridge.swift | 46 +++++++++++++------ crates/live_kit_client/examples/test_app.rs | 2 +- crates/live_kit_client/src/prod.rs | 23 ++++++++-- crates/live_kit_client/src/test.rs | 14 +++++- 13 files changed, 147 insertions(+), 51 deletions(-) create mode 100644 crates/call/src/call_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 3ce94d08b918309552c69950671af25c266cd9be..91b165ca6862945df4be0ff9ee4b217735786f83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1052,6 +1052,10 @@ dependencies = [ "media", "postage", "project", + "schemars", + "serde", + "serde_derive", + "serde_json", "settings", "util", ] @@ -6470,7 +6474,7 @@ name = "search" version = "0.1.0" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags", "client", "collections", "editor", diff --git a/assets/settings/default.json b/assets/settings/default.json index e1f2d932705003342ed75ddbd125d0b3c3436ef3..d35049a84d0ecb800f20fa5a0111ed378cd64c87 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -66,6 +66,11 @@ // 3. Draw all invisible symbols: // "all" "show_whitespaces": "selection", + // Settings related to calls in Zed + "calls": { + // Join calls with the microphone muted by default + "mute_on_join": true + }, // Scrollbar related settings "scrollbar": { // When to show the scrollbar in the editor. diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 61f35932479311de30df72ac0cc8ee17dcabcc38..eb448d8d8d089369c724f49e5911a8946598f8a4 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -36,6 +36,10 @@ anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true postage.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_derive.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index cf6dd1799ce53e2d26d53012eb8cd21867d6baef..1ad7dbc1fc09fdb5fa81f893a7aec6cdd5a1eac4 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,9 +1,11 @@ pub mod participant; pub mod room; +pub mod call_settings; use std::sync::Arc; use anyhow::{anyhow, Result}; +use call_settings::CallSettings; use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore}; use collections::HashSet; use futures::{future::Shared, FutureExt}; @@ -19,6 +21,8 @@ pub use participant::ParticipantLocation; pub use room::Room; pub fn init(client: Arc, user_store: ModelHandle, cx: &mut AppContext) { + settings::register::(cx); + let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx)); cx.set_global(active_call); } diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..356ae3ef1984763656df87cacb42c5a6be33eee2 --- /dev/null +++ b/crates/call/src/call_settings.rs @@ -0,0 +1,27 @@ +use schemars::JsonSchema; +use serde_derive::{Serialize, Deserialize}; +use settings::Setting; + +#[derive(Deserialize, Debug)] +pub struct CallSettings { + pub mute_on_join: bool, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct CallSettingsContent { + pub mute_on_join: Option, +} + +impl Setting for CallSettings { + const KEY: Option<&'static str> = Some("calls"); + + type FileContent = CallSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 87e6faf988d58290c8b0c659887e021cacd1abc2..d57d4f711bfab69036093ae963ed4c0469b1da7b 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1,4 +1,5 @@ use crate::{ + call_settings::CallSettings, participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack}, IncomingCall, }; @@ -19,7 +20,7 @@ use live_kit_client::{ }; use postage::stream::Stream; use project::Project; -use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; +use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration, panic::Location}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -153,8 +154,10 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; - this.update(&mut cx, |this, cx| this.share_microphone(cx)) - .await?; + if !cx.read(|cx| settings::get::(cx).mute_on_join) { + this.update(&mut cx, |this, cx| this.share_microphone(cx)) + .await?; + } anyhow::Ok(()) }) @@ -656,7 +659,7 @@ impl Room { peer_id, projects: participant.projects, location, - muted: false, + muted: true, speaking: false, video_tracks: Default::default(), audio_tracks: Default::default(), @@ -670,6 +673,10 @@ impl Room { live_kit.room.remote_video_tracks(&user.id.to_string()); let audio_tracks = live_kit.room.remote_audio_tracks(&user.id.to_string()); + let publications = live_kit + .room + .remote_audio_track_publications(&user.id.to_string()); + for track in video_tracks { this.remote_video_track_updated( RemoteVideoTrackUpdate::Subscribed(track), @@ -677,9 +684,15 @@ impl Room { ) .log_err(); } - for track in audio_tracks { + + for (track, publication) in + audio_tracks.iter().zip(publications.iter()) + { this.remote_audio_track_updated( - RemoteAudioTrackUpdate::Subscribed(track), + RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + ), cx, ) .log_err(); @@ -819,8 +832,8 @@ impl Room { cx.notify(); } RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { + let mut found = false; for participant in &mut self.remote_participants.values_mut() { - let mut found = false; for track in participant.audio_tracks.values() { if track.sid() == track_id { found = true; @@ -832,16 +845,20 @@ impl Room { break; } } + cx.notify(); } - RemoteAudioTrackUpdate::Subscribed(track) => { + RemoteAudioTrackUpdate::Subscribed(track, publication) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.audio_tracks.insert(track_id.clone(), track); + participant.muted = publication.is_muted(); + cx.emit(Event::RemoteAudioTracksChanged { participant_id: participant.peer_id, }); @@ -1053,7 +1070,7 @@ impl Room { self.live_kit .as_ref() .and_then(|live_kit| match &live_kit.microphone_track { - LocalTrack::None => None, + LocalTrack::None => Some(true), LocalTrack::Pending { muted, .. } => Some(*muted), LocalTrack::Published { muted, .. } => Some(*muted), }) @@ -1070,7 +1087,9 @@ impl Room { self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } + #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { + dbg!(Location::caller()); if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); } else if self.is_sharing_mic() { @@ -1244,6 +1263,10 @@ impl Room { pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { let should_mute = !self.is_muted(); if let Some(live_kit) = self.live_kit.as_mut() { + if matches!(live_kit.microphone_track, LocalTrack::None) { + return Ok(self.share_microphone(cx)); + } + let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; live_kit.muted_by_user = should_mute; diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6cfc9d8e30e0dfc7bf97f0ff8e8b94a4722e2b44..ce8d10d655d76b018aef855a934c95e1d7b1640c 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -652,10 +652,10 @@ impl CollabTitlebarItem { let is_muted = room.read(cx).is_muted(); if is_muted { icon = "icons/radix/mic-mute.svg"; - tooltip = "Unmute microphone\nRight click for options"; + tooltip = "Unmute microphone"; } else { icon = "icons/radix/mic.svg"; - tooltip = "Mute microphone\nRight click for options"; + tooltip = "Mute microphone"; } let titlebar = &theme.titlebar; @@ -705,10 +705,10 @@ impl CollabTitlebarItem { let is_deafened = room.read(cx).is_deafened().unwrap_or(false); if is_deafened { icon = "icons/radix/speaker-off.svg"; - tooltip = "Unmute speakers\nRight click for options"; + tooltip = "Unmute speakers"; } else { icon = "icons/radix/speaker-loud.svg"; - tooltip = "Mute speakers\nRight click for options"; + tooltip = "Mute speakers"; } let titlebar = &theme.titlebar; diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 7608fdbfee7f96e0f2f0ac0e9d4a3c580c33318d..dbdeb45573e15aa57c7468ea1ce149ea94f86710 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -18,13 +18,7 @@ use workspace::AppState; actions!( collab, - [ - ToggleScreenSharing, - ToggleMute, - ToggleDeafen, - LeaveCall, - ShareMicrophone - ] + [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] ); pub fn init(app_state: &Arc, cx: &mut AppContext) { @@ -40,7 +34,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { cx.add_global_action(toggle_screen_sharing); cx.add_global_action(toggle_mute); cx.add_global_action(toggle_deafen); - cx.add_global_action(share_microphone); } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { @@ -85,10 +78,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { .log_err(); } } - -pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::share_microphone) - .detach_and_log_err(cx) - } -} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4f4aa7477d6f0461795cb2e1b6366cd101e1f6df..4962b08db2175c10923b3e32ee455ff51a26be46 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1311,7 +1311,7 @@ impl EditorElement { } fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { - let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1; + let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; let style = &self.style; cx.text_layout_cache() diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 40d3641db23afcbac801b7f5f2daa15411c15f72..5f22acf581686d0a3ffc3ae69962c00f271dccb1 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -6,7 +6,7 @@ import ScreenCaptureKit class LKRoomDelegate: RoomDelegate { var data: UnsafeRawPointer var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void - var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void + var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void @@ -16,7 +16,7 @@ class LKRoomDelegate: RoomDelegate { init( data: UnsafeRawPointer, onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, @@ -43,7 +43,7 @@ class LKRoomDelegate: RoomDelegate { if track.kind == .video { self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) } else if track.kind == .audio { - self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) + self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) } } @@ -52,12 +52,12 @@ class LKRoomDelegate: RoomDelegate { self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) } } - + func room(_ room: Room, didUpdate speakers: [Participant]) { guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } self.onActiveSpeakersChanged(self.data, speaker_ids) } - + func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { if track.kind == .video { self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) @@ -104,7 +104,7 @@ class LKVideoRenderer: NSObject, VideoRenderer { public func LKRoomDelegateCreate( data: UnsafeRawPointer, onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, @@ -180,39 +180,39 @@ public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawP @_cdecl("LKRoomAudioTracksForRemoteParticipant") public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? } } - + return nil; } @_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? } } - + return nil; } @_cdecl("LKRoomVideoTracksForRemoteParticipant") public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? } } - + return nil; } @@ -222,7 +222,7 @@ public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { echoCancellation: true, noiseSuppression: true )) - + return Unmanaged.passRetained(track).toOpaque() } @@ -276,7 +276,7 @@ public func LKLocalTrackPublicationSetMute( callback_data: UnsafeRawPointer ) { let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - + if muted { publication.mute().then { on_complete(callback_data, nil) @@ -307,3 +307,21 @@ public func LKRemoteTrackPublicationSetEnabled( on_complete(callback_data, error.localizedDescription as CFString) } } + +@_cdecl("LKRemoteTrackPublicationIsMuted") +public func LKRemoteTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationGetSid") +public func LKRemoteTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index f5f6d0e46f123d8015f7674d8c0292f1f35d3d75..f2169d7f30898a58a4a05c1ce0dec0869b2d9402 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -63,7 +63,7 @@ fn main() { let audio_track = LocalAudioTrack::create(); let audio_track_publication = room_a.publish_audio_track(&audio_track).await.unwrap(); - if let RemoteAudioTrackUpdate::Subscribed(track) = + if let RemoteAudioTrackUpdate::Subscribed(track, _) = audio_track_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 6daa0601ca95541665dd8bd9c2eeaf526320df8c..2b5148e4a39407ca660cdb7e827df2d2be0eb45b 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -26,6 +26,7 @@ extern "C" { publisher_id: CFStringRef, track_id: CFStringRef, remote_track: *const c_void, + remote_publication: *const c_void, ), on_did_unsubscribe_from_remote_audio_track: extern "C" fn( callback_data: *mut c_void, @@ -125,6 +126,9 @@ extern "C" { on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), callback_data: *mut c_void, ); + + fn LKRemoteTrackPublicationIsMuted(publication: *const c_void) -> bool; + fn LKRemoteTrackPublicationGetSid(publication: *const c_void) -> CFStringRef; } pub type Sid = String; @@ -372,10 +376,11 @@ impl Room { rx } - fn did_subscribe_to_remote_audio_track(&self, track: RemoteAudioTrack) { + fn did_subscribe_to_remote_audio_track(&self, track: RemoteAudioTrack, publication: RemoteTrackPublication) { let track = Arc::new(track); + let publication = Arc::new(publication); self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(track.clone())) + tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(track.clone(), publication.clone())) .is_ok() }); } @@ -501,13 +506,15 @@ impl RoomDelegate { publisher_id: CFStringRef, track_id: CFStringRef, track: *const c_void, + publication: *const c_void, ) { let room = unsafe { Weak::from_raw(room as *mut Room) }; let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; let track = RemoteAudioTrack::new(track, track_id, publisher_id); + let publication = RemoteTrackPublication::new(publication); if let Some(room) = room.upgrade() { - room.did_subscribe_to_remote_audio_track(track); + room.did_subscribe_to_remote_audio_track(track, publication); } let _ = Weak::into_raw(room); } @@ -682,6 +689,14 @@ impl RemoteTrackPublication { Self(native_track_publication) } + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } + } + pub fn set_enabled(&self, enabled: bool) -> impl Future> { let (tx, rx) = futures::channel::oneshot::channel(); @@ -832,7 +847,7 @@ pub enum RemoteVideoTrackUpdate { pub enum RemoteAudioTrackUpdate { ActiveSpeakersChanged { speakers: Vec }, MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc), + Subscribed(Arc, Arc), Unsubscribed { publisher_id: Sid, track_id: Sid }, } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index ada864fc44492d278a34c949589af5a4d605a0d2..3b91e0ef8918d606ab3d667d4a879e130b3dc7ef 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -216,6 +216,8 @@ impl TestServer { publisher_id: identity.clone(), }); + let publication = Arc::new(RemoteTrackPublication); + room.audio_tracks.push(track.clone()); for (id, client_room) in &room.client_rooms { @@ -225,7 +227,7 @@ impl TestServer { .lock() .audio_track_updates .0 - .try_broadcast(RemoteAudioTrackUpdate::Subscribed(track.clone())) + .try_broadcast(RemoteAudioTrackUpdate::Subscribed(track.clone(), publication.clone())) .unwrap(); } } @@ -501,6 +503,14 @@ impl RemoteTrackPublication { pub fn set_enabled(&self, _enabled: bool) -> impl Future> { async { Ok(()) } } + + pub fn is_muted(&self) -> bool { + false + } + + pub fn sid(&self) -> String { + "".to_string() + } } #[derive(Clone)] @@ -579,7 +589,7 @@ pub enum RemoteVideoTrackUpdate { pub enum RemoteAudioTrackUpdate { ActiveSpeakersChanged { speakers: Vec }, MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc), + Subscribed(Arc, Arc), Unsubscribed { publisher_id: Sid, track_id: Sid }, } From 1e4bddd276fba6227d5e4cde8218ca2c8c86d2f5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 12:34:56 -0700 Subject: [PATCH 088/162] fmt --- crates/call/src/call.rs | 2 +- crates/call/src/call_settings.rs | 2 +- crates/call/src/room.rs | 2 +- crates/live_kit_client/src/prod.rs | 13 ++++++++++--- crates/live_kit_client/src/test.rs | 5 ++++- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 1ad7dbc1fc09fdb5fa81f893a7aec6cdd5a1eac4..3fc76b964d389923fdf6479bcf208a4d1ba14212 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,6 +1,6 @@ +pub mod call_settings; pub mod participant; pub mod room; -pub mod call_settings; use std::sync::Arc; diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 356ae3ef1984763656df87cacb42c5a6be33eee2..2808a99617b68e75b278fe5e1ec399081e15bd57 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -1,5 +1,5 @@ use schemars::JsonSchema; -use serde_derive::{Serialize, Deserialize}; +use serde_derive::{Deserialize, Serialize}; use settings::Setting; #[derive(Deserialize, Debug)] diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index d57d4f711bfab69036093ae963ed4c0469b1da7b..08ac8befc42de7d60d4acfa9efc8a7c7706b68ac 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -20,7 +20,7 @@ use live_kit_client::{ }; use postage::stream::Stream; use project::Project; -use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration, panic::Location}; +use std::{future::Future, mem, panic::Location, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 2b5148e4a39407ca660cdb7e827df2d2be0eb45b..d8d0277440cf58498c9197bc92421e0ec8b52bc1 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -376,12 +376,19 @@ impl Room { rx } - fn did_subscribe_to_remote_audio_track(&self, track: RemoteAudioTrack, publication: RemoteTrackPublication) { + fn did_subscribe_to_remote_audio_track( + &self, + track: RemoteAudioTrack, + publication: RemoteTrackPublication, + ) { let track = Arc::new(track); let publication = Arc::new(publication); self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(track.clone(), publication.clone())) - .is_ok() + tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .is_ok() }); } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 3b91e0ef8918d606ab3d667d4a879e130b3dc7ef..704760bab7f42b9e07687fc52bc69a34173e5170 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -227,7 +227,10 @@ impl TestServer { .lock() .audio_track_updates .0 - .try_broadcast(RemoteAudioTrackUpdate::Subscribed(track.clone(), publication.clone())) + .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) .unwrap(); } } From 2e3aa703d9a10b8c037aef63024517ad03bde64b Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 19 Jul 2023 15:43:45 -0400 Subject: [PATCH 089/162] In macOS platform layer map a ctrl-click to a right click --- crates/gpui/src/platform/mac/window.rs | 46 ++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 381a4fbaaacc1c45ad2ed19140e1980009857751..c0f0ade7b926b59dc3a49a714483f576f3a64026 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -10,8 +10,8 @@ use crate::{ mac::{ platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer, screen::Screen, }, - Event, InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent, - MouseMovedEvent, Scene, WindowBounds, WindowKind, + Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton, + MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind, }, }; use block::ConcreteBlock; @@ -1053,7 +1053,44 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_height = window_state_borrow.content_size().y(); let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - if let Some(event) = event { + + if let Some(mut event) = event { + let synthesized_second_event = match &mut event { + Event::MouseDown( + event @ MouseButtonEvent { + button: MouseButton::Left, + modifiers: Modifiers { ctrl: true, .. }, + .. + }, + ) => { + *event = MouseButtonEvent { + button: MouseButton::Right, + modifiers: Modifiers { + ctrl: false, + ..event.modifiers + }, + click_count: 1, + ..*event + }; + + Some(Event::MouseUp(MouseButtonEvent { + button: MouseButton::Right, + ..*event + })) + } + + // Because we map a ctrl-left_down to a right_down -> right_up let's ignore + // the ctrl-left_up to avoid having a mismatch in button down/up events if the + // user is still holding ctrl when releasing the left mouse button + Event::MouseUp(MouseButtonEvent { + button: MouseButton::Left, + modifiers: Modifiers { ctrl: true, .. }, + .. + }) => return, + + _ => None, + }; + match &event { Event::MouseMoved( event @ MouseMovedEvent { @@ -1105,6 +1142,9 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); callback(event); + if let Some(event) = synthesized_second_event { + callback(event); + } window_state.borrow_mut().event_callback = Some(callback); } } From 7e904183bfc0dc6bab320a973608e4b17c55956e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 19 Jul 2023 16:24:29 -0400 Subject: [PATCH 090/162] Fix return type in watch_file_types() --- crates/zed/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ea019a0fdd6361557d0b53c50f3215e0e8a9f895..3f89ab3f1f2a690f3d1087522821b4aad35a72d7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -717,9 +717,7 @@ async fn watch_languages(_: Arc, _: Arc) -> Option<()> } #[cfg(not(debug_assertions))] -fn watch_file_types(fs: Arc, cx: &mut AppContext) { - None -} +fn watch_file_types(fs: Arc, cx: &mut AppContext) {} fn connect_to_cli( server_name: &str, From aa67413abc9e8d187a1380ad58cd86f87173f746 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 16:55:28 -0700 Subject: [PATCH 091/162] Raise minimum line height to 1.1 Disable buffer_line_height setting in non-buffer editors --- crates/editor/src/editor.rs | 14 ++++++++++++-- crates/editor/src/element.rs | 1 + crates/theme/src/theme_settings.rs | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cbf3d1a17371685babca6834afeab1fc59b9352d..6a22a1f1f238213caeb6b5126feea093af49eaad 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1300,7 +1300,7 @@ impl Editor { let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { let settings = settings::get::(cx); - let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); + let style = build_style(settings, get_field_editor_theme.as_deref(), None, &mode, cx); DisplayMap::new( buffer.clone(), style.text.font_id, @@ -1500,6 +1500,7 @@ impl Editor { settings::get::(cx), self.get_field_editor_theme.as_deref(), self.override_text_style.as_deref(), + &self.mode, cx, ) } @@ -8152,10 +8153,11 @@ fn build_style( settings: &ThemeSettings, get_field_editor_theme: Option<&GetFieldEditorTheme>, override_text_style: Option<&OverrideTextStyle>, + mode: &EditorMode, cx: &AppContext, ) -> EditorStyle { let font_cache = cx.font_cache(); - let line_height_scalar = settings.line_height(); + let mut line_height_scalar = settings.line_height(); let theme_id = settings.theme.meta.id; let mut theme = settings.theme.editor.clone(); let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { @@ -8166,6 +8168,14 @@ fn build_style( .container .background_color .unwrap_or_default(); + + line_height_scalar = match mode { + EditorMode::Full => line_height_scalar, + EditorMode::AutoHeight { .. } | EditorMode::SingleLine => { + cx.font_cache().line_height(field_editor_theme.text.font_size) + } + }; + EditorStyle { text: field_editor_theme.text, placeholder_text: field_editor_theme.placeholder_text, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4962b08db2175c10923b3e32ee455ff51a26be46..59a2b222d8502765a94096073c77585569cb617b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1977,6 +1977,7 @@ impl Element for EditorElement { let snapshot = editor.snapshot(cx); let style = self.style.clone(); + let line_height = (style.text.font_size * style.line_height_scalar).round(); let gutter_padding; diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index b576391e14b841a42c826196ce41471e8ab332c4..ab302a123a96e3ca3d74feeaabd0767df4403ffc 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use util::ResultExt as _; const MIN_FONT_SIZE: f32 = 6.0; -const MIN_LINE_HEIGHT: f32 = 1.0; +const MIN_LINE_HEIGHT: f32 = 1.1; #[derive(Clone, JsonSchema)] pub struct ThemeSettings { From cb97f5a69c1a5b45faae60f56e04a7e74e6b71d6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 16:56:49 -0700 Subject: [PATCH 092/162] fmt --- crates/editor/src/editor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6a22a1f1f238213caeb6b5126feea093af49eaad..47f31f2c94c7b5224eea4d80b3b9048a32bda4ee 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8171,9 +8171,9 @@ fn build_style( line_height_scalar = match mode { EditorMode::Full => line_height_scalar, - EditorMode::AutoHeight { .. } | EditorMode::SingleLine => { - cx.font_cache().line_height(field_editor_theme.text.font_size) - } + EditorMode::AutoHeight { .. } | EditorMode::SingleLine => cx + .font_cache() + .line_height(field_editor_theme.text.font_size), }; EditorStyle { From e1379f0ef051cf43b524d292741c0065dbbd1e40 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 19 Jul 2023 17:58:21 -0600 Subject: [PATCH 093/162] Add support for activating a pane by direction Contributes: zed-industries/community#476 Contributes: zed-industries/community#478 --- crates/editor/src/editor.rs | 2 + crates/editor/src/element.rs | 14 ++++++ crates/editor/src/items.rs | 10 +++- crates/workspace/src/item.rs | 9 ++++ crates/workspace/src/pane.rs | 6 +++ crates/workspace/src/pane_group.rs | 74 +++++++++++++++++++++++++++++- crates/workspace/src/workspace.rs | 47 ++++++++++++++++++- 7 files changed, 157 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cbf3d1a17371685babca6834afeab1fc59b9352d..b790543ee54c4efe5060411278dc35485377e3b3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -563,6 +563,7 @@ pub struct Editor { inlay_hint_cache: InlayHintCache, next_inlay_id: usize, _subscriptions: Vec, + pixel_position_of_newest_cursor: Option, } pub struct EditorSnapshot { @@ -1394,6 +1395,7 @@ impl Editor { copilot_state: Default::default(), inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, + pixel_position_of_newest_cursor: None, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4962b08db2175c10923b3e32ee455ff51a26be46..7a532892328511045da5a97cc368ee04f4964321 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -61,6 +61,7 @@ enum FoldMarkers {} struct SelectionLayout { head: DisplayPoint, cursor_shape: CursorShape, + is_newest: bool, range: Range, } @@ -70,6 +71,7 @@ impl SelectionLayout { line_mode: bool, cursor_shape: CursorShape, map: &DisplaySnapshot, + is_newest: bool, ) -> Self { if line_mode { let selection = selection.map(|p| p.to_point(&map.buffer_snapshot)); @@ -77,6 +79,7 @@ impl SelectionLayout { Self { head: selection.head().to_display_point(map), cursor_shape, + is_newest, range: point_range.start.to_display_point(map) ..point_range.end.to_display_point(map), } @@ -85,6 +88,7 @@ impl SelectionLayout { Self { head: selection.head(), cursor_shape, + is_newest, range: selection.range(), } } @@ -864,6 +868,12 @@ impl EditorElement { let x = cursor_character_x - scroll_left; let y = cursor_position.row() as f32 * layout.position_map.line_height - scroll_top; + if selection.is_newest { + editor.pixel_position_of_newest_cursor = Some(vec2f( + bounds.origin_x() + x + block_width / 2., + bounds.origin_y() + y + layout.position_map.line_height / 2., + )); + } cursors.push(Cursor { color: selection_style.cursor, block_width, @@ -2108,6 +2118,7 @@ impl Element for EditorElement { line_mode, cursor_shape, &snapshot.display_snapshot, + false, )); } selections.extend(remote_selections); @@ -2117,6 +2128,7 @@ impl Element for EditorElement { .selections .disjoint_in_range(start_anchor..end_anchor, cx); local_selections.extend(editor.selections.pending(cx)); + let newest = editor.selections.newest(cx); for selection in &local_selections { let is_empty = selection.start == selection.end; let selection_start = snapshot.prev_line_boundary(selection.start).1; @@ -2139,11 +2151,13 @@ impl Element for EditorElement { local_selections .into_iter() .map(|selection| { + let is_newest = selection == newest; SelectionLayout::new( selection, editor.selections.line_mode, editor.cursor_shape, &snapshot.display_snapshot, + is_newest, ) }) .collect(), diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 0ce41a97c96416af9bd4d45b1255c8f4b7ae0301..7c8fe12aa068cfd97566142d6436f0ce4f0300d2 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -7,8 +7,10 @@ use anyhow::{Context, Result}; use collections::HashSet; use futures::future::try_join_all; use gpui::{ - elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + elements::*, + geometry::vector::{vec2f, Vector2F}, + AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, @@ -750,6 +752,10 @@ impl Item for Editor { Some(Box::new(handle.clone())) } + fn pixel_position_of_cursor(&self) -> Option { + self.pixel_position_of_newest_cursor + } + fn breadcrumb_location(&self) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft { flex: None } } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 460698efb80aa4fc41912810649964477a8a48f4..f0af080d4a6cd9719db897a6320c869695092fe9 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -5,6 +5,7 @@ use crate::{ use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; use anyhow::Result; use client::{proto, Client}; +use gpui::geometry::vector::Vector2F; use gpui::{ fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, @@ -203,6 +204,9 @@ pub trait Item: View { fn show_toolbar(&self) -> bool { true } + fn pixel_position_of_cursor(&self) -> Option { + None + } } pub trait ItemHandle: 'static + fmt::Debug { @@ -271,6 +275,7 @@ pub trait ItemHandle: 'static + fmt::Debug { fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; fn serialized_item_kind(&self) -> Option<&'static str>; fn show_toolbar(&self, cx: &AppContext) -> bool; + fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option; } pub trait WeakItemHandle { @@ -615,6 +620,10 @@ impl ItemHandle for ViewHandle { fn show_toolbar(&self, cx: &AppContext) -> bool { self.read(cx).show_toolbar() } + + fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + self.read(cx).pixel_position_of_cursor() + } } impl From> for AnyViewHandle { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f5b96fd421b6cea5e997dc524d7dc95e969d5286..2972c307f2624a6bb23bef0f1919aab5a43eb66d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -542,6 +542,12 @@ impl Pane { self.items.get(self.active_item_index).cloned() } + pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + self.items + .get(self.active_item_index)? + .pixel_position_of_cursor(cx) + } + pub fn item_for_entry( &self, entry_id: ProjectEntryId, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 52761b06c814522220342e15c01e38f253a17667..baa654d9672a5f307958a7945aff3612da87a0de 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -54,6 +54,20 @@ impl PaneGroup { } } + pub fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + match &self.root { + Member::Pane(_) => None, + Member::Axis(axis) => axis.bounding_box_for_pane(pane), + } + } + + pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + match &self.root { + Member::Pane(pane) => Some(pane), + Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), + } + } + /// Returns: /// - Ok(true) if it found and removed a pane /// - Ok(false) if it found but did not remove the pane @@ -309,15 +323,18 @@ pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, pub flexes: Rc>>, + pub bounding_boxes: Rc>>>, } impl PaneAxis { pub fn new(axis: Axis, members: Vec) -> Self { let flexes = Rc::new(RefCell::new(vec![1.; members.len()])); + let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); Self { axis, members, flexes, + bounding_boxes, } } @@ -326,10 +343,12 @@ impl PaneAxis { debug_assert!(members.len() == flexes.len()); let flexes = Rc::new(RefCell::new(flexes)); + let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); Self { axis, members, flexes, + bounding_boxes, } } @@ -409,6 +428,40 @@ impl PaneAxis { } } + fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + for (idx, member) in self.members.iter().enumerate() { + match member { + Member::Pane(found) => { + if pane == found { + return self.bounding_boxes.borrow()[idx]; + } + } + Member::Axis(axis) => { + if let Some(rect) = axis.bounding_box_for_pane(pane) { + return Some(rect); + } + } + } + } + None + } + + fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + let bounding_boxes = self.bounding_boxes.borrow(); + + for (idx, member) in self.members.iter().enumerate() { + if let Some(coordinates) = bounding_boxes[idx] { + if coordinates.contains_point(coordinate) { + return match member { + Member::Pane(found) => Some(found), + Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), + }; + } + } + } + None + } + fn render( &self, project: &ModelHandle, @@ -423,7 +476,12 @@ impl PaneAxis { ) -> AnyElement { debug_assert!(self.members.len() == self.flexes.borrow().len()); - let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); + let mut pane_axis = PaneAxisElement::new( + self.axis, + basis, + self.flexes.clone(), + self.bounding_boxes.clone(), + ); let mut active_pane_ix = None; let mut members = self.members.iter().enumerate().peekable(); @@ -546,14 +604,21 @@ mod element { active_pane_ix: Option, flexes: Rc>>, children: Vec>, + bounding_boxes: Rc>>>, } impl PaneAxisElement { - pub fn new(axis: Axis, basis: usize, flexes: Rc>>) -> Self { + pub fn new( + axis: Axis, + basis: usize, + flexes: Rc>>, + bounding_boxes: Rc>>>, + ) -> Self { Self { axis, basis, flexes, + bounding_boxes, active_pane_ix: None, children: Default::default(), } @@ -708,11 +773,16 @@ mod element { let mut child_origin = bounds.origin(); + let mut bounding_boxes = self.bounding_boxes.borrow_mut(); + bounding_boxes.clear(); + let mut children_iter = self.children.iter_mut().enumerate().peekable(); while let Some((ix, child)) = children_iter.next() { let child_start = child_origin.clone(); child.paint(scene, child_origin, visible_bounds, view, cx); + bounding_boxes.push(Some(RectF::new(child_origin, child.size()))); + match self.axis { Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3e62af8ea65db48618e129cf25bce97417c58c48..e87e8f1855b8b60345a3299e8143ff64f34c6d80 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -152,6 +152,9 @@ pub struct OpenPaths { #[derive(Clone, Deserialize, PartialEq)] pub struct ActivatePane(pub usize); +#[derive(Clone, Deserialize, PartialEq)] +pub struct ActivatePaneInDirection(pub SplitDirection); + #[derive(Deserialize)] pub struct Toast { id: usize, @@ -197,7 +200,7 @@ impl Clone for Toast { } } -impl_actions!(workspace, [ActivatePane, Toast]); +impl_actions!(workspace, [ActivatePane, ActivatePaneInDirection, Toast]); pub type WorkspaceId = i64; @@ -262,6 +265,13 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { workspace.activate_next_pane(cx) }); + + cx.add_action( + |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| { + workspace.activate_pane_in_direction(action.0, cx) + }, + ); + cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { workspace.toggle_dock(DockPosition::Left, cx); }); @@ -2054,6 +2064,40 @@ impl Workspace { } } + pub fn activate_pane_in_direction( + &mut self, + direction: SplitDirection, + cx: &mut ViewContext, + ) { + let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) { + Some(coordinates) => coordinates, + None => { + return; + } + }; + let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); + let center = match cursor { + Some(cursor) => cursor, + None => bounding_box.center(), + }; + + // currently there's a small gap between panes, so we can't just look "1px to the left" + // instead of trying to calcuate this exactly, we assume it'll always be smaller than + // "pane_gap" pixels (and that no-one uses panes smaller in any dimension than pane_gap). + let pane_gap = 20.; + + let target = match direction { + SplitDirection::Left => vec2f(bounding_box.origin_x() - pane_gap, center.y()), + SplitDirection::Right => vec2f(bounding_box.max_x() + pane_gap, center.y()), + SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - pane_gap), + SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + pane_gap), + }; + + if let Some(pane) = self.center.pane_at_pixel_position(target) { + cx.focus(pane); + } + } + fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { if self.active_pane != pane { self.active_pane = pane.clone(); @@ -3030,6 +3074,7 @@ impl Workspace { axis, members, flexes, + bounding_boxes: _, }) => SerializedPaneGroup::Group { axis: *axis, children: members From 2762f9b1c681a0af0cf3c6e08e8f1a3855352faf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 19 Jul 2023 18:29:13 -0600 Subject: [PATCH 094/162] vim: Add support for ctrl-w commands Primarily {h,j,k,l,left,right,up,down} for moving to a pane by direction; but also {w,W,p} for going forward/back, and {v,s} for splitting a pane vertically/horizontally, and {c,q} to close a pane. There are a large number of ctrl-w commands that are not supported, and which fall into three buckets: * switch this pane with that one (VScode also has this, and it's a requested feature) * move to top/bottom/leftmost/rightmost * counts on any of these * jump to "definition/file-under-cursor/etc.etc." in a new pane. --- assets/keymaps/vim.json | 70 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index aa1e23541448fd200979c5955c9e5acc641dd4fc..8fb174c71eb9bb7cd7466b603525e492179d46c2 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -145,7 +145,75 @@ "9": [ "vim::Number", 9 - ] + ], + // window related commands (ctrl-w X) + "ctrl-w left": [ + "workspace::ActivatePaneInDirection", + "Left" + ], + "ctrl-w right": [ + "workspace::ActivatePaneInDirection", + "Right" + ], + "ctrl-w up": [ + "workspace::ActivatePaneInDirection", + "Up" + ], + "ctrl-w down": [ + "workspace::ActivatePaneInDirection", + "Down" + ], + "ctrl-w h": [ + "workspace::ActivatePaneInDirection", + "Left" + ], + "ctrl-w l": [ + "workspace::ActivatePaneInDirection", + "Right" + ], + "ctrl-w k": [ + "workspace::ActivatePaneInDirection", + "Up" + ], + "ctrl-w j": [ + "workspace::ActivatePaneInDirection", + "Down" + ], + "ctrl-w ctrl-h": [ + "workspace::ActivatePaneInDirection", + "Left" + ], + "ctrl-w ctrl-l": [ + "workspace::ActivatePaneInDirection", + "Right" + ], + "ctrl-w ctrl-k": [ + "workspace::ActivatePaneInDirection", + "Up" + ], + "ctrl-w ctrl-j": [ + "workspace::ActivatePaneInDirection", + "Down" + ], + "ctrl-w g t": "pane::ActivateNextItem", + "ctrl-w ctrl-g t": "pane::ActivateNextItem", + "ctrl-w g shift-t": "pane::ActivatePrevItem", + "ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem", + "ctrl-w w": "workspace::ActivateNextPane", + "ctrl-w ctrl-w": "workspace::ActivateNextPane", + "ctrl-w p": "workspace::ActivatePreviousPane", + "ctrl-w ctrl-p": "workspace::ActivatePreviousPane", + "ctrl-w shift-w": "workspace::ActivatePreviousPane", + "ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane", + "ctrl-w v": "pane::SplitLeft", + "ctrl-w ctrl-v": "pane::SplitLeft", + "ctrl-w s": "pane::SplitUp", + "ctrl-w shift-s": "pane::SplitUp", + "ctrl-w ctrl-s": "pane::SplitUp", + "ctrl-w c": "pane::CloseAllItems", + "ctrl-w ctrl-c": "pane::CloseAllItems", + "ctrl-w q": "pane::CloseAllItems", + "ctrl-w ctrl-q": "pane::CloseAllItems" } }, { From 15dc8b43c476a6da0a44457bf7d8581228851b2a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 19 Jul 2023 18:33:08 -0600 Subject: [PATCH 095/162] Default keybindings for activating pane by direction Breaking change: previously cmd-k cmd-{left,right} moved to the {previous,next} pane; now they will move in the specified direction. --- assets/keymaps/default.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1a13d8cdb30ce4192d76179fa86dba5812539c95..883b0c1872a47ea9242716cfbd39298ccdfacb94 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -446,8 +446,22 @@ }, { "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" + "cmd-k cmd-left": [ + "workspace::ActivatePaneInDirection", + "Left" + ], + "cmd-k cmd-right": [ + "workspace::ActivatePaneInDirection", + "Right" + ], + "cmd-k cmd-up": [ + "workspace::ActivatePaneInDirection", + "Up" + ], + "cmd-k cmd-down": [ + "workspace::ActivatePaneInDirection", + "Down" + ] } }, // Bindings from Atom From a5e63fbf77f3092f1d32a3fe5057e318d3f82bd2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 19 Jul 2023 16:47:08 +0300 Subject: [PATCH 096/162] Properly display keybindings in context menus --- crates/call/src/room.rs | 3 +-- crates/gpui/src/app.rs | 18 +++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 08ac8befc42de7d60d4acfa9efc8a7c7706b68ac..328a94506c136dad0fbf000b00b391d6c4025b7f 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -20,7 +20,7 @@ use live_kit_client::{ }; use postage::stream::Stream; use project::Project; -use std::{future::Future, mem, panic::Location, pin::Pin, sync::Arc, time::Duration}; +use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -1089,7 +1089,6 @@ impl Room { #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { - dbg!(Location::caller()); if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); } else if self.is_sharing_mic() { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b40a67db61cf2077d3b22ccc72d8afc9b96e9819..7af363d596b63abe06f78717d8945dcba820d7fd 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3411,18 +3411,14 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> { handler_depth = Some(contexts.len()) } + let action_contexts = if let Some(depth) = handler_depth { + &contexts[depth..] + } else { + &contexts + }; + self.keystroke_matcher - .bindings_for_action(action.id()) - .find_map(|b| { - let highest_handler = handler_depth?; - if action.eq(b.action()) - && (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) - { - Some(b.keystrokes().into()) - } else { - None - } - }) + .keystrokes_for_action(action, action_contexts) } fn notify_if_view_ancestors_change(&mut self, view_id: usize) { From 0e6048a85d0ed547774f34cdfd15b565d82407df Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 20 Jul 2023 13:42:11 +0300 Subject: [PATCH 097/162] Keep basic line height for single line editors --- crates/editor/src/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 47f31f2c94c7b5224eea4d80b3b9048a32bda4ee..f06425fc0b3730635ced55b5784e90fdd66ab3d7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8170,8 +8170,8 @@ fn build_style( .unwrap_or_default(); line_height_scalar = match mode { - EditorMode::Full => line_height_scalar, - EditorMode::AutoHeight { .. } | EditorMode::SingleLine => cx + EditorMode::SingleLine | EditorMode::Full => line_height_scalar, + EditorMode::AutoHeight { .. } => cx .font_cache() .line_height(field_editor_theme.text.font_size), }; From 0237276557a2eff7345e53e72a9537b0ead3734c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 20 Jul 2023 15:39:22 +0300 Subject: [PATCH 098/162] Fully revert the line height change --- crates/editor/src/editor.rs | 14 ++------------ crates/theme/src/theme_settings.rs | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f06425fc0b3730635ced55b5784e90fdd66ab3d7..cbf3d1a17371685babca6834afeab1fc59b9352d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1300,7 +1300,7 @@ impl Editor { let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { let settings = settings::get::(cx); - let style = build_style(settings, get_field_editor_theme.as_deref(), None, &mode, cx); + let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); DisplayMap::new( buffer.clone(), style.text.font_id, @@ -1500,7 +1500,6 @@ impl Editor { settings::get::(cx), self.get_field_editor_theme.as_deref(), self.override_text_style.as_deref(), - &self.mode, cx, ) } @@ -8153,11 +8152,10 @@ fn build_style( settings: &ThemeSettings, get_field_editor_theme: Option<&GetFieldEditorTheme>, override_text_style: Option<&OverrideTextStyle>, - mode: &EditorMode, cx: &AppContext, ) -> EditorStyle { let font_cache = cx.font_cache(); - let mut line_height_scalar = settings.line_height(); + let line_height_scalar = settings.line_height(); let theme_id = settings.theme.meta.id; let mut theme = settings.theme.editor.clone(); let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { @@ -8168,14 +8166,6 @@ fn build_style( .container .background_color .unwrap_or_default(); - - line_height_scalar = match mode { - EditorMode::SingleLine | EditorMode::Full => line_height_scalar, - EditorMode::AutoHeight { .. } => cx - .font_cache() - .line_height(field_editor_theme.text.font_size), - }; - EditorStyle { text: field_editor_theme.text, placeholder_text: field_editor_theme.placeholder_text, diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index ab302a123a96e3ca3d74feeaabd0767df4403ffc..b576391e14b841a42c826196ce41471e8ab332c4 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use util::ResultExt as _; const MIN_FONT_SIZE: f32 = 6.0; -const MIN_LINE_HEIGHT: f32 = 1.1; +const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone, JsonSchema)] pub struct ThemeSettings { From a90b151d52e1fa026c338c2102567e167d8894c9 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 10:07:32 -0600 Subject: [PATCH 099/162] Updated icons with additions --- assets/icons/file_icons/archive.svg | 6 +++--- assets/icons/file_icons/audio.svg | 6 ++++++ assets/icons/file_icons/book.svg | 6 ++++-- assets/icons/file_icons/camera.svg | 4 ++-- assets/icons/file_icons/database.svg | 2 +- assets/icons/file_icons/eslint.svg | 2 +- assets/icons/file_icons/file.svg | 4 ++-- assets/icons/file_icons/file_types.json | 9 ++++++--- assets/icons/file_icons/folder-open.svg | 7 ++++--- assets/icons/file_icons/folder.svg | 5 +++-- assets/icons/file_icons/git.svg | 2 +- assets/icons/file_icons/image.svg | 7 ++++--- assets/icons/file_icons/lock.svg | 4 ++-- assets/icons/file_icons/notebook.svg | 6 ++++-- assets/icons/file_icons/package.svg | 3 ++- assets/icons/file_icons/prettier.svg | 10 +++++----- assets/icons/file_icons/rust.svg | 2 +- assets/icons/file_icons/settings.svg | 4 ++-- assets/icons/file_icons/terminal.svg | 2 +- assets/icons/file_icons/toml.svg | 5 +++++ assets/icons/file_icons/typescript.svg | 6 +++--- assets/icons/file_icons/video.svg | 4 ++++ 22 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 assets/icons/file_icons/audio.svg create mode 100644 assets/icons/file_icons/toml.svg create mode 100644 assets/icons/file_icons/video.svg diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg index f11115cdcee09d1b40317c525cd3d256ea6be557..820c5846ba8cd87876de839a53f1f988c964669a 100644 --- a/assets/icons/file_icons/archive.svg +++ b/assets/icons/file_icons/archive.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/icons/file_icons/audio.svg b/assets/icons/file_icons/audio.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2275efb63ffa23646ff5933f159802ef9b4a197 --- /dev/null +++ b/assets/icons/file_icons/audio.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/book.svg b/assets/icons/file_icons/book.svg index 890b8988a30c703d87a9b27e408ab6730c215fdf..c9aa764d72d3ab0f6dd586a18364b7f80a2031a0 100644 --- a/assets/icons/file_icons/book.svg +++ b/assets/icons/file_icons/book.svg @@ -1,4 +1,6 @@ - - + + + + diff --git a/assets/icons/file_icons/camera.svg b/assets/icons/file_icons/camera.svg index d8b9cf459c512343d135a5bfe5c8928874cd0862..bc1993ad6320486705c54250cfa2559989100745 100644 --- a/assets/icons/file_icons/camera.svg +++ b/assets/icons/file_icons/camera.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/icons/file_icons/database.svg b/assets/icons/file_icons/database.svg index 9072e091b5da2feb5d728450a2022d72bf674081..812d147717ee23ae57eb3444023c32ee7954dbd6 100644 --- a/assets/icons/file_icons/database.svg +++ b/assets/icons/file_icons/database.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/eslint.svg b/assets/icons/file_icons/eslint.svg index ec5051d4474957e33034d32e79e646124adbe222..14ac83df96370fb8c74dda329c385f95216368df 100644 --- a/assets/icons/file_icons/eslint.svg +++ b/assets/icons/file_icons/eslint.svg @@ -1,4 +1,4 @@ - + diff --git a/assets/icons/file_icons/file.svg b/assets/icons/file_icons/file.svg index cc422734e77e3e226b2c2452f006163f4de12ea6..e05a47548e62ba02045472e141e5f088aa2301fd 100644 --- a/assets/icons/file_icons/file.svg +++ b/assets/icons/file_icons/file.svg @@ -1,5 +1,5 @@ - + - + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 4f3f8160d7e722b23c63c01db387527dd0eb8448..edc398a295ac1efe77faf3101823d736f08add4a 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -74,7 +74,7 @@ "svg": "image", "swift": "code", "tiff": "image", - "toml": "settings", + "toml": "toml", "ts": "typescript", "tsx": "code", "txt": "document", @@ -89,7 +89,7 @@ }, "types": { "audio": { - "icon": "icons/file_icons/file.svg" + "icon": "icons/file_icons/audio.svg" }, "code": { "icon": "icons/file_icons/code.svg" @@ -136,6 +136,9 @@ "terminal": { "icon": "icons/file_icons/terminal.svg" }, + "toml": { + "icon": "icons/file_icons/toml.svg" + }, "typescript": { "icon": "icons/file_icons/typescript.svg" }, @@ -143,7 +146,7 @@ "icon": "icons/file_icons/git.svg" }, "video": { - "icon": "icons/file_icons/file.svg" + "icon": "icons/file_icons/video.svg" } } } diff --git a/assets/icons/file_icons/folder-open.svg b/assets/icons/file_icons/folder-open.svg index 65c5744049b9c3271fc494fc290ec6a88b9519bf..405d0b83080b4995a56d19dc1e9538e80fc103af 100644 --- a/assets/icons/file_icons/folder-open.svg +++ b/assets/icons/file_icons/folder-open.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg index 5157bae839ef47be384f3d33aa4472f2e5609f6f..4ef944a69cab181eeb323be22acf881fcdbebd3f 100644 --- a/assets/icons/file_icons/folder.svg +++ b/assets/icons/file_icons/folder.svg @@ -1,4 +1,5 @@ - - + + + diff --git a/assets/icons/file_icons/git.svg b/assets/icons/file_icons/git.svg index 82d8c8f57c014e7c2bf679b34dcdf4da0c668049..a30b47fb86edc6532b709f6e97fec6cbac4501a3 100644 --- a/assets/icons/file_icons/git.svg +++ b/assets/icons/file_icons/git.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg index ee5e49f2d480d0f6f25ecc95b49aff1a5dbf5871..c258170daeaa2e9a57edcd0267e86286749a2b97 100644 --- a/assets/icons/file_icons/image.svg +++ b/assets/icons/file_icons/image.svg @@ -1,6 +1,7 @@ - - - + + + + diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg index 3051bbf801d4fe4f6dd14bc71d924fc9c87a32f6..b6aa3394efaaac1e83bce0f119dcde155fb01b89 100644 --- a/assets/icons/file_icons/lock.svg +++ b/assets/icons/file_icons/lock.svg @@ -1,6 +1,6 @@ - - + + diff --git a/assets/icons/file_icons/notebook.svg b/assets/icons/file_icons/notebook.svg index 6eaec16d0a34ec83aec053f6387049c2fc7a6f0e..4f55ceac58aa69544b396a3756835fa663a85080 100644 --- a/assets/icons/file_icons/notebook.svg +++ b/assets/icons/file_icons/notebook.svg @@ -1,6 +1,8 @@ - + + - + + diff --git a/assets/icons/file_icons/package.svg b/assets/icons/file_icons/package.svg index 2a692ba4b4d1f89bfce502ce9a7354dde7c90459..a46126e3e902e94df8289d459e8916c9995d3a77 100644 --- a/assets/icons/file_icons/package.svg +++ b/assets/icons/file_icons/package.svg @@ -1,3 +1,4 @@ - + + diff --git a/assets/icons/file_icons/prettier.svg b/assets/icons/file_icons/prettier.svg index 2d2c6ee7193e5d6c6a281f777b13fa4ed55c6bf2..23cefe0efc834d0f953b081fbf5220acdea9117d 100644 --- a/assets/icons/file_icons/prettier.svg +++ b/assets/icons/file_icons/prettier.svg @@ -1,12 +1,12 @@ - - + + - + - + - + diff --git a/assets/icons/file_icons/rust.svg b/assets/icons/file_icons/rust.svg index 1802f0e190d86041be658afda706b5bc36db1644..91982b3eeb9f23a108dd4fb484b8925d18b77437 100644 --- a/assets/icons/file_icons/rust.svg +++ b/assets/icons/file_icons/rust.svg @@ -1,4 +1,4 @@ - + diff --git a/assets/icons/file_icons/settings.svg b/assets/icons/file_icons/settings.svg index e827055d1972ffd657fcc6129775a66be37d8e5f..35af7e1899ef8619249744502794452b074679ec 100644 --- a/assets/icons/file_icons/settings.svg +++ b/assets/icons/file_icons/settings.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/icons/file_icons/terminal.svg b/assets/icons/file_icons/terminal.svg index 939587c53ecc97042821439590ab49ff2f980853..15dd705b0b313930e1971d24d3774050880b5a4c 100644 --- a/assets/icons/file_icons/terminal.svg +++ b/assets/icons/file_icons/terminal.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/toml.svg b/assets/icons/file_icons/toml.svg new file mode 100644 index 0000000000000000000000000000000000000000..496c41e75562faf52ee4a7df5428d4d293f7e6d5 --- /dev/null +++ b/assets/icons/file_icons/toml.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/typescript.svg b/assets/icons/file_icons/typescript.svg index 179b3d85724edb25889a62b80d1bd46d510fde5e..f7748a86c46bfa915bf3f86af4208f2f8712a8aa 100644 --- a/assets/icons/file_icons/typescript.svg +++ b/assets/icons/file_icons/typescript.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/icons/file_icons/video.svg b/assets/icons/file_icons/video.svg new file mode 100644 index 0000000000000000000000000000000000000000..c7ebf98af62ddfbe68073717379694449ee6e716 --- /dev/null +++ b/assets/icons/file_icons/video.svg @@ -0,0 +1,4 @@ + + + + From f051e66231608969a3eb1063180d6ea50c0c3e4e Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 10:15:20 -0600 Subject: [PATCH 100/162] code icon adjustment --- assets/icons/file_icons/code.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/icons/file_icons/code.svg b/assets/icons/file_icons/code.svg index 2733e4b535af74fbf795b51fe8090343396bc953..5e59cbe58fbf7255e36ee454834a788fed75b48c 100644 --- a/assets/icons/file_icons/code.svg +++ b/assets/icons/file_icons/code.svg @@ -1,4 +1,4 @@ - - + + From d6a463afb868e5616ee365030d2b516a207f5413 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 11:06:16 -0600 Subject: [PATCH 101/162] Better calculation of pane distance --- crates/workspace/src/workspace.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e87e8f1855b8b60345a3299e8143ff64f34c6d80..2f3f2f9010a827d1d0323a3705bab4410c621d47 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2081,16 +2081,13 @@ impl Workspace { None => bounding_box.center(), }; - // currently there's a small gap between panes, so we can't just look "1px to the left" - // instead of trying to calcuate this exactly, we assume it'll always be smaller than - // "pane_gap" pixels (and that no-one uses panes smaller in any dimension than pane_gap). - let pane_gap = 20.; + let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; let target = match direction { - SplitDirection::Left => vec2f(bounding_box.origin_x() - pane_gap, center.y()), - SplitDirection::Right => vec2f(bounding_box.max_x() + pane_gap, center.y()), - SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - pane_gap), - SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + pane_gap), + SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), + SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), + SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), + SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), }; if let Some(pane) = self.center.pane_at_pixel_position(target) { From 464cc2e71affec45785a04a123707a4931464a82 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 11:11:37 -0600 Subject: [PATCH 102/162] Assertions for assumptions --- crates/workspace/src/pane_group.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index baa654d9672a5f307958a7945aff3612da87a0de..7b7c84561632a13f8ec3f7c4f7f9bf3fa9161324 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -429,6 +429,8 @@ impl PaneAxis { } fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + for (idx, member) in self.members.iter().enumerate() { match member { Member::Pane(found) => { @@ -447,6 +449,8 @@ impl PaneAxis { } fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + let bounding_boxes = self.bounding_boxes.borrow(); for (idx, member) in self.members.iter().enumerate() { From 0e984e1e69e7125ed38397556f67fcde1e80a415 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 11:11:47 -0600 Subject: [PATCH 103/162] Ignore off-screen cursors --- crates/workspace/src/workspace.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2f3f2f9010a827d1d0323a3705bab4410c621d47..0ebd01e1f7a706e1e2baf3b15ff1f35a51a3fb7f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2077,8 +2077,8 @@ impl Workspace { }; let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); let center = match cursor { - Some(cursor) => cursor, - None => bounding_box.center(), + Some(cursor) if bounding_box.contains_point(cursor) => cursor, + _ => bounding_box.center(), }; let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; From 5d22a300c3c4e600ae2aa4b3a30ea0685c904134 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 20 Jul 2023 13:18:15 -0400 Subject: [PATCH 104/162] Add the `local` and `declare` keywords to bash syntax highlighting --- crates/zed/src/languages/bash/highlights.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/src/languages/bash/highlights.scm b/crates/zed/src/languages/bash/highlights.scm index f33a7c2d3ae8c63364845dbd8cef1242ffa07f04..a72c5468edd911b60b31c8ae07041c3f733f3497 100644 --- a/crates/zed/src/languages/bash/highlights.scm +++ b/crates/zed/src/languages/bash/highlights.scm @@ -27,6 +27,8 @@ "unset" "until" "while" + "local" + "declare" ] @keyword (comment) @comment From 4d1dbb8aa3e4fe0bcebd7c5ea98aa8d463f1c8f2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 10:33:28 -0700 Subject: [PATCH 105/162] Add a double click to reset resized splits --- crates/workspace/src/pane_group.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 7b7c84561632a13f8ec3f7c4f7f9bf3fa9161324..abcb609dd17364fd4d6c269bf6cfaefdda8decd5 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -826,10 +826,11 @@ mod element { let child_size = child.size(); let next_child_size = next_child.size(); let drag_bounds = visible_bounds.clone(); - let flexes = self.flexes.clone(); - let current_flex = flexes.borrow()[ix]; + let flexes = self.flexes.borrow(); + let current_flex = flexes[ix]; let next_ix = *next_ix; - let next_flex = flexes.borrow()[next_ix]; + let next_flex = flexes[next_ix]; + drop(flexes); enum ResizeHandle {} let mut mouse_region = MouseRegion::new::( cx.view_id(), @@ -838,7 +839,9 @@ mod element { ); mouse_region = mouse_region.on_drag( MouseButton::Left, - move |drag, workspace: &mut Workspace, cx| { + { + let flexes = self.flexes.clone(); + move |drag, workspace: &mut Workspace, cx| { let min_size = match axis { Axis::Horizontal => HORIZONTAL_MIN_SIZE, Axis::Vertical => VERTICAL_MIN_SIZE, @@ -881,8 +884,17 @@ mod element { workspace.schedule_serialize(cx); cx.notify(); - }, - ); + }}, + ).on_click(MouseButton::Left, { + let flexes = self.flexes.clone(); + move |e, v: &mut Workspace, cx| { + if e.click_count >= 2 { + let mut borrow = flexes.borrow_mut(); + *borrow = vec![1.; borrow.len()]; + v.schedule_serialize(cx); + cx.notify(); + } + }}); scene.push_mouse_region(mouse_region); scene.pop_stacking_context(); From d84d663ac3e6c4473f522f2f84da24ca724221d7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 10:36:23 -0700 Subject: [PATCH 106/162] fmt --- crates/workspace/src/pane_group.rs | 112 +++++++++++++++-------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index abcb609dd17364fd4d6c269bf6cfaefdda8decd5..e60f6deb2ff2663918c7fa3dd8c2722a69dbb2e9 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -837,64 +837,68 @@ mod element { self.basis + ix, handle_bounds, ); - mouse_region = mouse_region.on_drag( - MouseButton::Left, - { + mouse_region = mouse_region + .on_drag(MouseButton::Left, { let flexes = self.flexes.clone(); move |drag, workspace: &mut Workspace, cx| { - let min_size = match axis { - Axis::Horizontal => HORIZONTAL_MIN_SIZE, - Axis::Vertical => VERTICAL_MIN_SIZE, - }; - // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - 1. > child_size.along(axis) - || min_size - 1. > next_child_size.along(axis) - { - return; + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > child_size.along(axis) + || min_size - 1. > next_child_size.along(axis) + { + return; + } + + let mut current_target_size = + (drag.position - child_start).along(axis); + + let proposed_current_pixel_change = + current_target_size - child_size.along(axis); + + if proposed_current_pixel_change < 0. { + current_target_size = f32::max(current_target_size, min_size); + } else if proposed_current_pixel_change > 0. { + // TODO: cascade this change to other children if current item is at min size + let next_target_size = f32::max( + next_child_size.along(axis) - proposed_current_pixel_change, + min_size, + ); + current_target_size = f32::min( + current_target_size, + child_size.along(axis) + next_child_size.along(axis) + - next_target_size, + ); + } + + let current_pixel_change = + current_target_size - child_size.along(axis); + let flex_change = + current_pixel_change / drag_bounds.length_along(axis); + let current_target_flex = current_flex + flex_change; + let next_target_flex = next_flex - flex_change; + + let mut borrow = flexes.borrow_mut(); + *borrow.get_mut(ix).unwrap() = current_target_flex; + *borrow.get_mut(next_ix).unwrap() = next_target_flex; + + workspace.schedule_serialize(cx); + cx.notify(); } - - let mut current_target_size = (drag.position - child_start).along(axis); - - let proposed_current_pixel_change = - current_target_size - child_size.along(axis); - - if proposed_current_pixel_change < 0. { - current_target_size = f32::max(current_target_size, min_size); - } else if proposed_current_pixel_change > 0. { - // TODO: cascade this change to other children if current item is at min size - let next_target_size = f32::max( - next_child_size.along(axis) - proposed_current_pixel_change, - min_size, - ); - current_target_size = f32::min( - current_target_size, - child_size.along(axis) + next_child_size.along(axis) - - next_target_size, - ); + }) + .on_click(MouseButton::Left, { + let flexes = self.flexes.clone(); + move |e, v: &mut Workspace, cx| { + if e.click_count >= 2 { + let mut borrow = flexes.borrow_mut(); + *borrow = vec![1.; borrow.len()]; + v.schedule_serialize(cx); + cx.notify(); + } } - - let current_pixel_change = current_target_size - child_size.along(axis); - let flex_change = current_pixel_change / drag_bounds.length_along(axis); - let current_target_flex = current_flex + flex_change; - let next_target_flex = next_flex - flex_change; - - let mut borrow = flexes.borrow_mut(); - *borrow.get_mut(ix).unwrap() = current_target_flex; - *borrow.get_mut(next_ix).unwrap() = next_target_flex; - - workspace.schedule_serialize(cx); - cx.notify(); - }}, - ).on_click(MouseButton::Left, { - let flexes = self.flexes.clone(); - move |e, v: &mut Workspace, cx| { - if e.click_count >= 2 { - let mut borrow = flexes.borrow_mut(); - *borrow = vec![1.; borrow.len()]; - v.schedule_serialize(cx); - cx.notify(); - } - }}); + }); scene.push_mouse_region(mouse_region); scene.pop_stacking_context(); From 8f0b24b264c1d0f621ca7acfc041cfa64c073896 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:01:41 -0600 Subject: [PATCH 107/162] Add moar icons --- assets/icons/file_icons/chevron_down.svg | 3 +++ assets/icons/file_icons/chevron_left.svg | 3 +++ assets/icons/file_icons/chevron_right.svg | 3 +++ assets/icons/file_icons/chevron_up.svg | 3 +++ assets/icons/file_icons/file_types.json | 16 ++++++++---- assets/icons/file_icons/folder.svg | 2 +- .../{folder-open.svg => folder_open.svg} | 2 +- crates/project_panel/src/file_associations.rs | 25 ++++++++++++++++--- 8 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 assets/icons/file_icons/chevron_down.svg create mode 100644 assets/icons/file_icons/chevron_left.svg create mode 100644 assets/icons/file_icons/chevron_right.svg create mode 100644 assets/icons/file_icons/chevron_up.svg rename assets/icons/file_icons/{folder-open.svg => folder_open.svg} (89%) diff --git a/assets/icons/file_icons/chevron_down.svg b/assets/icons/file_icons/chevron_down.svg new file mode 100644 index 0000000000000000000000000000000000000000..b971555cfa0b8c15daf35522a3f3ef449ffac087 --- /dev/null +++ b/assets/icons/file_icons/chevron_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_left.svg b/assets/icons/file_icons/chevron_left.svg new file mode 100644 index 0000000000000000000000000000000000000000..8e61beed5df055132edde2510908324cc8a47fb1 --- /dev/null +++ b/assets/icons/file_icons/chevron_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_right.svg b/assets/icons/file_icons/chevron_right.svg new file mode 100644 index 0000000000000000000000000000000000000000..fcd9d83fc203578f5135a5d040999bea6765769e --- /dev/null +++ b/assets/icons/file_icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_up.svg b/assets/icons/file_icons/chevron_up.svg new file mode 100644 index 0000000000000000000000000000000000000000..171cdd61c0511aabe2f25463089d3cfd9cbf5039 --- /dev/null +++ b/assets/icons/file_icons/chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index edc398a295ac1efe77faf3101823d736f08add4a..b53d263063a1fa75a93ed7a317145af38ed86d07 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -94,20 +94,26 @@ "code": { "icon": "icons/file_icons/code.svg" }, - "default": { - "icon": "icons/file_icons/file.svg" + "collapsed_chevron": { + "icon": "icons/file_icons/chevron_right.svg" }, - "directory": { + "collapsed_folder": { "icon": "icons/file_icons/folder.svg" }, + "default": { + "icon": "icons/file_icons/file.svg" + }, "document": { "icon": "icons/file_icons/book.svg" }, "eslint": { "icon": "icons/file_icons/eslint.svg" }, - "expanded_directory": { - "icon": "icons/file_icons/folder-open.svg" + "expanded_chevron": { + "icon": "icons/file_icons/chevron_down.svg" + }, + "expanded_folder": { + "icon": "icons/file_icons/folder_open.svg" }, "image": { "icon": "icons/file_icons/image.svg" diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg index 4ef944a69cab181eeb323be22acf881fcdbebd3f..d890160100e01d002287005574321b19180891e2 100644 --- a/assets/icons/file_icons/folder.svg +++ b/assets/icons/file_icons/folder.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/folder-open.svg b/assets/icons/file_icons/folder_open.svg similarity index 89% rename from assets/icons/file_icons/folder-open.svg rename to assets/icons/file_icons/folder_open.svg index 405d0b83080b4995a56d19dc1e9538e80fc103af..bf64f6ee396a266bafcd542883ee9728fabb6376 100644 --- a/assets/icons/file_icons/folder-open.svg +++ b/assets/icons/file_icons/folder_open.svg @@ -1,5 +1,5 @@ - + diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 6e2e373d766b02c04fb4ecd9cf4a4ba2cc0d3a0c..2694fa1697f31d76fb4f37e1632c184ac2e9ce8f 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -17,8 +17,10 @@ pub struct FileAssociations { types: HashMap, } -const DIRECTORY_TYPE: &'static str = "directory"; -const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_directory"; +const COLLAPSED_DIRECTORY_TYPE: &'static str = "collapsed_folder"; +const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_folder"; +const COLLAPSED_CHEVRON_TYPE: &'static str = "collapsed_chevron"; +const EXPANDED_CHEVRON_TYPE: &'static str = "expanded_chevron"; pub const FILE_TYPES_ASSET: &'static str = "icons/file_icons/file_types.json"; pub fn init(assets: impl AssetSource, cx: &mut AppContext) { @@ -72,7 +74,24 @@ impl FileAssociations { let key = if expanded { EXPANDED_DIRECTORY_TYPE } else { - DIRECTORY_TYPE + COLLAPSED_DIRECTORY_TYPE + }; + + this.types + .get(key) + .map(|type_config| type_config.icon.clone()) + }) + .unwrap_or_else(|| Arc::from("".to_string())) + } + + pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Arc { + iife!({ + let this = cx.has_global::().then(|| cx.global::())?; + + let key = if expanded { + EXPANDED_CHEVRON_TYPE + } else { + COLLAPSED_CHEVRON_TYPE }; this.types From a7695c47bf1e59aca8a4ebaa6b37b0485f7945f0 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:03:07 -0600 Subject: [PATCH 108/162] Update default settings --- assets/settings/default.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index d35049a84d0ecb800f20fa5a0111ed378cd64c87..343abbf077ff910c0bd3c673f780b2a3da914d49 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -106,6 +106,8 @@ "git_status": true, // Whether to show file icons in the project panel. "file_icons": true, + // Whether to show folder icons or chevrons for directories in the project panel. + "folder_icons": true, // Where to dock project panel. Can be 'left' or 'right'. "dock": "left", // Default width of the project panel. @@ -203,9 +205,7 @@ "copilot": { // The set of glob patterns for which copilot should be disabled // in any matching file. - "disabled_globs": [ - ".env" - ] + "disabled_globs": [".env"] }, // Settings specific to journaling "journal": { From 13ae1249f509a1644fd0d9ce676e12c90b38968a Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:23:28 -0600 Subject: [PATCH 109/162] Allow for folders or chevrons --- crates/project_panel/src/project_panel.rs | 41 ++++++++++------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d97c47a339dfde546550fc9b70ebc75659f416a6..014649f9a81281cbdc5338598c5f93683a51644d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1176,9 +1176,9 @@ impl ProjectPanel { } let end_ix = range.end.min(ix + visible_worktree_entries.len()); - let (git_status_setting, show_file_icons) = { + let (git_status_setting, show_file_icons, show_folder_icons) = { let settings = settings::get::(cx); - (settings.git_status, settings.file_icons) + (settings.git_status, settings.file_icons, settings.folder_icons) }; if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { let snapshot = worktree.read(cx).snapshot(); @@ -1193,10 +1193,18 @@ impl ProjectPanel { for entry in visible_worktree_entries[entry_range].iter() { let status = git_status_setting.then(|| entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); - let icon = show_file_icons.then(|| match entry.kind { - EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx), - _ => FileAssociations::get_folder_icon(is_expanded, cx), - }); + let icon = match entry.kind { + EntryKind::File(_) => if show_file_icons { + Some(FileAssociations::get_icon(&entry.path, cx)) + } else { + None + } + _ => if show_folder_icons { + Some(FileAssociations::get_folder_icon(is_expanded, cx)) + } else { + Some(FileAssociations::get_chevron_icon(is_expanded, cx)) + }, + }; let mut details = EntryDetails { filename: entry @@ -1258,7 +1266,6 @@ impl ProjectPanel { style: &ProjectPanelEntry, cx: &mut ViewContext, ) -> AnyElement { - let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; let mut filename_text_style = style.text.clone(); @@ -1282,26 +1289,14 @@ impl ProjectPanel { .aligned() .constrained() .with_width(style.icon_size) - } else if kind.is_dir() { - if details.is_expanded { - Svg::new("icons/chevron_down_8.svg").with_color(style.chevron_color) - } else { - Svg::new("icons/chevron_right_8.svg").with_color(style.chevron_color) - } - .constrained() - .with_max_width(style.chevron_size) - .with_max_height(style.chevron_size) - .aligned() - .constrained() - .with_width(style.chevron_size) - } else { + } else { Empty::new() .constrained() - .with_max_width(style.chevron_size) - .with_max_height(style.chevron_size) + .with_max_width(style.icon_size) + .with_max_height(style.icon_size) .aligned() .constrained() - .with_width(style.chevron_size) + .with_width(style.icon_size) }) .with_child(if show_editor && editor.is_some() { ChildView::new(editor.as_ref().unwrap(), cx) From 0e068a644f6448bd50cf939ea5e027c69bf412fc Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:23:47 -0600 Subject: [PATCH 110/162] organize settings --- .../project_panel/src/project_panel_settings.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index f0d60d7f4fc0505103c524c2a59022e17772a59d..126433e5a3930c832a8d7c66fa7031e1236172b1 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -12,18 +12,22 @@ pub enum ProjectPanelDockPosition { #[derive(Deserialize, Debug)] pub struct ProjectPanelSettings { - pub git_status: bool, - pub file_icons: bool, - pub dock: ProjectPanelDockPosition, pub default_width: f32, + pub dock: ProjectPanelDockPosition, + pub file_icons: bool, + pub folder_icons: bool, + pub git_status: bool, + pub indent_size: f32, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct ProjectPanelSettingsContent { - pub git_status: Option, - pub file_icons: Option, - pub dock: Option, pub default_width: Option, + pub dock: Option, + pub file_icons: Option, + pub folder_icons: Option, + pub git_status: Option, + pub indent_size: Option, } impl Setting for ProjectPanelSettings { From c56d62fd847d9b3ba611c9fc8e20be061b145c6d Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:29:50 -0600 Subject: [PATCH 111/162] gitmodules to git icon --- assets/icons/file_icons/file_types.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index b53d263063a1fa75a93ed7a317145af38ed86d07..0ccf9c2bb7c7b4eb12f6ca83646ab9a38c661490 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -17,6 +17,7 @@ "fish": "terminal", "gitattributes": "vcs", "gitignore": "vcs", + "gitmodules": "vcs", "gif": "image", "go": "code", "h": "code", From 95947f6d3a2d18e25e7e2fef28e6d0fec5c944ab Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:29:57 -0600 Subject: [PATCH 112/162] icon adjustment --- assets/icons/file_icons/file.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/icons/file_icons/file.svg b/assets/icons/file_icons/file.svg index e05a47548e62ba02045472e141e5f088aa2301fd..bfffe036844c4469544e0112fa2677c177bbc9d8 100644 --- a/assets/icons/file_icons/file.svg +++ b/assets/icons/file_icons/file.svg @@ -1,5 +1,5 @@ - + From abb145da702637e37fd54b0244056547f3f5ae65 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 12:30:35 -0600 Subject: [PATCH 113/162] add indent size to project panel settings --- assets/settings/default.json | 14 ++++++++------ crates/project_panel/src/project_panel.rs | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 343abbf077ff910c0bd3c673f780b2a3da914d49..6dc573ddb6c27b13b49826c6a021d4595c44faae 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,16 +102,18 @@ "show_other_hints": true }, "project_panel": { - // Whether to show the git status in the project panel. - "git_status": true, + // Default width of the project panel. + "default_width": 240, + // Where to dock project panel. Can be 'left' or 'right'. + "dock": "left", // Whether to show file icons in the project panel. "file_icons": true, // Whether to show folder icons or chevrons for directories in the project panel. "folder_icons": true, - // Where to dock project panel. Can be 'left' or 'right'. - "dock": "left", - // Default width of the project panel. - "default_width": 240 + // Whether to show the git status in the project panel. + "git_status": true, + // Amount of indentation for nested items. + "indent_size": 20 }, "assistant": { // Where to dock the assistant. Can be 'left', 'right' or 'bottom'. diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 014649f9a81281cbdc5338598c5f93683a51644d..8164b2860ae80c1a0a11da7016b81362d94f803d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1332,7 +1332,8 @@ impl ProjectPanel { ) -> AnyElement { let kind = details.kind; let path = details.path.clone(); - let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; + let settings = settings::get::(cx); + let padding = theme.container.padding.left + details.depth as f32 * settings.indent_size; let entry_style = if details.is_cut { &theme.cut_entry From 1242b5b4a20ba2a04b918dd79844b196119730b1 Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 13:13:44 -0600 Subject: [PATCH 114/162] Solid tab on folder icon --- assets/icons/file_icons/folder.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg index d890160100e01d002287005574321b19180891e2..fd45ab1c4494b6151f7aa799b8b78b3e427f3d5a 100644 --- a/assets/icons/file_icons/folder.svg +++ b/assets/icons/file_icons/folder.svg @@ -1,5 +1,5 @@ - + From 7d3d54652bd17a3eea52d25e81459fccbde7d4bc Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 15:54:26 -0400 Subject: [PATCH 115/162] Remove unused method --- crates/call/src/call.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3fc76b964d389923fdf6479bcf208a4d1ba14212..2defd6b40f0f778157e6da24684b36d1cd565408 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -284,21 +284,6 @@ impl ActiveCall { } } - pub fn toggle_screen_sharing(&self, cx: &mut AppContext) { - if let Some(room) = self.room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - self.report_call_event("disable screen share", cx); - Task::ready(room.unshare_screen(cx)) - } else { - self.report_call_event("enable screen share", cx); - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } - } - pub fn share_project( &mut self, project: ModelHandle, From 429daf5f8cba347937f928a30afad91eb7807bea Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 16:00:11 -0400 Subject: [PATCH 116/162] Add microphone events to calls --- crates/collab_ui/src/collab_ui.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index dbdeb45573e15aa57c7468ea1ce149ea94f86710..7d1aa5d1b0f7603c747615e5f737f7aa2bda289f 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -64,10 +64,24 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { } pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { + let call = ActiveCall::global(cx).read(cx); if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::toggle_mute) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + let client = call.client(); + room.update(cx, |room, cx| { + if room.is_muted() { + ActiveCall::report_call_event_for_room("enable microphone", room.id(), &client, cx); + } else { + ActiveCall::report_call_event_for_room( + "disable microphone", + room.id(), + &client, + cx, + ); + } + room.toggle_mute(cx) + }) + .map(|task| task.detach_and_log_err(cx)) + .log_err(); } } From 719c56734ab5cde6907dfbd3d002779286a01aa2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 16:21:21 -0400 Subject: [PATCH 117/162] Reuse previously-obtained call object --- crates/collab_ui/src/collab_ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 7d1aa5d1b0f7603c747615e5f737f7aa2bda289f..df4b502391a3830aec28c817983f98b7dad7643c 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -65,7 +65,7 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { let call = ActiveCall::global(cx).read(cx); - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + if let Some(room) = call.room().cloned() { let client = call.client(); room.update(cx, |room, cx| { if room.is_muted() { From 6b95ac9b26982348e53b517607342ae51c48a202 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 13:45:19 -0700 Subject: [PATCH 118/162] fmt --- crates/project_panel/src/project_panel.rs | 30 ++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8164b2860ae80c1a0a11da7016b81362d94f803d..8097f5ecfd5c0cb9187bbfd78f23f8dd1497fb57 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1178,7 +1178,11 @@ impl ProjectPanel { let end_ix = range.end.min(ix + visible_worktree_entries.len()); let (git_status_setting, show_file_icons, show_folder_icons) = { let settings = settings::get::(cx); - (settings.git_status, settings.file_icons, settings.folder_icons) + ( + settings.git_status, + settings.file_icons, + settings.folder_icons, + ) }; if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { let snapshot = worktree.read(cx).snapshot(); @@ -1194,16 +1198,20 @@ impl ProjectPanel { let status = git_status_setting.then(|| entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); let icon = match entry.kind { - EntryKind::File(_) => if show_file_icons { - Some(FileAssociations::get_icon(&entry.path, cx)) - } else { - None + EntryKind::File(_) => { + if show_file_icons { + Some(FileAssociations::get_icon(&entry.path, cx)) + } else { + None + } + } + _ => { + if show_folder_icons { + Some(FileAssociations::get_folder_icon(is_expanded, cx)) + } else { + Some(FileAssociations::get_chevron_icon(is_expanded, cx)) + } } - _ => if show_folder_icons { - Some(FileAssociations::get_folder_icon(is_expanded, cx)) - } else { - Some(FileAssociations::get_chevron_icon(is_expanded, cx)) - }, }; let mut details = EntryDetails { @@ -1289,7 +1297,7 @@ impl ProjectPanel { .aligned() .constrained() .with_width(style.icon_size) - } else { + } else { Empty::new() .constrained() .with_max_width(style.icon_size) From 0769458ae462e711a984d04aad1b75e368ac76df Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 15:04:23 -0600 Subject: [PATCH 119/162] Detail adjustments --- assets/icons/file_icons/archive.svg | 2 +- assets/icons/file_icons/folder_open.svg | 2 +- assets/icons/file_icons/lock.svg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg index 820c5846ba8cd87876de839a53f1f988c964669a..35e3dc59bdb828e2bd96b119f81081e23d488c72 100644 --- a/assets/icons/file_icons/archive.svg +++ b/assets/icons/file_icons/archive.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/folder_open.svg b/assets/icons/file_icons/folder_open.svg index bf64f6ee396a266bafcd542883ee9728fabb6376..55c7d51649a697553629f0c7190d80603abe3323 100644 --- a/assets/icons/file_icons/folder_open.svg +++ b/assets/icons/file_icons/folder_open.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg index b6aa3394efaaac1e83bce0f119dcde155fb01b89..14fed3941acdf7bbd2b71b504694626a860cec57 100644 --- a/assets/icons/file_icons/lock.svg +++ b/assets/icons/file_icons/lock.svg @@ -1,6 +1,6 @@ - + From 1d1da74d72d705070fd931ccc21fc162cef68e4b Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 15:05:26 -0600 Subject: [PATCH 120/162] Adjustment --- assets/icons/file_icons/image.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg index c258170daeaa2e9a57edcd0267e86286749a2b97..d9d5b82af1782ef29adca84309a4fbf7e8a37672 100644 --- a/assets/icons/file_icons/image.svg +++ b/assets/icons/file_icons/image.svg @@ -1,6 +1,6 @@ - + From a9bfe973617bc088d84c08326309264f882642bf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 16:39:13 -0700 Subject: [PATCH 121/162] Add wrap guides and associated settings --- assets/settings/default.json | 13 ++++---- crates/editor/src/editor.rs | 14 +++++++++ crates/editor/src/element.rs | 39 +++++++++++++++++++++--- crates/language/src/language_settings.rs | 9 ++++++ crates/theme/src/theme.rs | 2 ++ styles/src/style_tree/editor.ts | 2 ++ 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 6dc573ddb6c27b13b49826c6a021d4595c44faae..2ae8d5c4a8d564e4b8cc4473f7a9cab844c7b5bb 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -50,6 +50,13 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Whether to show wrap guides in the editor. Setting this to true will + // show a guide at the 'preferred_line_length' value if softwrap is set to + // 'preferred_line_length', and will show any additional guides as specified + // by the 'wrap_guides' setting. + "show_wrap_guides": true, + // Character counts at which to show wrap guides in the editor. + "wrap_guides": [], // Whether to use additional LSP queries to format (and amend) the code after // every "trigger" symbol input, defined by LSP server capabilities. "use_on_type_format": true, @@ -356,12 +363,6 @@ // LSP Specific settings. "lsp": { // Specify the LSP name as a key here. - // As of 8/10/22, supported LSPs are: - // pyright - // gopls - // rust-analyzer - // typescript-language-server - // vscode-json-languageserver // "rust-analyzer": { // //These initialization options are merged into Zed's defaults // "initialization_options": { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b790543ee54c4efe5060411278dc35485377e3b3..87ba250a889500d1d06eea9a4307abd21f10a78a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7086,6 +7086,20 @@ impl Editor { .text() } + pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { + let mut wrap_guides = smallvec::smallvec![]; + + let settings = self.buffer.read(cx).settings_at(0, cx); + if settings.show_wrap_guides { + if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { + wrap_guides.push((soft_wrap as usize, true)); + } + wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) + } + + wrap_guides + } + pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { let settings = self.buffer.read(cx).settings_at(0, cx); let mode = self diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1189503c58648d9dca545ad0317ff6ea0635a489..3f84d7e5036405a00d0fe19d4aef3c7d3b5b0558 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -541,6 +541,25 @@ impl EditorElement { corner_radius: 0., }); } + + for (wrap_position, active) in layout.wrap_guides.iter() { + let x = text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.; + let color = if *active { + self.style.active_wrap_guide + } else { + self.style.wrap_guide + }; + scene.push_quad(Quad { + bounds: RectF::new( + vec2f(x, text_bounds.origin_y()), + vec2f(1., text_bounds.height()), + ), + background: Some(color), + border: Border::new(0., Color::transparent_black()), + corner_radius: 0., + }); + } + } } @@ -1320,16 +1339,15 @@ impl EditorElement { } } - fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { - let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; + fn column_pixels(&self, column: usize, cx: &ViewContext) -> f32 { let style = &self.style; cx.text_layout_cache() .layout_str( - "1".repeat(digit_count).as_str(), + " ".repeat(column).as_str(), style.text.font_size, &[( - digit_count, + column, RunStyle { font_id: style.text.font_id, color: Color::black(), @@ -1340,6 +1358,11 @@ impl EditorElement { .width() } + fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { + let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; + self.column_pixels(digit_count, cx) + } + //Folds contained in a hunk are ignored apart from shrinking visual size //If a fold contains any hunks then that fold line is marked as modified fn layout_git_gutters( @@ -2025,6 +2048,12 @@ impl Element for EditorElement { } }; + let wrap_guides = editor + .wrap_guides(cx) + .iter() + .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) + .collect(); + let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height; if let EditorMode::AutoHeight { max_lines } = snapshot.mode { size.set_y( @@ -2385,6 +2414,7 @@ impl Element for EditorElement { snapshot, }), visible_display_row_range: start_row..end_row, + wrap_guides, gutter_size, gutter_padding, text_size, @@ -2535,6 +2565,7 @@ pub struct LayoutState { gutter_margin: f32, text_size: Vector2F, mode: EditorMode, + wrap_guides: SmallVec<[(f32, bool); 2]>, visible_display_row_range: Range, active_rows: BTreeMap, highlighted_rows: Option>, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 820217567a60b3ce7250014b85fd104967d762a7..c3f706802a5c15446304e490d70cd14f6c7f2c86 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -44,6 +44,8 @@ pub struct LanguageSettings { pub hard_tabs: bool, pub soft_wrap: SoftWrap, pub preferred_line_length: u32, + pub show_wrap_guides: bool, + pub wrap_guides: Vec, pub format_on_save: FormatOnSave, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, @@ -84,6 +86,10 @@ pub struct LanguageSettingsContent { #[serde(default)] pub preferred_line_length: Option, #[serde(default)] + pub show_wrap_guides: Option, + #[serde(default)] + pub wrap_guides: Option>, + #[serde(default)] pub format_on_save: Option, #[serde(default)] pub remove_trailing_whitespace_on_save: Option, @@ -378,6 +384,9 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent merge(&mut settings.tab_size, src.tab_size); merge(&mut settings.hard_tabs, src.hard_tabs); merge(&mut settings.soft_wrap, src.soft_wrap); + merge(&mut settings.show_wrap_guides, src.show_wrap_guides); + merge(&mut settings.wrap_guides, src.wrap_guides.clone()); + merge( &mut settings.preferred_line_length, src.preferred_line_length, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 29c0d9ce8ebbd515a85dfd149504ed22d2fd7691..81ae7a65cab6e83e4d97901e786140e2e8304887 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -691,6 +691,8 @@ pub struct Editor { pub document_highlight_read_background: Color, pub document_highlight_write_background: Color, pub diff: DiffStyle, + pub wrap_guide: Color, + pub active_wrap_guide: Color, pub line_number: Color, pub line_number_active: Color, pub guest_selections: Vec, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 7e20f09b32111cb6d7a2b7c8d1afbd21e4ccf73b..0f874206101bce5bf247ff470d4835ee4fb7ec6c 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -170,6 +170,8 @@ export default function editor(): any { line_number: with_opacity(foreground(layer), 0.35), line_number_active: foreground(layer), rename_fade: 0.6, + wrap_guide: with_opacity(foreground(layer), 0.1), + active_wrap_guide: with_opacity(foreground(layer), 0.1), unnecessary_code_fade: 0.5, selection: theme.players[0], whitespace: theme.ramps.neutral(0.5).hex(), From 05a84093635f9b7f76d9a8f664c23de8b71e8e16 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 16:45:41 -0700 Subject: [PATCH 122/162] bump the brightness of the active wrap guide --- crates/editor/src/element.rs | 1 - styles/src/style_tree/editor.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 3f84d7e5036405a00d0fe19d4aef3c7d3b5b0558..b48fa5b56dd831f82852f224370500027302f3af 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -559,7 +559,6 @@ impl EditorElement { corner_radius: 0., }); } - } } diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 0f874206101bce5bf247ff470d4835ee4fb7ec6c..acf983e8bee13f24d2897191c283217e1ee2ee55 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -171,7 +171,7 @@ export default function editor(): any { line_number_active: foreground(layer), rename_fade: 0.6, wrap_guide: with_opacity(foreground(layer), 0.1), - active_wrap_guide: with_opacity(foreground(layer), 0.1), + active_wrap_guide: with_opacity(foreground(layer), 0.2), unnecessary_code_fade: 0.5, selection: theme.players[0], whitespace: theme.ramps.neutral(0.5).hex(), From 73379100340272b8ae748bd0c2f7aff69f990b4c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 19:41:38 -0600 Subject: [PATCH 123/162] Fix enter in search --- assets/keymaps/vim.json | 2 +- crates/vim/src/editor_events.rs | 4 ++-- crates/vim/src/normal/search.rs | 15 +++++++++++++++ crates/vim/src/state.rs | 1 + crates/vim/src/vim.rs | 21 ++++++++++++++++----- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8fb174c71eb9bb7cd7466b603525e492179d46c2..7312c1e2f8ccfb0a8a4d193b6249f3a27137be14 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -426,7 +426,7 @@ } }, { - "context": "BufferSearchBar", + "context": "BufferSearchBar > VimEnabled", "bindings": { "enter": "vim::SearchSubmit", "escape": "buffer_search::Dismiss" diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index a11f1cc182c64610ff588d46b51b142563b1abd7..60e63f982347b9fee2a10087d530527a1db69ad4 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -13,7 +13,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) { cx.update_window(previously_active_editor.window_id(), |cx| { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |previously_active_editor, cx| { - Vim::unhook_vim_settings(previously_active_editor, cx); + vim.unhook_vim_settings(previously_active_editor, cx) }); }); }); @@ -35,7 +35,7 @@ fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) { } } - editor.update(cx, |editor, cx| Vim::unhook_vim_settings(editor, cx)) + editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx)) }); }); } diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index cae64a40a6b0cdb290014539b7c10ead6a7a0b82..1e3a64c3a57baa539a06dc14921c6a3e3b2dff58 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -282,4 +282,19 @@ mod test { cx.simulate_keystrokes(["enter"]); cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal); } + + #[gpui::test] + async fn test_non_vim_search( + cx: &mut gpui::TestAppContext, + deterministic: Arc, + ) { + let mut cx = VimTestContext::new(cx, false).await; + cx.set_state("ˇone one one one", Mode::Normal); + cx.simulate_keystrokes(["cmd-f"]); + deterministic.run_until_parked(); + + cx.assert_editor_state("«oneˇ» one one one"); + cx.simulate_keystrokes(["enter"]); + cx.assert_editor_state("one «oneˇ» one one"); + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 6434b710b2a37f75bc0687069bec9b69431b6405..23471066cdf8ff7da25aab039faa38997f98395b 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -91,6 +91,7 @@ impl VimState { pub fn keymap_context_layer(&self) -> KeymapContext { let mut context = KeymapContext::default(); + context.add_identifier("VimEnabled"); context.add_key( "vim_mode", match self.mode { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index ada8f2c1defdd5014d673239b9c3f57d5e8124d9..69b94428ddad7a3f4f96cf4ad40abcbecd5a3124 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -14,8 +14,8 @@ use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ - actions, impl_actions, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + actions, impl_actions, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use language::CursorShape; use motion::Motion; @@ -304,17 +304,28 @@ impl Vim { // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur, // but we need collapse_matches to persist when the search bar is focused. editor.set_collapse_matches(false); - Self::unhook_vim_settings(editor, cx); + self.unhook_vim_settings(editor, cx); } }); } - fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext) { + fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext) { editor.set_cursor_shape(CursorShape::Bar, cx); editor.set_clip_at_line_ends(false, cx); editor.set_input_enabled(true); editor.selections.line_mode = false; - editor.remove_keymap_context_layer::(cx); + + // we set the VimEnabled context on all editors so that we + // can distinguish between vim mode and non-vim mode in the BufferSearchBar. + // This is a bit of a hack, but currently the search crate does not depend on vim, + // and it seems nice to keep it that way. + if self.enabled { + let mut context = KeymapContext::default(); + context.add_identifier("VimEnabled"); + editor.set_keymap_context_layer::(context, cx) + } else { + editor.remove_keymap_context_layer::(cx); + } } } From d98fcc4402ca030194181c35e41eb80d433b954f Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 21 Jul 2023 02:44:44 -0400 Subject: [PATCH 124/162] Add key binding to close all docks --- assets/keymaps/default.json | 1 + crates/workspace/src/workspace.rs | 18 ++++++++++++++++++ crates/zed/src/menus.rs | 1 + 3 files changed, 20 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 883b0c1872a47ea9242716cfbd39298ccdfacb94..7f420e6d93654a10ce1c61111f1044e5f346392a 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -406,6 +406,7 @@ "cmd-b": "workspace::ToggleLeftDock", "cmd-r": "workspace::ToggleRightDock", "cmd-j": "workspace::ToggleBottomDock", + "alt-cmd-y": "workspace::CloseAllDocks", "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0ebd01e1f7a706e1e2baf3b15ff1f35a51a3fb7f..c64b5189e18f91eb763f7d2452c994efa1c1b826 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -141,6 +141,7 @@ actions!( ToggleLeftDock, ToggleRightDock, ToggleBottomDock, + CloseAllDocks, ] ); @@ -281,6 +282,9 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { workspace.toggle_dock(DockPosition::Bottom, cx); }); + cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| { + workspace.close_all_docks(cx); + }); cx.add_action(Workspace::activate_pane_at_index); cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { workspace.reopen_closed_item(cx).detach(); @@ -1670,6 +1674,20 @@ impl Workspace { self.serialize_workspace(cx); } + pub fn close_all_docks(&mut self, cx: &mut ViewContext) { + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + + for dock in docks { + dock.update(cx, |dock, cx| { + dock.set_open(false, cx); + }); + } + + cx.focus_self(); + cx.notify(); + self.serialize_workspace(cx); + } + /// Transfer focus to the panel of the given type. pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { self.focus_or_unfocus_panel::(cx, |_, _| true)? diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 9112cd207b5da73f6a7d20a0436db152f98114d2..22a260b588e6ae002a6750e94179e5e118ee52a6 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -93,6 +93,7 @@ pub fn menus() -> Vec> { MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), + MenuItem::action("Close All Docks", workspace::CloseAllDocks), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ From cd3620692b1c7a64379ebc0226c6a1d4e05a0962 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 21 Jul 2023 11:28:56 +0300 Subject: [PATCH 125/162] Do not highlight fake URLs in terminal --- crates/terminal/src/terminal.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 3a64cff24f870eab763addb8936daa14e285fda3..f81af1319e504fa0cf529de418ec88f07c023d41 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 - 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(r#"(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(); } @@ -875,8 +875,10 @@ impl Terminal { } 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(); - + let is_url = match regex_match_at(term, point, &URL_REGEX) { + Some(url_match) => url_match == word_match, + None => false, + }; Some((maybe_url_or_path, is_url, word_match)) } else { None From 807279208d2fd3c08da0a10bca47c3f1d5df0b9d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 21 Jul 2023 09:08:40 -0600 Subject: [PATCH 126/162] Fix shift-enter in search If you want to type a newline in an auto_height editor, ctrl and ctrl-shift are your friends. --- assets/keymaps/default.json | 4 ++-- crates/vim/src/normal/search.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 883b0c1872a47ea9242716cfbd39298ccdfacb94..7ebf5004ddfaec95c482e6cd3a4bc7abb2f65108 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -195,8 +195,8 @@ { "context": "Editor && mode == auto_height", "bindings": { - "shift-enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineBelow" + "ctrl-enter": "editor::Newline", + "ctrl-shift-enter": "editor::NewlineBelow" } }, { diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 1e3a64c3a57baa539a06dc14921c6a3e3b2dff58..d584c575d2970e3fa1131acb08b1adfac7ca38a9 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -296,5 +296,7 @@ mod test { cx.assert_editor_state("«oneˇ» one one one"); cx.simulate_keystrokes(["enter"]); cx.assert_editor_state("one «oneˇ» one one"); + cx.simulate_keystrokes(["shift-enter"]); + cx.assert_editor_state("«oneˇ» one one one"); } } From 8ba69c15d1f5014d31fccb8a69eb19cdc330c6ac Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 19 Jul 2023 18:54:29 -0600 Subject: [PATCH 127/162] refactor: Remove G/Z Namespace support This previously enabled things like `d g g` to work, but we can fix that instead by not clearing out pending vim state on change. Either way, it is unnecessary and causes some user-confusion (zed-industries/community#176), so remove this code for now; and use comments to organize the file a bit instead. --- assets/keymaps/vim.json | 67 ++++++++++++++-------------------------- crates/vim/src/motion.rs | 5 ++- crates/vim/src/normal.rs | 25 ++------------- crates/vim/src/state.rs | 9 ------ crates/vim/src/vim.rs | 7 +++-- 5 files changed, 34 insertions(+), 79 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 7312c1e2f8ccfb0a8a4d193b6249f3a27137be14..5aa448e9d1ae74a719172fab084951beb61ee21c 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -2,12 +2,6 @@ { "context": "Editor && VimControl && !VimWaiting && !menu", "bindings": { - "g": [ - "vim::PushOperator", - { - "Namespace": "G" - } - ], "i": [ "vim::PushOperator", { @@ -110,6 +104,30 @@ "*": "vim::MoveToNext", "#": "vim::MoveToPrev", "0": "vim::StartOfLine", // When no number operator present, use start of line motion + // "g" commands + "g g": "vim::StartOfDocument", + "g h": "editor::Hover", + "g t": "pane::ActivateNextItem", + "g shift-t": "pane::ActivatePrevItem", + "g d": "editor::GoToDefinition", + "g shift-d": "editor::GoToTypeDefinition", + "g *": [ + "vim::MoveToNext", + { + "partialWord": true + } + ], + "g #": [ + "vim::MoveToPrev", + { + "partialWord": true + } + ], + // z commands + "z t": "editor::ScrollCursorTop", + "z z": "editor::ScrollCursorCenter", + "z b": "editor::ScrollCursorBottom", + // Count support "1": [ "vim::Number", 1 @@ -234,12 +252,6 @@ "vim::PushOperator", "Yank" ], - "z": [ - "vim::PushOperator", - { - "Namespace": "Z" - } - ], "i": [ "vim::SwitchMode", "Insert" @@ -306,29 +318,6 @@ ] } }, - { - "context": "Editor && vim_operator == g", - "bindings": { - "g": "vim::StartOfDocument", - "h": "editor::Hover", - "t": "pane::ActivateNextItem", - "shift-t": "pane::ActivatePrevItem", - "d": "editor::GoToDefinition", - "shift-d": "editor::GoToTypeDefinition", - "*": [ - "vim::MoveToNext", - { - "partialWord": true - } - ], - "#": [ - "vim::MoveToPrev", - { - "partialWord": true - } - ] - } - }, { "context": "Editor && vim_operator == c", "bindings": { @@ -347,14 +336,6 @@ "y": "vim::CurrentLine" } }, - { - "context": "Editor && vim_operator == z", - "bindings": { - "t": "editor::ScrollCursorTop", - "z": "editor::ScrollCursorCenter", - "b": "editor::ScrollCursorBottom" - } - }, { "context": "Editor && VimObject", "bindings": { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 07b095dd5e186412d566badc8cbdda5be637130b..fb742af3aba5036fd813889d9ee94851bb15d89d 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -127,9 +127,8 @@ pub fn init(cx: &mut AppContext) { } pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { - if let Some(Operator::Namespace(_)) - | Some(Operator::FindForward { .. }) - | Some(Operator::FindBackward { .. }) = Vim::read(cx).active_operator() + if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) = + Vim::read(cx).active_operator() { Vim::update(cx, |vim, cx| vim.pop_operator(cx)); } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 8dcaa5008ef60f1e184ff2b251569d20a5b68943..e0765839a0827617dd9eefa6fb428b597c7a4e3f 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -107,7 +107,7 @@ pub fn normal_motion( Some(Operator::Delete) => delete_motion(vim, motion, times, cx), Some(Operator::Yank) => yank_motion(vim, motion, times, cx), Some(operator) => { - // Can't do anything for text objects or namespace operators. Ignoring + // Can't do anything for text objects, Ignoring error!("Unexpected normal mode motion operator: {:?}", operator) } } @@ -441,11 +441,8 @@ mod test { use indoc::indoc; use crate::{ - state::{ - Mode::{self, *}, - Namespace, Operator, - }, - test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext}, + state::Mode::{self, *}, + test::{ExemptionFeatures, NeovimBackedTestContext}, }; #[gpui::test] @@ -610,22 +607,6 @@ mod test { .await; } - #[gpui::test] - async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) { - let mut cx = VimTestContext::new(cx, true).await; - - // Can abort with escape to get back to normal mode - cx.simulate_keystroke("g"); - assert_eq!(cx.mode(), Normal); - assert_eq!( - cx.active_operator(), - Some(Operator::Namespace(Namespace::G)) - ); - cx.simulate_keystroke("escape"); - assert_eq!(cx.mode(), Normal); - assert_eq!(cx.active_operator(), None); - } - #[gpui::test] async fn test_gg(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 23471066cdf8ff7da25aab039faa38997f98395b..bf644c08cc4940feae3e37f231e632ab375af997 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -16,16 +16,9 @@ impl Default for Mode { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub enum Namespace { - G, - Z, -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] pub enum Operator { Number(usize), - Namespace(Namespace), Change, Delete, Yank, @@ -126,8 +119,6 @@ impl Operator { pub fn id(&self) -> &'static str { match self { Operator::Number(_) => "n", - Operator::Namespace(Namespace::G) => "g", - Operator::Namespace(Namespace::Z) => "z", Operator::Object { around: false } => "i", Operator::Object { around: true } => "a", Operator::Change => "c", diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 69b94428ddad7a3f4f96cf4ad40abcbecd5a3124..82d2e752c39c31b44a562b7fdd2d62b77377fa53 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -14,7 +14,7 @@ use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ - actions, impl_actions, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, + actions, impl_actions,keymap_matcher::MatchResult, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use language::CursorShape; @@ -90,7 +90,10 @@ pub fn init(cx: &mut AppContext) { } pub fn observe_keystrokes(cx: &mut WindowContext) { - cx.observe_keystrokes(|_keystroke, _result, handled_by, cx| { + cx.observe_keystrokes(|_keystroke, result, handled_by, cx| { + if result == &MatchResult::Pending { + return true; + } if let Some(handled_by) = handled_by { // Keystroke is handled by the vim system, so continue forward if handled_by.namespace() == "vim" { From a50d30bf8e47d50a12ff3ab5803dc1ad6577c5af Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 09:08:07 -0600 Subject: [PATCH 128/162] Quality of life shortcuts for code actions --- assets/keymaps/vim.json | 5 ++++- crates/vim/src/normal.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 5aa448e9d1ae74a719172fab084951beb61ee21c..da850662f28341e984c6fb586dbbc29fedbe1290 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -111,6 +111,8 @@ "g shift-t": "pane::ActivatePrevItem", "g d": "editor::GoToDefinition", "g shift-d": "editor::GoToTypeDefinition", + "g .": "editor::ToggleCodeActions", // zed specific + "g shift-a": "editor::FindAllReferences", // zed specific "g *": [ "vim::MoveToNext", { @@ -321,7 +323,8 @@ { "context": "Editor && vim_operator == c", "bindings": { - "c": "vim::CurrentLine" + "c": "vim::CurrentLine", + "d": "editor::Rename" // zed specific } }, { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index e0765839a0827617dd9eefa6fb428b597c7a4e3f..79c990ffebcd58532d9419564afaee183baa515b 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -441,7 +441,7 @@ mod test { use indoc::indoc; use crate::{ - state::Mode::{self, *}, + state::Mode::{self}, test::{ExemptionFeatures, NeovimBackedTestContext}, }; From 4772e4ccee6497af670942011ee1c41fde6fb433 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 15:22:15 -0600 Subject: [PATCH 129/162] vim: add , and ; --- assets/keymaps/vim.json | 7 ++ crates/vim/src/motion.rs | 64 ++++++++++++++++++- crates/vim/src/state.rs | 4 ++ crates/vim/src/vim.rs | 12 ++-- .../vim/test_data/test_comma_semicolon.json | 17 +++++ 5 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 crates/vim/test_data/test_comma_semicolon.json diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index da850662f28341e984c6fb586dbbc29fedbe1290..94a271f037848f3f09255986c124174fa88d52c0 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -292,6 +292,13 @@ "backwards": true } ], + ";": "vim::RepeatFind", + ",": [ + "vim::RepeatFind", + { + "backwards": true + } + ], "ctrl-f": "vim::PageDown", "pagedown": "vim::PageDown", "ctrl-b": "vim::PageUp", diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index fb742af3aba5036fd813889d9ee94851bb15d89d..b8bd256d8abb2b16f3b9f0e5f357dea0abb8f39c 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -62,6 +62,12 @@ struct PreviousWordStart { ignore_punctuation: bool, } +#[derive(Clone, Deserialize, PartialEq)] +struct RepeatFind { + #[serde(default)] + backwards: bool, +} + actions!( vim, [ @@ -82,7 +88,10 @@ actions!( NextLineStart, ] ); -impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]); +impl_actions!( + vim, + [NextWordStart, NextWordEnd, PreviousWordStart, RepeatFind] +); pub fn init(cx: &mut AppContext) { cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx)); @@ -123,7 +132,10 @@ pub fn init(cx: &mut AppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); - cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx)) + cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx)); + cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| { + repeat_motion(action.backwards, cx) + }) } pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { @@ -145,6 +157,35 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| vim.clear_operator(cx)); } +fn repeat_motion(backwards: bool, cx: &mut WindowContext) { + let find = match Vim::read(cx).state.last_find.clone() { + Some(Motion::FindForward { before, text }) => { + if backwards { + Motion::FindBackward { + after: before, + text, + } + } else { + Motion::FindForward { before, text } + } + } + + Some(Motion::FindBackward { after, text }) => { + if backwards { + Motion::FindForward { + before: after, + text, + } + } else { + Motion::FindBackward { after, text } + } + } + _ => return, + }; + + motion(find, cx) +} + // Motion handling is specified here: // https://github.com/vim/vim/blob/master/runtime/doc/motion.txt impl Motion { @@ -742,4 +783,23 @@ mod test { cx.simulate_shared_keystrokes(["%"]).await; cx.assert_shared_state("func boop(ˇ) {\n}").await; } + + #[gpui::test] + async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("ˇone two three four").await; + cx.simulate_shared_keystrokes(["f", "o"]).await; + cx.assert_shared_state("one twˇo three four").await; + cx.simulate_shared_keystrokes([","]).await; + cx.assert_shared_state("ˇone two three four").await; + cx.simulate_shared_keystrokes(["2", ";"]).await; + cx.assert_shared_state("one two three fˇour").await; + cx.simulate_shared_keystrokes(["shift-t", "e"]).await; + cx.assert_shared_state("one two threeˇ four").await; + cx.simulate_shared_keystrokes(["3", ";"]).await; + cx.assert_shared_state("oneˇ two three four").await; + cx.simulate_shared_keystrokes([","]).await; + cx.assert_shared_state("one two thˇree four").await; + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index bf644c08cc4940feae3e37f231e632ab375af997..eb52945cedd80346560be26207acce13e52bbdb0 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -3,6 +3,8 @@ use language::CursorShape; use serde::{Deserialize, Serialize}; use workspace::searchable::Direction; +use crate::motion::Motion; + #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum Mode { Normal, @@ -33,6 +35,8 @@ pub struct VimState { pub mode: Mode, pub operator_stack: Vec, pub search: SearchState, + + pub last_find: Option, } pub struct SearchState { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 82d2e752c39c31b44a562b7fdd2d62b77377fa53..e31fa4adddb043d61ec386bc21a3660ea34bb86d 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -14,8 +14,8 @@ use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ - actions, impl_actions,keymap_matcher::MatchResult, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext, + Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use language::CursorShape; use motion::Motion; @@ -246,10 +246,14 @@ impl Vim { match Vim::read(cx).active_operator() { Some(Operator::FindForward { before }) => { - motion::motion(Motion::FindForward { before, text }, cx) + let find = Motion::FindForward { before, text }; + Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone())); + motion::motion(find, cx) } Some(Operator::FindBackward { after }) => { - motion::motion(Motion::FindBackward { after, text }, cx) + let find = Motion::FindBackward { after, text }; + Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone())); + motion::motion(find, cx) } Some(Operator::Replace) => match Vim::read(cx).state.mode { Mode::Normal => normal_replace(text, cx), diff --git a/crates/vim/test_data/test_comma_semicolon.json b/crates/vim/test_data/test_comma_semicolon.json new file mode 100644 index 0000000000000000000000000000000000000000..8cde887ed1262bd83d2a5fb8307fe924466579ca --- /dev/null +++ b/crates/vim/test_data/test_comma_semicolon.json @@ -0,0 +1,17 @@ +{"Put":{"state":"ˇone two three four"}} +{"Key":"f"} +{"Key":"o"} +{"Get":{"state":"one twˇo three four","mode":"Normal"}} +{"Key":","} +{"Get":{"state":"ˇone two three four","mode":"Normal"}} +{"Key":"2"} +{"Key":";"} +{"Get":{"state":"one two three fˇour","mode":"Normal"}} +{"Key":"shift-t"} +{"Key":"e"} +{"Get":{"state":"one two threeˇ four","mode":"Normal"}} +{"Key":"3"} +{"Key":";"} +{"Get":{"state":"oneˇ two three four","mode":"Normal"}} +{"Key":","} +{"Get":{"state":"one two thˇree four","mode":"Normal"}} From 243a1a854eb542bf8748838156f1eb97e3a39632 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 21 Jul 2023 14:25:30 -0400 Subject: [PATCH 130/162] Avoid panic from assuming a vim operator exists on the operator stack --- crates/vim/src/visual.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 5e22e77bf08eca70ac71e1ecc9202e5d6d2d325c..d87e4ff97471addf600fb6fe546af1ef8331b0ed 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -58,7 +58,9 @@ pub fn visual_motion(motion: Motion, times: Option, cx: &mut WindowContex pub fn visual_object(object: Object, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { - if let Operator::Object { around } = vim.pop_operator(cx) { + if let Some(Operator::Object { around }) = vim.active_operator() { + vim.pop_operator(cx); + vim.update_active_editor(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_with(|map, selection| { From 595bc16749db7384c32f92ec87719428cbb5a186 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 20 Jul 2023 16:01:01 +0300 Subject: [PATCH 131/162] Add search in directory action in the project panel --- assets/keymaps/default.json | 3 +- crates/project_panel/src/project_panel.rs | 110 +++++++++++++++- crates/search/src/project_search.rs | 152 +++++++++++++++++++++- crates/workspace/src/workspace.rs | 6 +- crates/zed/src/zed.rs | 21 +++ 5 files changed, 287 insertions(+), 5 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 92ae4a81ee02c68bb84ab898c2e257fbdcb7a1f2..d970df1abdeb3a4d0de973e88294f17cb5baa4e4 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -529,7 +529,8 @@ "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" + "alt-cmd-r": "project_panel::RevealInFinder", + "alt-shift-f": "project_panel::NewSearchInDirectory" } }, { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8097f5ecfd5c0cb9187bbfd78f23f8dd1497fb57..6f5ae99df9df6f9a212172eb8d71a410a4ede282 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -125,7 +125,8 @@ actions!( Paste, Delete, Rename, - ToggleFocus + ToggleFocus, + NewSearchInDirectory, ] ); @@ -151,6 +152,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) { cx.add_action(ProjectPanel::copy_path); cx.add_action(ProjectPanel::copy_relative_path); cx.add_action(ProjectPanel::reveal_in_finder); + cx.add_action(ProjectPanel::new_search_in_directory); cx.add_action( |this: &mut ProjectPanel, action: &Paste, cx: &mut ViewContext| { this.paste(action, cx); @@ -169,6 +171,9 @@ pub enum Event { }, DockPositionChanged, Focus, + NewSearchInDirectory { + dir_entry: Entry, + }, } #[derive(Serialize, Deserialize)] @@ -417,6 +422,12 @@ impl ProjectPanel { CopyRelativePath, )); menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder)); + if entry.is_dir() { + menu_entries.push(ContextMenuItem::action( + "Search inside", + NewSearchInDirectory, + )); + } if let Some(clipboard_entry) = self.clipboard_entry { if clipboard_entry.worktree_id() == worktree.id() { menu_entries.push(ContextMenuItem::action("Paste", Paste)); @@ -928,6 +939,20 @@ impl ProjectPanel { } } + pub fn new_search_in_directory( + &mut self, + _: &NewSearchInDirectory, + cx: &mut ViewContext, + ) { + if let Some((_, entry)) = self.selected_entry(cx) { + if entry.is_dir() { + cx.emit(Event::NewSearchInDirectory { + dir_entry: entry.clone(), + }); + } + } + } + fn move_entry( &mut self, entry_to_move: ProjectEntryId, @@ -1677,7 +1702,11 @@ mod tests { use project::FakeFs; use serde_json::json; use settings::SettingsStore; - use std::{collections::HashSet, path::Path}; + use std::{ + collections::HashSet, + path::Path, + sync::atomic::{self, AtomicUsize}, + }; use workspace::{pane, AppState}; #[gpui::test] @@ -2516,6 +2545,83 @@ mod tests { ); } + #[gpui::test] + async fn test_new_search_in_directory_trigger(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); + + let new_search_events_count = Arc::new(AtomicUsize::new(0)); + let _subscription = panel.update(cx, |_, cx| { + let subcription_count = Arc::clone(&new_search_events_count); + cx.subscribe(&cx.handle(), move |_, _, event, _| { + if matches!(event, Event::NewSearchInDirectory { .. }) { + subcription_count.fetch_add(1, atomic::Ordering::SeqCst); + } + }) + }); + + toggle_expand_dir(&panel, "src/test", cx); + select_path(&panel, "src/test/first.rs", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " first.rs <== selected", + " second.rs", + " third.rs" + ] + ); + panel.update(cx, |panel, cx| { + panel.new_search_in_directory(&NewSearchInDirectory, cx) + }); + assert_eq!( + new_search_events_count.load(atomic::Ordering::SeqCst), + 0, + "Should not trigger new search in directory when called on a file" + ); + + select_path(&panel, "src/test", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test <== selected", + " first.rs", + " second.rs", + " third.rs" + ] + ); + panel.update(cx, |panel, cx| { + panel.new_search_in_directory(&NewSearchInDirectory, cx) + }); + assert_eq!( + new_search_events_count.load(atomic::Ordering::SeqCst), + 1, + "Should trigger new search in directory when called on a directory" + ); + } + fn toggle_expand_dir( panel: &ViewHandle, path: impl AsRef, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index abebb9a48f1a853097248d796550f00bb3e6cd70..9054d9e1213dcc0544af0faeb35eb8bd74a3f117 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -18,7 +18,7 @@ use gpui::{ Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, }; use menu::Confirm; -use project::{search::SearchQuery, Project}; +use project::{search::SearchQuery, Entry, Project}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -501,6 +501,28 @@ impl ProjectSearchView { this } + pub fn new_search_in_directory( + workspace: &mut Workspace, + dir_entry: &Entry, + cx: &mut ViewContext, + ) { + if !dir_entry.is_dir() { + return; + } + let filter_path = dir_entry.path.join("**"); + let Some(filter_str) = filter_path.to_str() else { return; }; + + let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); + let search = cx.add_view(|cx| ProjectSearchView::new(model, cx)); + workspace.add_item(Box::new(search.clone()), cx); + search.update(cx, |search, cx| { + search + .included_files_editor + .update(cx, |editor, cx| editor.set_text(filter_str, cx)); + search.focus_query_editor(cx) + }); + } + // 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( @@ -1414,6 +1436,134 @@ pub mod tests { }); } + #[gpui::test] + async fn test_new_project_search_in_directory( + deterministic: Arc, + cx: &mut TestAppContext, + ) { + init_test(cx); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a": { + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;", + }, + "b": { + "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 worktree_id = project.read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + + let active_item = cx.read(|cx| { + workspace + .read(cx) + .active_pane() + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + }); + assert!( + active_item.is_none(), + "Expected no search panel to be active, but got: {active_item:?}" + ); + + let one_file_entry = cx.update(|cx| { + workspace + .read(cx) + .project() + .read(cx) + .entry_for_path(&(worktree_id, "a/one.rs").into(), cx) + .expect("no entry for /a/one.rs file") + }); + assert!(one_file_entry.is_file()); + workspace.update(cx, |workspace, cx| { + ProjectSearchView::new_search_in_directory(workspace, &one_file_entry, cx) + }); + let active_search_entry = cx.read(|cx| { + workspace + .read(cx) + .active_pane() + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + }); + assert!( + active_search_entry.is_none(), + "Expected no search panel to be active for file entry" + ); + + let a_dir_entry = cx.update(|cx| { + workspace + .read(cx) + .project() + .read(cx) + .entry_for_path(&(worktree_id, "a").into(), cx) + .expect("no entry for /a/ directory") + }); + assert!(a_dir_entry.is_dir()); + workspace.update(cx, |workspace, cx| { + ProjectSearchView::new_search_in_directory(workspace, &a_dir_entry, cx) + }); + + let Some(search_view) = cx.read(|cx| { + workspace + .read(cx) + .active_pane() + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + }) else { + panic!("Search view expected to appear after new search in directory event trigger") + }; + deterministic.run_until_parked(); + search_view.update(cx, |search_view, cx| { + assert!( + search_view.query_editor.is_focused(cx), + "On new search in directory, focus should be moved into query editor" + ); + search_view.excluded_files_editor.update(cx, |editor, cx| { + assert!( + editor.display_text(cx).is_empty(), + "New search in directory should not have any excluded files" + ); + }); + search_view.included_files_editor.update(cx, |editor, cx| { + assert_eq!( + editor.display_text(cx), + a_dir_entry.path.join("**").display().to_string(), + "New search in directory should have included dir entry path" + ); + }); + }); + + search_view.update(cx, |search_view, cx| { + search_view + .query_editor + .update(cx, |query_editor, cx| query_editor.set_text("const", cx)); + search_view.search(cx); + }); + deterministic.run_until_parked(); + search_view.update(cx, |search_view, cx| { + assert_eq!( + search_view + .results_editor + .update(cx, |editor, cx| editor.display_text(cx)), + "\n\nconst ONE: usize = 1;\n\n\nconst TWO: usize = one::ONE + one::ONE;", + "New search in directory should have a filter that matches a certain directory" + ); + }); + } + pub fn init_test(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); let fonts = cx.font_cache(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c64b5189e18f91eb763f7d2452c994efa1c1b826..5c1a75e97abf33ba1247f078f2a0d86e251988cd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -512,7 +512,7 @@ pub struct Workspace { follower_states_by_leader: FollowerStatesByLeader, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, - active_call: Option<(ModelHandle, Vec)>, + active_call: Option<(ModelHandle, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, @@ -3009,6 +3009,10 @@ impl Workspace { self.database_id } + pub fn push_subscription(&mut self, subscription: Subscription) { + self.subscriptions.push(subscription) + } + fn location(&self, cx: &AppContext) -> Option { let project = self.project().read(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6bbba0bd02b7da005c9711d00591952baf2594ae..8a2691da154bd8c6f2bbbbb2bbbf37b4449436d3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -338,6 +338,27 @@ pub fn initialize_workspace( let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone()); let (project_panel, terminal_panel, assistant_panel) = futures::try_join!(project_panel, terminal_panel, assistant_panel)?; + + cx.update(|cx| { + if let Some(workspace) = workspace_handle.upgrade(cx) { + cx.update_window(project_panel.window_id(), |cx| { + workspace.update(cx, |workspace, cx| { + let project_panel_subscription = + cx.subscribe(&project_panel, move |workspace, _, event, cx| { + if let project_panel::Event::NewSearchInDirectory { dir_entry } = + event + { + search::ProjectSearchView::new_search_in_directory( + workspace, dir_entry, cx, + ) + } + }); + workspace.push_subscription(project_panel_subscription); + }); + }); + } + }); + workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); From 2d8159998d03da2ac96fee1e2469cf4ecf9a6e52 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 21 Jul 2023 16:13:00 -0400 Subject: [PATCH 132/162] Put our downloaded copy of Node in the env for every NPM action Intelephense (PHP language server) has a dependency on `protobufjs` which invokes `node` in the `postinstall` script and if the user did not have a system Node runtime installed that would fail. Have this use our downloaded installation too --- crates/node_runtime/src/node_runtime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index de9cf501ac7515e0ac8352e408c3f1e7dd22fa89..94858df880e28eeaf44a4899e75395aea47cca6a 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -62,6 +62,14 @@ impl NodeRuntime { args: &[&str], ) -> Result { let attempt = |installation_path: PathBuf| async move { + let mut env_path = installation_path.join("bin").into_os_string(); + if let Some(existing_path) = std::env::var_os("PATH") { + if !existing_path.is_empty() { + env_path.push(":"); + env_path.push(&existing_path); + } + } + let node_binary = installation_path.join("bin/node"); let npm_file = installation_path.join("bin/npm"); @@ -74,6 +82,7 @@ impl NodeRuntime { } let mut command = Command::new(node_binary); + command.env("PATH", env_path); command.arg(npm_file).arg(subcommand).args(args); if let Some(directory) = directory { From 804da68af756c71bf8e4a7eb2a8588bef1b677a1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 21 Jul 2023 23:22:22 +0300 Subject: [PATCH 133/162] When renaming in project panel, select file names without extensions --- crates/project_panel/src/project_panel.rs | 80 +++++++++++++++++------ 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8097f5ecfd5c0cb9187bbfd78f23f8dd1497fb57..896e0a75ec95da65c22b6e07e0aacaa36c6e382a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4,7 +4,7 @@ mod project_panel_settings; use context_menu::{ContextMenu, ContextMenuItem}; use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; -use editor::{Cancel, Editor}; +use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor}; use file_associations::FileAssociations; use futures::stream::StreamExt; @@ -730,13 +730,20 @@ impl ProjectPanel { is_dir: entry.is_dir(), processing_filename: None, }); - let filename = entry + let file_name = entry .path .file_name() - .map_or(String::new(), |s| s.to_string_lossy().to_string()); + .map(|s| s.to_string_lossy()) + .unwrap_or_default() + .to_string(); + let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy()); + let selection_end = + file_stem.map_or(file_name.len(), |file_stem| file_stem.len()); self.filename_editor.update(cx, |editor, cx| { - editor.set_text(filename, cx); - editor.select_all(&Default::default(), cx); + editor.set_text(file_name, cx); + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([0..selection_end]) + }) }); cx.focus(&self.filename_editor); self.update_visible_entries(None, cx); @@ -1913,7 +1920,7 @@ mod tests { .update(cx, |panel, cx| { panel .filename_editor - .update(cx, |editor, cx| editor.set_text("another-filename", cx)); + .update(cx, |editor, cx| editor.set_text("another-filename.txt", cx)); panel.confirm(&Confirm, cx).unwrap() }) .await @@ -1927,14 +1934,14 @@ mod tests { " v b", " > 3", " > 4", - " another-filename <== selected", + " another-filename.txt <== selected", " > C", " .dockerignore", " the-new-filename", ] ); - select_path(&panel, "root1/b/another-filename", cx); + select_path(&panel, "root1/b/another-filename.txt", cx); panel.update(cx, |panel, cx| panel.rename(&Rename, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), @@ -1945,7 +1952,7 @@ mod tests { " v b", " > 3", " > 4", - " [EDITOR: 'another-filename'] <== selected", + " [EDITOR: 'another-filename.txt'] <== selected", " > C", " .dockerignore", " the-new-filename", @@ -1953,9 +1960,15 @@ mod tests { ); let confirm = panel.update(cx, |panel, cx| { - panel - .filename_editor - .update(cx, |editor, cx| editor.set_text("a-different-filename", cx)); + panel.filename_editor.update(cx, |editor, cx| { + let file_name_selections = editor.selections.all::(cx); + assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}"); + let file_name_selection = &file_name_selections[0]; + assert_eq!(file_name_selection.start, 0, "Should select the file name from the start"); + assert_eq!(file_name_selection.end, "another-filename".len(), "Should not select file extension"); + + editor.set_text("a-different-filename.tar.gz", cx) + }); panel.confirm(&Confirm, cx).unwrap() }); assert_eq!( @@ -1967,7 +1980,7 @@ mod tests { " v b", " > 3", " > 4", - " [PROCESSING: 'a-different-filename'] <== selected", + " [PROCESSING: 'a-different-filename.tar.gz'] <== selected", " > C", " .dockerignore", " the-new-filename", @@ -1984,13 +1997,42 @@ mod tests { " v b", " > 3", " > 4", - " a-different-filename <== selected", + " a-different-filename.tar.gz <== selected", " > C", " .dockerignore", " the-new-filename", ] ); + panel.update(cx, |panel, cx| panel.rename(&Rename, cx)); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v root1", + " > .git", + " > a", + " v b", + " > 3", + " > 4", + " [EDITOR: 'a-different-filename.tar.gz'] <== selected", + " > C", + " .dockerignore", + " the-new-filename", + ] + ); + + panel.update(cx, |panel, cx| { + panel.filename_editor.update(cx, |editor, cx| { + let file_name_selections = editor.selections.all::(cx); + assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}"); + let file_name_selection = &file_name_selections[0]; + assert_eq!(file_name_selection.start, 0, "Should select the file name from the start"); + assert_eq!(file_name_selection.end, "a-different-filename.tar".len(), "Should not select file extension, but still may select anything up to the last dot"); + + }); + panel.cancel(&Cancel, cx) + }); + panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), @@ -2002,7 +2044,7 @@ mod tests { " > [EDITOR: ''] <== selected", " > 3", " > 4", - " a-different-filename", + " a-different-filename.tar.gz", " > C", " .dockerignore", ] @@ -2025,7 +2067,7 @@ mod tests { " > [PROCESSING: 'new-dir']", " > 3 <== selected", " > 4", - " a-different-filename", + " a-different-filename.tar.gz", " > C", " .dockerignore", ] @@ -2042,7 +2084,7 @@ mod tests { " > 3 <== selected", " > 4", " > new-dir", - " a-different-filename", + " a-different-filename.tar.gz", " > C", " .dockerignore", ] @@ -2059,7 +2101,7 @@ mod tests { " > [EDITOR: '3'] <== selected", " > 4", " > new-dir", - " a-different-filename", + " a-different-filename.tar.gz", " > C", " .dockerignore", ] @@ -2077,7 +2119,7 @@ mod tests { " > 3 <== selected", " > 4", " > new-dir", - " a-different-filename", + " a-different-filename.tar.gz", " > C", " .dockerignore", ] From 4bd415f2b6e1c77bf8a238d864eef1e0db8189cc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 Jul 2023 13:22:36 -0700 Subject: [PATCH 134/162] Retrieve git statuses in one batch when scanning dirs --- crates/fs/src/repository.rs | 71 ++++----- crates/project/src/worktree.rs | 259 +++++++++++++++------------------ script/zed-with-local-servers | 2 + 3 files changed, 153 insertions(+), 179 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 3826dae2aa73eeb3f5c263d0291e932939a19fff..611427c0a841b27e1bb3e771aa4d0edc264da5db 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -25,24 +25,13 @@ pub struct Branch { #[async_trait::async_trait] pub trait GitRepository: Send { fn reload_index(&self); - fn load_index_text(&self, relative_file_path: &Path) -> Option; - fn branch_name(&self) -> Option; - - fn statuses(&self) -> Option>; - + fn statuses(&self) -> TreeMap; fn status(&self, path: &RepoPath) -> Result>; - - fn branches(&self) -> Result> { - Ok(vec![]) - } - fn change_branch(&self, _: &str) -> Result<()> { - Ok(()) - } - fn create_branch(&self, _: &str) -> Result<()> { - Ok(()) - } + fn branches(&self) -> Result>; + fn change_branch(&self, _: &str) -> Result<()>; + fn create_branch(&self, _: &str) -> Result<()>; } impl std::fmt::Debug for dyn GitRepository { @@ -89,24 +78,22 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> Option> { - let statuses = self.statuses(None).log_err()?; - + fn statuses(&self) -> TreeMap { let mut map = TreeMap::default(); - - for status in statuses - .iter() - .filter(|status| !status.status().contains(git2::Status::IGNORED)) - { - let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - let Some(status) = read_status(status.status()) else { - continue - }; - - map.insert(path, status) + if let Some(statuses) = self.statuses(None).log_err() { + for status in statuses + .iter() + .filter(|status| !status.status().contains(git2::Status::IGNORED)) + { + let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); + let Some(status) = read_status(status.status()) else { + continue + }; + + map.insert(path, status) + } } - - Some(map) + map } fn status(&self, path: &RepoPath) -> Result> { @@ -213,19 +200,35 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> Option> { - let state = self.state.lock(); + fn statuses(&self) -> TreeMap { let mut map = TreeMap::default(); + let state = self.state.lock(); for (repo_path, status) in state.worktree_statuses.iter() { map.insert(repo_path.to_owned(), status.to_owned()); } - Some(map) + map } fn status(&self, path: &RepoPath) -> Result> { let state = self.state.lock(); Ok(state.worktree_statuses.get(path).cloned()) } + + fn branches(&self) -> Result> { + Ok(vec![]) + } + + fn change_branch(&self, name: &str) -> Result<()> { + let mut state = self.state.lock(); + state.branch_name = Some(name.to_owned()); + Ok(()) + } + + fn create_branch(&self, name: &str) -> Result<()> { + let mut state = self.state.lock(); + state.branch_name = Some(name.to_owned()); + Ok(()) + } } fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index a1730fd365f9dd13002a94011acf0c187b06b6d5..2ce1693459c45e203ebd6a1752ad6093d0785ee3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2015,37 +2015,6 @@ impl LocalSnapshot { entry } - #[must_use = "Changed paths must be used for diffing later"] - fn scan_statuses( - &mut self, - repo_ptr: &dyn GitRepository, - work_directory: &RepositoryWorkDirectory, - ) -> Vec> { - let mut changes = vec![]; - let mut edits = vec![]; - - let statuses = repo_ptr.statuses(); - - for mut entry in self - .descendent_entries(false, false, &work_directory.0) - .cloned() - { - let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else { - continue; - }; - let repo_path = RepoPath(repo_path.to_path_buf()); - let git_file_status = statuses.as_ref().and_then(|s| s.get(&repo_path).copied()); - if entry.git_status != git_file_status { - entry.git_status = git_file_status; - changes.push(entry.path.clone()); - edits.push(Edit::Insert(entry)); - } - } - - self.entries_by_path.edit(edits, &()); - changes - } - fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { let mut inodes = TreeSet::default(); for ancestor in path.ancestors().skip(1) { @@ -2189,6 +2158,30 @@ impl BackgroundScannerState { .any(|p| entry.path.starts_with(p)) } + fn enqueue_scan_dir(&self, abs_path: Arc, entry: &Entry, scan_job_tx: &Sender) { + let path = entry.path.clone(); + let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true); + let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path); + let containing_repository = self + .snapshot + .local_repo_for_path(&path) + .map(|(path, repo)| (path, repo.repo_ptr.lock().statuses())); + if !ancestor_inodes.contains(&entry.inode) { + ancestor_inodes.insert(entry.inode); + scan_job_tx + .try_send(ScanJob { + abs_path, + path, + ignore_stack, + scan_queue: scan_job_tx.clone(), + ancestor_inodes, + is_external: entry.is_external, + containing_repository, + }) + .unwrap(); + } + } + fn reuse_entry_id(&mut self, entry: &mut Entry) { if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) { entry.id = removed_entry_id; @@ -2201,7 +2194,7 @@ impl BackgroundScannerState { self.reuse_entry_id(&mut entry); let entry = self.snapshot.insert_entry(entry, fs); if entry.path.file_name() == Some(&DOT_GIT) { - self.build_repository(entry.path.clone(), fs); + self.build_git_repository(entry.path.clone(), fs); } #[cfg(test)] @@ -2215,7 +2208,6 @@ impl BackgroundScannerState { parent_path: &Arc, entries: impl IntoIterator, ignore: Option>, - fs: &dyn Fs, ) { let mut parent_entry = if let Some(parent_entry) = self .snapshot @@ -2244,16 +2236,12 @@ impl BackgroundScannerState { .insert(abs_parent_path, (ignore, false)); } - self.scanned_dirs.insert(parent_entry.id); + let parent_entry_id = parent_entry.id; + self.scanned_dirs.insert(parent_entry_id); let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; let mut entries_by_id_edits = Vec::new(); - let mut dotgit_path = None; for entry in entries { - if entry.path.file_name() == Some(&DOT_GIT) { - dotgit_path = Some(entry.path.clone()); - } - entries_by_id_edits.push(Edit::Insert(PathEntry { id: entry.id, path: entry.path.clone(), @@ -2268,9 +2256,6 @@ impl BackgroundScannerState { .edit(entries_by_path_edits, &()); self.snapshot.entries_by_id.edit(entries_by_id_edits, &()); - if let Some(dotgit_path) = dotgit_path { - self.build_repository(dotgit_path, fs); - } if let Err(ix) = self.changed_paths.binary_search(parent_path) { self.changed_paths.insert(ix, parent_path.clone()); } @@ -2346,7 +2331,7 @@ impl BackgroundScannerState { }); match repository { None => { - self.build_repository(dot_git_dir.into(), fs); + self.build_git_repository(dot_git_dir.into(), fs); } Some((entry_id, repository)) => { if repository.git_dir_scan_id == scan_id { @@ -2370,13 +2355,8 @@ impl BackgroundScannerState { .repository_entries .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - let changed_paths = self.snapshot.scan_statuses(&*repository, &work_dir); - util::extend_sorted( - &mut self.changed_paths, - changed_paths, - usize::MAX, - Ord::cmp, - ) + let statuses = repository.statuses(); + self.update_git_statuses(&work_dir, &statuses); } } } @@ -2397,7 +2377,11 @@ impl BackgroundScannerState { snapshot.repository_entries = repository_entries; } - fn build_repository(&mut self, dot_git_path: Arc, fs: &dyn Fs) -> Option<()> { + fn build_git_repository( + &mut self, + dot_git_path: Arc, + fs: &dyn Fs, + ) -> Option<(RepositoryWorkDirectory, TreeMap)> { log::info!("build git repository {:?}", dot_git_path); let work_dir_path: Arc = dot_git_path.parent().unwrap().into(); @@ -2429,9 +2413,8 @@ impl BackgroundScannerState { }, ); - let changed_paths = self - .snapshot - .scan_statuses(repo_lock.deref(), &work_directory); + let statuses = repo_lock.statuses(); + self.update_git_statuses(&work_directory, &statuses); drop(repo_lock); self.snapshot.git_repositories.insert( @@ -2443,8 +2426,36 @@ impl BackgroundScannerState { }, ); - util::extend_sorted(&mut self.changed_paths, changed_paths, usize::MAX, Ord::cmp); - Some(()) + Some((work_directory, statuses)) + } + + fn update_git_statuses( + &mut self, + work_directory: &RepositoryWorkDirectory, + statuses: &TreeMap, + ) { + let mut changes = vec![]; + let mut edits = vec![]; + + for mut entry in self + .snapshot + .descendent_entries(false, false, &work_directory.0) + .cloned() + { + let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else { + continue; + }; + let repo_path = RepoPath(repo_path.to_path_buf()); + let git_file_status = statuses.get(&repo_path).copied(); + if entry.git_status != git_file_status { + entry.git_status = git_file_status; + changes.push(entry.path.clone()); + edits.push(Edit::Insert(entry)); + } + } + + self.snapshot.entries_by_path.edit(edits, &()); + util::extend_sorted(&mut self.changed_paths, changes, usize::MAX, Ord::cmp); } } @@ -3031,16 +3042,8 @@ impl BackgroundScanner { ) { use futures::FutureExt as _; - let (root_abs_path, root_inode) = { - let snapshot = &self.state.lock().snapshot; - ( - snapshot.abs_path.clone(), - snapshot.root_entry().map(|e| e.inode), - ) - }; - // Populate ignores above the root. - let ignore_stack; + let root_abs_path = self.state.lock().snapshot.abs_path.clone(); for ancestor in root_abs_path.ancestors().skip(1) { if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await { @@ -3051,31 +3054,24 @@ impl BackgroundScanner { .insert(ancestor.into(), (ignore.into(), false)); } } + + let (scan_job_tx, scan_job_rx) = channel::unbounded(); { let mut state = self.state.lock(); state.snapshot.scan_id += 1; - ignore_stack = state - .snapshot - .ignore_stack_for_abs_path(&root_abs_path, true); - if ignore_stack.is_all() { - if let Some(mut root_entry) = state.snapshot.root_entry().cloned() { + if let Some(mut root_entry) = state.snapshot.root_entry().cloned() { + let ignore_stack = state + .snapshot + .ignore_stack_for_abs_path(&root_abs_path, true); + if ignore_stack.is_all() { root_entry.is_ignored = true; - state.insert_entry(root_entry, self.fs.as_ref()); + state.insert_entry(root_entry.clone(), self.fs.as_ref()); } + state.enqueue_scan_dir(root_abs_path, &root_entry, &scan_job_tx); } }; // Perform an initial scan of the directory. - let (scan_job_tx, scan_job_rx) = channel::unbounded(); - smol::block_on(scan_job_tx.send(ScanJob { - abs_path: root_abs_path, - path: Arc::from(Path::new("")), - ignore_stack, - ancestor_inodes: TreeSet::from_ordered_entries(root_inode), - is_external: false, - scan_queue: scan_job_tx.clone(), - })) - .unwrap(); drop(scan_job_tx); self.scan_dirs(true, scan_job_rx).await; { @@ -3263,20 +3259,7 @@ impl BackgroundScanner { if let Some(entry) = state.snapshot.entry_for_path(ancestor) { if entry.kind == EntryKind::UnloadedDir { let abs_path = root_path.join(ancestor); - let ignore_stack = - state.snapshot.ignore_stack_for_abs_path(&abs_path, true); - let ancestor_inodes = - state.snapshot.ancestor_inodes_for_path(&ancestor); - scan_job_tx - .try_send(ScanJob { - abs_path: abs_path.into(), - path: ancestor.into(), - ignore_stack, - scan_queue: scan_job_tx.clone(), - ancestor_inodes, - is_external: entry.is_external, - }) - .unwrap(); + state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx); state.paths_to_scan.insert(path.clone()); break; } @@ -3391,18 +3374,16 @@ impl BackgroundScanner { let mut ignore_stack = job.ignore_stack.clone(); let mut new_ignore = None; - let (root_abs_path, root_char_bag, next_entry_id, repository) = { + let (root_abs_path, root_char_bag, next_entry_id) = { let snapshot = &self.state.lock().snapshot; ( snapshot.abs_path().clone(), snapshot.root_char_bag, self.next_entry_id.clone(), - snapshot - .local_repo_for_path(&job.path) - .map(|(work_dir, repo)| (work_dir, repo.clone())), ) }; + let mut dotgit_path = None; let mut root_canonical_path = None; let mut new_entries: Vec = Vec::new(); let mut new_jobs: Vec> = Vec::new(); @@ -3465,6 +3446,10 @@ impl BackgroundScanner { } } } + // If we find a .git, we'll need to load the repository. + else if child_name == *DOT_GIT { + dotgit_path = Some(child_path.clone()); + } let mut child_entry = Entry::new( child_path.clone(), @@ -3525,22 +3510,17 @@ impl BackgroundScanner { }, ancestor_inodes, scan_queue: job.scan_queue.clone(), + containing_repository: job.containing_repository.clone(), })); } else { new_jobs.push(None); } } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); - if !child_entry.is_ignored { - if let Some((repo_path, repo)) = &repository { - if let Ok(path) = child_path.strip_prefix(&repo_path.0) { - child_entry.git_status = repo - .repo_ptr - .lock() - .status(&RepoPath(path.into())) - .log_err() - .flatten(); - } + + if let Some((repository_dir, statuses)) = &job.containing_repository { + if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { + child_entry.git_status = statuses.get(&RepoPath(repo_path.into())).copied(); } } } @@ -3549,27 +3529,39 @@ impl BackgroundScanner { } let mut state = self.state.lock(); - let mut new_jobs = new_jobs.into_iter(); + + // Identify any subdirectories that should not be scanned. + let mut job_ix = 0; for entry in &mut new_entries { state.reuse_entry_id(entry); - if entry.is_dir() { - let new_job = new_jobs.next().expect("missing scan job for entry"); if state.should_scan_directory(&entry) { - if let Some(new_job) = new_job { - job.scan_queue - .try_send(new_job) - .expect("channel is unbounded"); - } + job_ix += 1; } else { log::debug!("defer scanning directory {:?}", entry.path); entry.kind = EntryKind::UnloadedDir; + new_jobs.remove(job_ix); } } } - assert!(new_jobs.next().is_none()); - state.populate_dir(&job.path, new_entries, new_ignore, self.fs.as_ref()); + state.populate_dir(&job.path, new_entries, new_ignore); + + let repository = + dotgit_path.and_then(|path| state.build_git_repository(path, self.fs.as_ref())); + + for new_job in new_jobs { + if let Some(mut new_job) = new_job { + if let Some(containing_repository) = &repository { + new_job.containing_repository = Some(containing_repository.clone()); + } + + job.scan_queue + .try_send(new_job) + .expect("channel is unbounded"); + } + } + Ok(()) } @@ -3652,20 +3644,7 @@ impl BackgroundScanner { if let (Some(scan_queue_tx), true) = (&scan_queue_tx, fs_entry.is_dir()) { if state.should_scan_directory(&fs_entry) { - let mut ancestor_inodes = - state.snapshot.ancestor_inodes_for_path(&path); - if !ancestor_inodes.contains(&metadata.inode) { - ancestor_inodes.insert(metadata.inode); - smol::block_on(scan_queue_tx.send(ScanJob { - abs_path, - path: path.clone(), - ignore_stack, - ancestor_inodes, - is_external: fs_entry.is_external, - scan_queue: scan_queue_tx.clone(), - })) - .unwrap(); - } + state.enqueue_scan_dir(abs_path, &fs_entry, scan_queue_tx); } else { fs_entry.kind = EntryKind::UnloadedDir; } @@ -3822,18 +3801,7 @@ impl BackgroundScanner { if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() { let state = self.state.lock(); if state.should_scan_directory(&entry) { - job.scan_queue - .try_send(ScanJob { - abs_path: abs_path.clone(), - path: entry.path.clone(), - ignore_stack: child_ignore_stack.clone(), - scan_queue: job.scan_queue.clone(), - ancestor_inodes: state - .snapshot - .ancestor_inodes_for_path(&entry.path), - is_external: false, - }) - .unwrap(); + state.enqueue_scan_dir(abs_path.clone(), &entry, &job.scan_queue); } } @@ -4022,6 +3990,7 @@ struct ScanJob { scan_queue: Sender, ancestor_inodes: TreeSet, is_external: bool, + containing_repository: Option<(RepositoryWorkDirectory, TreeMap)>, } struct UpdateIgnoreStatusJob { diff --git a/script/zed-with-local-servers b/script/zed-with-local-servers index ed07862d30169059957c50d259fa3499ee389996..f1de38adcf66f45e6a4ac3e4bce65579d1c9cfa0 100755 --- a/script/zed-with-local-servers +++ b/script/zed-with-local-servers @@ -1 +1,3 @@ +#!/bin/bash + ZED_ADMIN_API_TOKEN=secret ZED_SERVER_URL=http://localhost:3000 cargo run $@ From c538504b9c8805331f8cf03f3b4ba3d589da7f8f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 22 Jul 2023 00:17:02 +0300 Subject: [PATCH 135/162] Do not scroll when selecting all --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 87ba250a889500d1d06eea9a4307abd21f10a78a..f1cf5a942b05da76cf7a85046f3a8925bf375523 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5298,7 +5298,7 @@ impl Editor { pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { let end = self.buffer.read(cx).read(cx).len(); - self.change_selections(Some(Autoscroll::fit()), cx, |s| { + self.change_selections(None, cx, |s| { s.select_ranges(vec![0..end]); }); } From dcaf8a9af895abd8af08fc420d1312136ec14745 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 22 Jul 2023 01:34:25 +0300 Subject: [PATCH 136/162] Open paths starting with ~ from terminal click --- crates/terminal/src/terminal.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f81af1319e504fa0cf529de418ec88f07c023d41..2785c1a871632386505b8afb98f1d78898431c2f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -76,7 +76,7 @@ lazy_static! { // Regex Copied from alacritty's ui_config.rs static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); - static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap(); + static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-~]+").unwrap(); } ///Upward flowing events, for changing the title and such diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index cdb1d40efcfbaaf8b8a710cd11bd03d2f3ec9950..bee1107d6d7994301225ce257f93280769bb871b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -425,6 +425,16 @@ fn possible_open_targets( let maybe_path = path_like.path_like; let potential_abs_paths = if maybe_path.is_absolute() { vec![maybe_path] + } else if maybe_path.starts_with("~") { + if let Some(abs_path) = maybe_path + .strip_prefix("~") + .ok() + .and_then(|maybe_path| Some(dirs::home_dir()?.join(maybe_path))) + { + vec![abs_path] + } else { + Vec::new() + } } else if let Some(workspace) = workspace.upgrade(cx) { workspace.update(cx, |workspace, cx| { workspace From 05b161118c753e72fa61710b85c91f07f8e3c626 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:05:42 -0700 Subject: [PATCH 137/162] Don't call git status when ignored files change --- crates/fs/src/repository.rs | 1 + crates/project/src/worktree.rs | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 611427c0a841b27e1bb3e771aa4d0edc264da5db..0a43c7ee263cab972763e6c798c778c09fba10f8 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -109,6 +109,7 @@ impl GitRepository for LibGitRepository { } } } + fn branches(&self) -> Result> { let local_branches = self.branches(Some(BranchType::Local))?; let valid_branches = local_branches diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2ce1693459c45e203ebd6a1752ad6093d0785ee3..880313b6b1a16d75abf5538feab8797e4904598a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2162,10 +2162,12 @@ impl BackgroundScannerState { let path = entry.path.clone(); let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true); let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path); - let containing_repository = self - .snapshot - .local_repo_for_path(&path) - .map(|(path, repo)| (path, repo.repo_ptr.lock().statuses())); + let mut containing_repository = None; + if !ignore_stack.is_all() { + if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { + containing_repository = Some((workdir_path, repo.repo_ptr.lock().statuses())); + } + } if !ancestor_inodes.contains(&entry.inode) { ancestor_inodes.insert(entry.inode); scan_job_tx @@ -3517,10 +3519,12 @@ impl BackgroundScanner { } } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); - - if let Some((repository_dir, statuses)) = &job.containing_repository { - if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { - child_entry.git_status = statuses.get(&RepoPath(repo_path.into())).copied(); + if !child_entry.is_ignored { + if let Some((repository_dir, statuses)) = &job.containing_repository { + if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { + let repo_path = RepoPath(repo_path.into()); + child_entry.git_status = statuses.get(&repo_path).copied(); + } } } } From ff0864026ed9c981ed15252da4d4541e2190bfd9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:08:31 -0700 Subject: [PATCH 138/162] Only fetch statuses for changed paths --- crates/fs/src/repository.rs | 14 +++++++++----- crates/project/src/worktree.rs | 9 ++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 0a43c7ee263cab972763e6c798c778c09fba10f8..851d495b01e5753fc9110f907254b5f82908621b 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -27,7 +27,7 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; - fn statuses(&self) -> TreeMap; + fn statuses(&self, path_prefix: &Path) -> TreeMap; fn status(&self, path: &RepoPath) -> Result>; fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; @@ -78,9 +78,11 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> TreeMap { + fn statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); - if let Some(statuses) = self.statuses(None).log_err() { + let mut options = git2::StatusOptions::new(); + options.pathspec(path_prefix); + if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() .filter(|status| !status.status().contains(git2::Status::IGNORED)) @@ -201,11 +203,13 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> TreeMap { + fn statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let state = self.state.lock(); for (repo_path, status) in state.worktree_statuses.iter() { - map.insert(repo_path.to_owned(), status.to_owned()); + if repo_path.0.starts_with(path_prefix) { + map.insert(repo_path.to_owned(), status.to_owned()); + } } map } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 880313b6b1a16d75abf5538feab8797e4904598a..85cf03246487c143075c763f357ae47d92d458aa 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2165,7 +2165,10 @@ impl BackgroundScannerState { let mut containing_repository = None; if !ignore_stack.is_all() { if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { - containing_repository = Some((workdir_path, repo.repo_ptr.lock().statuses())); + if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) { + containing_repository = + Some((workdir_path, repo.repo_ptr.lock().statuses(repo_path))); + } } } if !ancestor_inodes.contains(&entry.inode) { @@ -2357,7 +2360,7 @@ impl BackgroundScannerState { .repository_entries .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - let statuses = repository.statuses(); + let statuses = repository.statuses(Path::new("")); self.update_git_statuses(&work_dir, &statuses); } } @@ -2415,7 +2418,7 @@ impl BackgroundScannerState { }, ); - let statuses = repo_lock.statuses(); + let statuses = repo_lock.statuses(Path::new("")); self.update_git_statuses(&work_directory, &statuses); drop(repo_lock); From 51d311affd108118f03aef42f2ad4937bfb334fb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:51:00 -0700 Subject: [PATCH 139/162] Compute unstaged git status separately, to take advantage of our cached file mtimes --- crates/fs/src/repository.rs | 34 ++++++++++++-- crates/project/src/worktree.rs | 83 +++++++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 851d495b01e5753fc9110f907254b5f82908621b..54f80c26a2e01936dc19e4ac09af51e5e3d0e152 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -10,6 +10,7 @@ use std::{ os::unix::prelude::OsStrExt, path::{Component, Path, PathBuf}, sync::Arc, + time::SystemTime, }; use sum_tree::{MapSeekTarget, TreeMap}; use util::ResultExt; @@ -27,7 +28,8 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; - fn statuses(&self, path_prefix: &Path) -> TreeMap; + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap; + fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option; fn status(&self, path: &RepoPath) -> Result>; fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; @@ -78,10 +80,11 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self, path_prefix: &Path) -> TreeMap { + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); + options.disable_pathspec_match(true); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() @@ -98,6 +101,27 @@ impl GitRepository for LibGitRepository { map } + fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option { + let index = self.index().log_err()?; + if let Some(entry) = index.get_path(&path, 0) { + let mtime = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err()?; + if entry.mtime.seconds() == mtime.as_secs() as i32 + && entry.mtime.nanoseconds() == mtime.subsec_nanos() + { + None + } else { + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status + } + } else { + Some(GitFileStatus::Added) + } + } + fn status(&self, path: &RepoPath) -> Result> { let status = self.status_file(path); match status { @@ -203,7 +227,7 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self, path_prefix: &Path) -> TreeMap { + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let state = self.state.lock(); for (repo_path, status) in state.worktree_statuses.iter() { @@ -214,6 +238,10 @@ impl GitRepository for FakeGitRepository { map } + fn unstaged_status(&self, _path: &RepoPath, _mtime: SystemTime) -> Option { + None + } + fn status(&self, path: &RepoPath) -> Result> { let state = self.state.lock(); Ok(state.worktree_statuses.get(path).cloned()) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 85cf03246487c143075c763f357ae47d92d458aa..c4b6ed6ca09d0189a86d332a060e2e8cd64333df 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2166,8 +2166,11 @@ impl BackgroundScannerState { if !ignore_stack.is_all() { if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) { - containing_repository = - Some((workdir_path, repo.repo_ptr.lock().statuses(repo_path))); + containing_repository = Some(( + workdir_path, + repo.repo_ptr.clone(), + repo.repo_ptr.lock().staged_statuses(repo_path), + )); } } } @@ -2360,8 +2363,7 @@ impl BackgroundScannerState { .repository_entries .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - let statuses = repository.statuses(Path::new("")); - self.update_git_statuses(&work_dir, &statuses); + self.update_git_statuses(&work_dir, &*repository); } } } @@ -2386,7 +2388,11 @@ impl BackgroundScannerState { &mut self, dot_git_path: Arc, fs: &dyn Fs, - ) -> Option<(RepositoryWorkDirectory, TreeMap)> { + ) -> Option<( + RepositoryWorkDirectory, + Arc>, + TreeMap, + )> { log::info!("build git repository {:?}", dot_git_path); let work_dir_path: Arc = dot_git_path.parent().unwrap().into(); @@ -2418,27 +2424,28 @@ impl BackgroundScannerState { }, ); - let statuses = repo_lock.statuses(Path::new("")); - self.update_git_statuses(&work_directory, &statuses); + let staged_statuses = self.update_git_statuses(&work_directory, &*repo_lock); drop(repo_lock); self.snapshot.git_repositories.insert( work_dir_id, LocalRepositoryEntry { git_dir_scan_id: 0, - repo_ptr: repository, + repo_ptr: repository.clone(), git_dir_path: dot_git_path.clone(), }, ); - Some((work_directory, statuses)) + Some((work_directory, repository, staged_statuses)) } fn update_git_statuses( &mut self, work_directory: &RepositoryWorkDirectory, - statuses: &TreeMap, - ) { + repo: &dyn GitRepository, + ) -> TreeMap { + let staged_statuses = repo.staged_statuses(Path::new("")); + let mut changes = vec![]; let mut edits = vec![]; @@ -2451,7 +2458,10 @@ impl BackgroundScannerState { continue; }; let repo_path = RepoPath(repo_path.to_path_buf()); - let git_file_status = statuses.get(&repo_path).copied(); + let git_file_status = combine_git_statuses( + staged_statuses.get(&repo_path).copied(), + repo.unstaged_status(&repo_path, entry.mtime), + ); if entry.git_status != git_file_status { entry.git_status = git_file_status; changes.push(entry.path.clone()); @@ -2461,6 +2471,7 @@ impl BackgroundScannerState { self.snapshot.entries_by_path.edit(edits, &()); util::extend_sorted(&mut self.changed_paths, changes, usize::MAX, Ord::cmp); + staged_statuses } } @@ -3523,10 +3534,17 @@ impl BackgroundScanner { } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); if !child_entry.is_ignored { - if let Some((repository_dir, statuses)) = &job.containing_repository { + if let Some((repository_dir, repository, staged_statuses)) = + &job.containing_repository + { if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { let repo_path = RepoPath(repo_path.into()); - child_entry.git_status = statuses.get(&repo_path).copied(); + child_entry.git_status = combine_git_statuses( + staged_statuses.get(&repo_path).copied(), + repository + .lock() + .unstaged_status(&repo_path, child_entry.mtime), + ); } } } @@ -3637,13 +3655,11 @@ impl BackgroundScanner { if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) { - if let Ok(path) = path.strip_prefix(work_dir.0) { - fs_entry.git_status = repo - .repo_ptr - .lock() - .status(&RepoPath(path.into())) - .log_err() - .flatten() + if let Ok(repo_path) = path.strip_prefix(work_dir.0) { + let repo_path = RepoPath(repo_path.into()); + let repo = repo.repo_ptr.lock(); + fs_entry.git_status = + repo.status(&repo_path).log_err().flatten(); } } } @@ -3997,7 +4013,11 @@ struct ScanJob { scan_queue: Sender, ancestor_inodes: TreeSet, is_external: bool, - containing_repository: Option<(RepositoryWorkDirectory, TreeMap)>, + containing_repository: Option<( + RepositoryWorkDirectory, + Arc>, + TreeMap, + )>, } struct UpdateIgnoreStatusJob { @@ -4324,3 +4344,22 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { } } } + +fn combine_git_statuses( + staged: Option, + unstaged: Option, +) -> Option { + if let Some(staged) = staged { + if let Some(unstaged) = unstaged { + if unstaged != staged { + Some(GitFileStatus::Modified) + } else { + Some(staged) + } + } else { + Some(staged) + } + } else { + unstaged + } +} From 6c09782aa2b132216a55d8028721eebe17786151 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 11:53:26 -0700 Subject: [PATCH 140/162] Optimize full file status via passing in known file mtime --- crates/fs/src/repository.rs | 65 +++++++++++++++++++++++++--------- crates/project/src/worktree.rs | 3 +- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 54f80c26a2e01936dc19e4ac09af51e5e3d0e152..e6a41839b62b1bf568ee12850f4704af81605ec0 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,6 +1,6 @@ use anyhow::Result; use collections::HashMap; -use git2::{BranchType, ErrorCode}; +use git2::{BranchType, StatusShow}; use parking_lot::Mutex; use rpc::proto; use serde_derive::{Deserialize, Serialize}; @@ -28,9 +28,25 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; + + /// Get the statuses of all of the files in the index that start with the given + /// path and have changes with resepect to the HEAD commit. This is fast because + /// the index stores hashes of trees, so that unchanged directories can be skipped. fn staged_statuses(&self, path_prefix: &Path) -> TreeMap; + + /// Get the status of a given file in the working directory with respect to + /// the index. In the common case, when there are no changes, this only requires + /// an index lookup. The index stores the mtime of each file when it was added, + /// so there's no work to do if the mtime matches. fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option; - fn status(&self, path: &RepoPath) -> Result>; + + /// Get the status of a given file in the working directory with respect to + /// the HEAD commit. In the common case, when there are no changes, this only + /// requires an index lookup and blob comparison between the index and the HEAD + /// commit. The index stores the mtime of each file when it was added, so there's + /// no need to consider the working directory file if the mtime matches. + fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option; + fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; fn create_branch(&self, _: &str) -> Result<()>; @@ -42,7 +58,6 @@ impl std::fmt::Debug for dyn GitRepository { } } -#[async_trait::async_trait] impl GitRepository for LibGitRepository { fn reload_index(&self) { if let Ok(mut index) = self.index() { @@ -122,18 +137,21 @@ impl GitRepository for LibGitRepository { } } - fn status(&self, path: &RepoPath) -> Result> { - let status = self.status_file(path); - match status { - Ok(status) => Ok(read_status(status)), - Err(e) => { - if e.code() == ErrorCode::NotFound { - Ok(None) - } else { - Err(e.into()) - } - } + fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option { + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + + // If the file has not changed since it was added to the index, then + // there's no need to examine the working directory file: just compare + // the blob in the index to the one in the HEAD commit. + if matches_index(self, path, mtime) { + options.show(StatusShow::Index); } + + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status } fn branches(&self) -> Result> { @@ -178,6 +196,21 @@ impl GitRepository for LibGitRepository { } } +fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool { + if let Some(index) = repo.index().log_err() { + if let Some(entry) = index.get_path(&path, 0) { + if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() { + if entry.mtime.seconds() == mtime.as_secs() as i32 + && entry.mtime.nanoseconds() == mtime.subsec_nanos() + { + return true; + } + } + } + } + false +} + fn read_status(status: git2::Status) -> Option { if status.contains(git2::Status::CONFLICTED) { Some(GitFileStatus::Conflict) @@ -242,9 +275,9 @@ impl GitRepository for FakeGitRepository { None } - fn status(&self, path: &RepoPath) -> Result> { + fn status(&self, path: &RepoPath, _mtime: SystemTime) -> Option { let state = self.state.lock(); - Ok(state.worktree_statuses.get(path).cloned()) + state.worktree_statuses.get(path).cloned() } fn branches(&self) -> Result> { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c4b6ed6ca09d0189a86d332a060e2e8cd64333df..b0795818b8d62557b688313fcfe8d017c2b55795 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3658,8 +3658,7 @@ impl BackgroundScanner { if let Ok(repo_path) = path.strip_prefix(work_dir.0) { let repo_path = RepoPath(repo_path.into()); let repo = repo.repo_ptr.lock(); - fs_entry.git_status = - repo.status(&repo_path).log_err().flatten(); + fs_entry.git_status = repo.status(&repo_path, fs_entry.mtime); } } } From f05095a6dd64d16787bf4152f02436e82577d8f9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 22 Jul 2023 02:24:46 +0300 Subject: [PATCH 141/162] Focus project panel on directory select --- crates/project/src/project.rs | 1 + crates/project_panel/src/project_panel.rs | 9 +- crates/terminal/src/terminal.rs | 4 +- crates/terminal_view/src/terminal_view.rs | 71 +++++++++------ crates/workspace/src/workspace.rs | 102 ++++++++++++++-------- crates/zed/src/zed.rs | 92 ++++++++++++------- 6 files changed, 184 insertions(+), 95 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3255df812a80736ffed7b9ebdc32239a3414167..6b905a1faa876bafd793b5e6fc02be4aacc437c2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -259,6 +259,7 @@ pub enum Event { LanguageServerLog(LanguageServerId, String), Notification(String), ActiveEntryChanged(Option), + ActivateProjectPanel, WorktreeAdded, WorktreeRemoved(WorktreeId), WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 87b0d21a9f5521ade4a2516ce2e3f95ad4496cb0..2a6da7db28950972dd09afe7a3cb3b646b89e7c3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -174,6 +174,7 @@ pub enum Event { NewSearchInDirectory { dir_entry: Entry, }, + ActivatePanel, } #[derive(Serialize, Deserialize)] @@ -200,6 +201,9 @@ impl ProjectPanel { cx.notify(); } } + project::Event::ActivateProjectPanel => { + cx.emit(Event::ActivatePanel); + } project::Event::WorktreeRemoved(id) => { this.expanded_dir_ids.remove(id); this.update_visible_entries(None, cx); @@ -1014,7 +1018,10 @@ impl ProjectPanel { None } - fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> { + pub fn selected_entry<'a>( + &self, + cx: &'a AppContext, + ) -> Option<(&'a Worktree, &'a project::Entry)> { let (worktree, entry) = self.selected_entry_handle(cx)?; Some((worktree.read(cx), entry)) } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 2785c1a871632386505b8afb98f1d78898431c2f..e3109102d10bdb23de8594c51322df9958b3d83b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -73,7 +73,9 @@ const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; lazy_static! { - // Regex Copied from alacritty's ui_config.rs + // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly: + // * avoid Rust-specific escaping. + // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings. static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(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_view.rs b/crates/terminal_view/src/terminal_view.rs index bee1107d6d7994301225ce257f93280769bb871b..e108a05ccc00bf3a47127c0ced234f0cc4dd332b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -187,37 +187,56 @@ impl TerminalView { } 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 is_dir = path.path_like.is_dir(); let task_workspace = workspace.clone(); cx.spawn(|_, mut cx| async move { - let opened_item = task_workspace + let opened_items = task_workspace .update(&mut cx, |workspace, cx| { - workspace.open_abs_path(path.path_like, visible, cx) + workspace.open_paths(vec![path.path_like], is_dir, 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(); + .await; + anyhow::ensure!( + opened_items.len() == 1, + "For a single path open, expected single opened item" + ); + let opened_item = opened_items + .into_iter() + .next() + .unwrap() + .transpose() + .context("path open")?; + if is_dir { + task_workspace.update(&mut cx, |workspace, cx| { + workspace.project().update(cx, |_, cx| { + cx.emit(project::Event::ActivateProjectPanel); + }) + })?; + } else { + if let Some(row) = path.row { + let col = path.column.unwrap_or(0); + if let Some(active_editor) = + opened_item.and_then(|item| 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(()) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5c1a75e97abf33ba1247f078f2a0d86e251988cd..1e9e431f9d6556953ddd298815ae1827afb07936 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -898,6 +898,18 @@ impl Workspace { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) where T::Event: std::fmt::Debug, + { + self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) + } + + pub fn add_panel_with_extra_event_handler( + &mut self, + panel: ViewHandle, + cx: &mut ViewContext, + handler: F, + ) where + T::Event: std::fmt::Debug, + F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, { let dock = match panel.position(cx) { DockPosition::Left => &self.left_dock, @@ -965,6 +977,8 @@ impl Workspace { } this.update_active_view_for_followers(cx); cx.notify(); + } else { + handler(this, &panel, event, cx) } } })); @@ -1417,45 +1431,65 @@ impl Workspace { // Sort the paths to ensure we add worktrees for parents before their children. abs_paths.sort_unstable(); cx.spawn(|this, mut cx| async move { - let mut project_paths = Vec::new(); - for path in &abs_paths { - if let Some(project_path) = this + let mut tasks = Vec::with_capacity(abs_paths.len()); + for abs_path in &abs_paths { + let project_path = match this .update(&mut cx, |this, cx| { - Workspace::project_path_for_path(this.project.clone(), path, visible, cx) + Workspace::project_path_for_path( + this.project.clone(), + abs_path, + visible, + cx, + ) }) .log_err() { - project_paths.push(project_path.await.log_err()); - } else { - project_paths.push(None); - } - } + Some(project_path) => project_path.await.log_err(), + None => None, + }; - let tasks = abs_paths - .iter() - .cloned() - .zip(project_paths.into_iter()) - .map(|(abs_path, project_path)| { - let this = this.clone(); - cx.spawn(|mut cx| { - let fs = fs.clone(); - async move { - let (_worktree, project_path) = project_path?; - if fs.is_file(&abs_path).await { - Some( - this.update(&mut cx, |this, cx| { - this.open_path(project_path, None, true, cx) + let this = this.clone(); + let task = cx.spawn(|mut cx| { + let fs = fs.clone(); + let abs_path = abs_path.clone(); + async move { + let (worktree, project_path) = project_path?; + if fs.is_file(&abs_path).await { + Some( + this.update(&mut cx, |this, cx| { + this.open_path(project_path, None, true, cx) + }) + .log_err()? + .await, + ) + } else { + this.update(&mut cx, |workspace, cx| { + let worktree = worktree.read(cx); + let worktree_abs_path = worktree.abs_path(); + let entry_id = if abs_path == worktree_abs_path.as_ref() { + worktree.root_entry() + } else { + abs_path + .strip_prefix(worktree_abs_path.as_ref()) + .ok() + .and_then(|relative_path| { + worktree.entry_for_path(relative_path) + }) + } + .map(|entry| entry.id); + if let Some(entry_id) = entry_id { + workspace.project().update(cx, |_, cx| { + cx.emit(project::Event::ActiveEntryChanged(Some(entry_id))); }) - .log_err()? - .await, - ) - } else { - None - } + } + }) + .log_err()?; + None } - }) - }) - .collect::>(); + } + }); + tasks.push(task); + } futures::future::join_all(tasks).await }) @@ -3009,10 +3043,6 @@ impl Workspace { self.database_id } - pub fn push_subscription(&mut self, subscription: Subscription) { - self.subscriptions.push(subscription) - } - fn location(&self, cx: &AppContext) -> Option { let project = self.project().read(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8a2691da154bd8c6f2bbbbb2bbbf37b4449436d3..db7c57a89c9588b374f63c40b7c592899ec8ef7b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -339,29 +339,21 @@ pub fn initialize_workspace( let (project_panel, terminal_panel, assistant_panel) = futures::try_join!(project_panel, terminal_panel, assistant_panel)?; - cx.update(|cx| { - if let Some(workspace) = workspace_handle.upgrade(cx) { - cx.update_window(project_panel.window_id(), |cx| { - workspace.update(cx, |workspace, cx| { - let project_panel_subscription = - cx.subscribe(&project_panel, move |workspace, _, event, cx| { - if let project_panel::Event::NewSearchInDirectory { dir_entry } = - event - { - search::ProjectSearchView::new_search_in_directory( - workspace, dir_entry, cx, - ) - } - }); - workspace.push_subscription(project_panel_subscription); - }); - }); - } - }); - workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); - workspace.add_panel(project_panel, cx); + workspace.add_panel_with_extra_event_handler( + project_panel, + cx, + |workspace, _, event, cx| match event { + project_panel::Event::NewSearchInDirectory { dir_entry } => { + search::ProjectSearchView::new_search_in_directory(workspace, dir_entry, cx) + } + project_panel::Event::ActivatePanel => { + workspace.focus_panel::(cx); + } + _ => {} + }, + ); workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); @@ -1106,8 +1098,46 @@ mod tests { ) .await; - let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx)) + .await + .unwrap(); + assert_eq!(cx.window_ids().len(), 1); + let workspace = cx + .read_window(cx.window_ids()[0], |cx| cx.root_view().clone()) + .unwrap() + .downcast::() + .unwrap(); + + #[track_caller] + fn assert_project_panel_selection( + workspace: &Workspace, + expected_worktree_path: &Path, + expected_entry_path: &Path, + cx: &AppContext, + ) { + let project_panel = [ + workspace.left_dock().read(cx).panel::(), + workspace.right_dock().read(cx).panel::(), + workspace.bottom_dock().read(cx).panel::(), + ] + .into_iter() + .find_map(std::convert::identity) + .expect("found no project panels") + .read(cx); + let (selected_worktree, selected_entry) = project_panel + .selected_entry(cx) + .expect("project panel should have a selected entry"); + assert_eq!( + selected_worktree.abs_path().as_ref(), + expected_worktree_path, + "Unexpected project panel selected worktree path" + ); + assert_eq!( + selected_entry.path.as_ref(), + expected_entry_path, + "Unexpected project panel selected entry path" + ); + } // Open a file within an existing worktree. workspace @@ -1116,9 +1146,10 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1139,8 +1170,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1153,7 +1185,6 @@ mod tests { ); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1174,8 +1205,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1188,7 +1220,6 @@ mod tests { ); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1209,8 +1240,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1223,7 +1255,6 @@ mod tests { ); let visible_worktree_roots = workspace - .read(cx) .visible_worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1237,7 +1268,6 @@ mod tests { assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() From b338ffe8d818411d0c4a824d7e3f5f92711794da Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 17:47:36 -0700 Subject: [PATCH 142/162] Rely on git status for any paths not matching the git index --- crates/fs/src/repository.rs | 38 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index e6a41839b62b1bf568ee12850f4704af81605ec0..47e1bc1aab21405444171afb49e58ab6115a91ae 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -97,9 +97,11 @@ impl GitRepository for LibGitRepository { fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); + let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); options.disable_pathspec_match(true); + if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() @@ -117,30 +119,32 @@ impl GitRepository for LibGitRepository { } fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option { - let index = self.index().log_err()?; - if let Some(entry) = index.get_path(&path, 0) { - let mtime = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err()?; - if entry.mtime.seconds() == mtime.as_secs() as i32 - && entry.mtime.nanoseconds() == mtime.subsec_nanos() - { - None - } else { - let mut options = git2::StatusOptions::new(); - options.pathspec(&path.0); - options.disable_pathspec_match(true); - let statuses = self.statuses(Some(&mut options)).log_err()?; - let status = statuses.get(0).and_then(|s| read_status(s.status())); - status - } - } else { - Some(GitFileStatus::Added) + // If the file has not changed since it was added to the index, then + // there can't be any changes. + if matches_index(self, path, mtime) { + return None; } + + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + options.include_untracked(true); + options.recurse_untracked_dirs(true); + options.include_unmodified(true); + options.show(StatusShow::Workdir); + + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status } fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option { let mut options = git2::StatusOptions::new(); options.pathspec(&path.0); options.disable_pathspec_match(true); + options.include_untracked(true); + options.recurse_untracked_dirs(true); + options.include_unmodified(true); // If the file has not changed since it was added to the index, then // there's no need to examine the working directory file: just compare From a3a9d024ba3323ae4b67e2dea4ecfb88ce6a489b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 17:53:58 -0700 Subject: [PATCH 143/162] Fix filtering of staged statuses --- crates/fs/src/repository.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 47e1bc1aab21405444171afb49e58ab6115a91ae..f4678d933f2109d607bd1ab79dd08271817d1655 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -101,18 +101,17 @@ impl GitRepository for LibGitRepository { let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); options.disable_pathspec_match(true); + options.show(StatusShow::Index); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { - for status in statuses - .iter() - .filter(|status| !status.status().contains(git2::Status::IGNORED)) - { + for status in statuses.iter() { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - let Some(status) = read_status(status.status()) else { - continue - }; - - map.insert(path, status) + let status = status.status(); + if !status.contains(git2::Status::IGNORED) { + if let Some(status) = read_status(status) { + map.insert(path, status) + } + } } } map From 8fff0b0ff8ade2b43f2608824a3cc7b3878afae3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 23 Jul 2023 21:34:12 -0700 Subject: [PATCH 144/162] Fix pathspec in staged_statuses Enable non-literal matching so that directory paths match all files contained within them. --- crates/fs/src/repository.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index f4678d933f2109d607bd1ab79dd08271817d1655..2b2aebe67959ed977c96ed60b36cf4c7ce3d23d2 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -100,7 +100,6 @@ impl GitRepository for LibGitRepository { let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); - options.disable_pathspec_match(true); options.show(StatusShow::Index); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { From a4914fcf3b0e80505ead8ef3734d572be027aab1 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Sun, 23 Jul 2023 16:32:14 -0700 Subject: [PATCH 145/162] Add tree-sitter-elm --- Cargo.lock | 11 ++++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/elm/config.toml | 11 ++++ crates/zed/src/languages/elm/highlights.scm | 71 +++++++++++++++++++++ crates/zed/src/languages/elm/injections.scm | 2 + 7 files changed, 98 insertions(+) create mode 100644 crates/zed/src/languages/elm/config.toml create mode 100644 crates/zed/src/languages/elm/highlights.scm create mode 100644 crates/zed/src/languages/elm/injections.scm diff --git a/Cargo.lock b/Cargo.lock index f0c8917aa28d300e1aea521f7c936c3ce918a984..eb6c51e47f69c4001e65da6fff38635e9f173ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7989,6 +7989,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-elm" +version = "5.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec83a2e1cfc69d03c8e73636e95662d6c6728539538d341b21251a77039fb94e" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-embedded-template" version = "0.20.0" @@ -9543,6 +9553,7 @@ dependencies = [ "tree-sitter-cpp", "tree-sitter-css", "tree-sitter-elixir", + "tree-sitter-elm", "tree-sitter-embedded-template", "tree-sitter-go", "tree-sitter-heex", diff --git a/Cargo.toml b/Cargo.toml index fa824115cb82b02c2428f2f1bd9889a0b93a5757..03614e61e4887c10fcd0ef6351dda5b34902d06a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } +tree-sitter-elm = "5.6.4" tree-sitter-embedded-template = "0.20.0" tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c5bf313701e76f1ea7084f7299f7596b7b0306df..df7f4bcce96d6c83b70d3b1044ca2a8c61c5bebb 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -109,6 +109,7 @@ tree-sitter-c.workspace = true tree-sitter-cpp.workspace = true tree-sitter-css.workspace = true tree-sitter-elixir.workspace = true +tree-sitter-elm.workspace = true tree-sitter-embedded-template.workspace = true tree-sitter-go.workspace = true tree-sitter-heex.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 365e8a30232e5dc9193b1077b16186f3c44e9d19..c7b5c59b876933b5ef553897c04ae16b85c7d53a 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -152,6 +152,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { tree_sitter_php::language(), vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], ); + language("elm", tree_sitter_elm::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/elm/config.toml b/crates/zed/src/languages/elm/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..5051427a93df4be56d86ac0b3a8f26a88f2cdde6 --- /dev/null +++ b/crates/zed/src/languages/elm/config.toml @@ -0,0 +1,11 @@ +name = "Elm" +path_suffixes = ["elm"] +line_comment = "-- " +block_comment = ["{- ", " -}"] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, +] diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..f6be193f838db77549a879c6563bc00ac8f064f2 --- /dev/null +++ b/crates/zed/src/languages/elm/highlights.scm @@ -0,0 +1,71 @@ +[ + "if" + "then" + "else" + "let" + "in" + (case) + (of) + (backslash) + (as) + (port) + (exposing) + (alias) + (import) + (module) + (type) + ] @keyword + +[ + (arrow) + (eq) + (operator_identifier) + (colon) +] @operator + +(type_annotation(lower_case_identifier) @function) +(port_annotation(lower_case_identifier) @function) +(function_declaration_left(lower_case_identifier) @function.definition) +(function_call_expr target: (value_expr) @identifier) + +(exposed_value(lower_case_identifier) @function) +(exposed_type(upper_case_identifier) @type) + +(field_access_expr(value_expr(value_qid)) @identifier) +(lower_pattern) @variable +(record_base_identifier) @identifier + +[ + "(" + ")" +] @punctuation.bracket + +[ + "|" + "," +] @punctuation.delimiter + +(number_constant_expr) @constant + +(type_declaration(upper_case_identifier) @type) +(type_ref) @type +(type_alias_declaration name: (upper_case_identifier) @type) + +(union_variant(upper_case_identifier) @variant) +(union_pattern) @variant +(value_expr(upper_case_qid(upper_case_identifier)) @type) + +[ + (line_comment) + (block_comment) +] @comment + +(string_escape) @string.escape + +[ + (open_quote) + (close_quote) + (regular_string_part) + (open_char) + (close_char) +] @string diff --git a/crates/zed/src/languages/elm/injections.scm b/crates/zed/src/languages/elm/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..0567320675a89c6649a191fdef950c6670d65707 --- /dev/null +++ b/crates/zed/src/languages/elm/injections.scm @@ -0,0 +1,2 @@ +((glsl_content) @content + (#set! "language" "glsl")) From dd504f5965f62d4d3aa4d079b039e68461ae1487 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Sun, 23 Jul 2023 16:43:44 -0700 Subject: [PATCH 146/162] Add tree-sitter-glsl --- Cargo.lock | 10 ++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/glsl/config.toml | 9 ++ crates/zed/src/languages/glsl/highlights.scm | 118 +++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 crates/zed/src/languages/glsl/config.toml create mode 100644 crates/zed/src/languages/glsl/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index eb6c51e47f69c4001e65da6fff38635e9f173ac4..3aa77e2ce141809e85ec3a8388dd3d34ef8aa3bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8009,6 +8009,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-glsl" +version = "0.1.4" +source = "git+https://github.com/theHamsta/tree-sitter-glsl?rev=2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3#2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-go" version = "0.19.1" @@ -9555,6 +9564,7 @@ dependencies = [ "tree-sitter-elixir", "tree-sitter-elm", "tree-sitter-embedded-template", + "tree-sitter-glsl", "tree-sitter-go", "tree-sitter-heex", "tree-sitter-html", diff --git a/Cargo.toml b/Cargo.toml index 03614e61e4887c10fcd0ef6351dda5b34902d06a..10f2160f456257f424e8afed6744db9b3ac2ecd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } tree-sitter-elm = "5.6.4" tree-sitter-embedded-template = "0.20.0" +tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" } tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index df7f4bcce96d6c83b70d3b1044ca2a8c61c5bebb..9bd2de0accc99f9c023779a3a99657cd31df71c1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -111,6 +111,7 @@ tree-sitter-css.workspace = true tree-sitter-elixir.workspace = true tree-sitter-elm.workspace = true tree-sitter-embedded-template.workspace = true +tree-sitter-glsl.workspace = true tree-sitter-go.workspace = true tree-sitter-heex.workspace = true tree-sitter-json.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index c7b5c59b876933b5ef553897c04ae16b85c7d53a..09f5162c12f81286174859c8217316dc04faf3e9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -153,6 +153,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], ); language("elm", tree_sitter_elm::language(), vec![]); + language("glsl", tree_sitter_glsl::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/glsl/config.toml b/crates/zed/src/languages/glsl/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..4081a6381f6409260c558937c9321c8fdbad8f94 --- /dev/null +++ b/crates/zed/src/languages/glsl/config.toml @@ -0,0 +1,9 @@ +name = "GLSL" +path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"] +line_comment = "// " +block_comment = ["/* ", " */"] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/glsl/highlights.scm b/crates/zed/src/languages/glsl/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..e4503c6fbba298afb1f2eddb82ce960af74b03c0 --- /dev/null +++ b/crates/zed/src/languages/glsl/highlights.scm @@ -0,0 +1,118 @@ +"break" @keyword +"case" @keyword +"const" @keyword +"continue" @keyword +"default" @keyword +"do" @keyword +"else" @keyword +"enum" @keyword +"extern" @keyword +"for" @keyword +"if" @keyword +"inline" @keyword +"return" @keyword +"sizeof" @keyword +"static" @keyword +"struct" @keyword +"switch" @keyword +"typedef" @keyword +"union" @keyword +"volatile" @keyword +"while" @keyword + +"#define" @keyword +"#elif" @keyword +"#else" @keyword +"#endif" @keyword +"#if" @keyword +"#ifdef" @keyword +"#ifndef" @keyword +"#include" @keyword +(preproc_directive) @keyword + +"--" @operator +"-" @operator +"-=" @operator +"->" @operator +"=" @operator +"!=" @operator +"*" @operator +"&" @operator +"&&" @operator +"+" @operator +"++" @operator +"+=" @operator +"<" @operator +"==" @operator +">" @operator +"||" @operator + +"." @delimiter +";" @delimiter + +(string_literal) @string +(system_lib_string) @string + +(null) @constant +(number_literal) @number +(char_literal) @number + +(call_expression + function: (identifier) @function) +(call_expression + function: (field_expression + field: (field_identifier) @function)) +(function_declarator + declarator: (identifier) @function) +(preproc_function_def + name: (identifier) @function.special) + +(field_identifier) @property +(statement_identifier) @label +(type_identifier) @type +(primitive_type) @type +(sized_type_specifier) @type + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]*$")) + +(identifier) @variable + +(comment) @comment +; inherits: c + +[ + "in" + "out" + "inout" + "uniform" + "shared" + "layout" + "attribute" + "varying" + "buffer" + "coherent" + "readonly" + "writeonly" + "precision" + "highp" + "mediump" + "lowp" + "centroid" + "sample" + "patch" + "smooth" + "flat" + "noperspective" + "invariant" + "precise" +] @type.qualifier + +"subroutine" @keyword.function + +(extension_storage_class) @storageclass + +( + (identifier) @variable.builtin + (#match? @variable.builtin "^gl_") +) From 6ad0852a70402a482841d7cc3d090553a719c19b Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 00:58:59 -0700 Subject: [PATCH 147/162] Add outline.scm for Elm --- crates/zed/src/languages/elm/outline.scm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 crates/zed/src/languages/elm/outline.scm diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..c220676290ce8242c0890716493c4c6369a555e0 --- /dev/null +++ b/crates/zed/src/languages/elm/outline.scm @@ -0,0 +1,22 @@ +(type_declaration + (type) @context + (upper_case_identifier) @name) @item + +(type_alias_declaration + (type) @context + (alias) @context + name: (upper_case_identifier) @name) @item + +(type_alias_declaration + typeExpression: + (type_expression + part: (record_type + (field_type + name: (lower_case_identifier) @name)))) @item + +(union_variant + name: (upper_case_identifier) @name) @item + +(value_declaration + functionDeclarationLeft: + (function_declaration_left(lower_case_identifier) @name)) @item From 7dccb487de2238c67c1b37c41e520e10ba7aeb86 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 Jul 2023 15:42:10 +0300 Subject: [PATCH 148/162] Fixes a crash when SelectAllMatches action was called on no matches --- crates/search/src/buffer_search.rs | 81 ++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7fade13a509dabd633b7da26601f90b770df3b6f..54293050989c146e5bb39363c12281c46eea1a53 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -568,7 +568,7 @@ impl BufferSearchBar { } fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext) { - if !self.dismissed { + if !self.dismissed && self.active_match_index.is_some() { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self .searchable_items_with_matches @@ -1175,9 +1175,16 @@ mod tests { .await .unwrap(); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.activate_current_match(cx); }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Initially, the editor should not be focused" + ); + }); let initial_selections = editor.update(cx, |editor, cx| { let initial_selections = editor.selections.display_ranges(cx); assert_eq!( @@ -1191,7 +1198,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1199,8 +1215,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), @@ -1210,6 +1224,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectNextMatch" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1221,8 +1243,6 @@ mod tests { all_selections, initial_selections, "Next match should be different from the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1231,7 +1251,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1239,8 +1268,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1250,6 +1277,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectPrevMatch" + ); + }); + let last_match_selections = search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1261,13 +1296,41 @@ mod tests { all_selections, initial_selections, "Previous match should be the same as the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), "Match index should be updated to the previous one" ); + all_selections + }); + + search_bar + .update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); + search_bar.search("abas_nonexistent_match", None, cx) + }) + .await + .unwrap(); + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); + }); + search_bar.update(cx, |search_bar, cx| { + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections, last_match_selections, + "Should not select anything new if there are no matches" + ); + assert!( + search_bar.active_match_index.is_none(), + "For no matches, there should be no active match index" + ); }); } } From ea74734b0a8ff445609732fe334a15284a26acee Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 08:51:44 -0700 Subject: [PATCH 149/162] Touch up elm tree sitter integration --- crates/zed/src/languages/elm/highlights.scm | 1 + crates/zed/src/languages/elm/outline.scm | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index f6be193f838db77549a879c6563bc00ac8f064f2..e20873af1bd35302d84a34cf65a2e5540aac03a5 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -53,6 +53,7 @@ (union_variant(upper_case_identifier) @variant) (union_pattern) @variant + (value_expr(upper_case_qid(upper_case_identifier)) @type) [ diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm index c220676290ce8242c0890716493c4c6369a555e0..88ff9a5af2a7fcd725c79b2a737f07e50fb09dd5 100644 --- a/crates/zed/src/languages/elm/outline.scm +++ b/crates/zed/src/languages/elm/outline.scm @@ -12,11 +12,10 @@ (type_expression part: (record_type (field_type - name: (lower_case_identifier) @name)))) @item + name: (lower_case_identifier) @name) @item))) (union_variant name: (upper_case_identifier) @name) @item -(value_declaration - functionDeclarationLeft: - (function_declaration_left(lower_case_identifier) @name)) @item +(type_annotation + name: (_) @name) @item From d95c4fdb2b182c0ae6238614cdc966b829646de4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 09:01:15 -0700 Subject: [PATCH 150/162] Remove unbound highlight queries --- crates/zed/src/languages/elm/highlights.scm | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index e20873af1bd35302d84a34cf65a2e5540aac03a5..3ab1028a3a46522271a8c3444047df847092b69e 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -51,9 +51,6 @@ (type_ref) @type (type_alias_declaration name: (upper_case_identifier) @type) -(union_variant(upper_case_identifier) @variant) -(union_pattern) @variant - (value_expr(upper_case_qid(upper_case_identifier)) @type) [ From 41105136a45ff46eca75753fe1361705ba93b1b6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 10:20:10 -0700 Subject: [PATCH 151/162] Add MacOS standard key binding for file renames --- assets/keymaps/default.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index d970df1abdeb3a4d0de973e88294f17cb5baa4e4..5c1002a5fc6015c6d31c658a57759b2686c1d671 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -528,6 +528,7 @@ "cmd-alt-c": "project_panel::CopyPath", "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", + "enter": "project_panel::Rename", "backspace": "project_panel::Delete", "alt-cmd-r": "project_panel::RevealInFinder", "alt-shift-f": "project_panel::NewSearchInDirectory" From fbe01089598fee96a5bbf87a44161dba330d7ec8 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:44:56 -0700 Subject: [PATCH 152/162] Highlight Elm arrows as keywords --- crates/zed/src/languages/elm/highlights.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index 3ab1028a3a46522271a8c3444047df847092b69e..be6657845323044c744f3e634d3c85000698a468 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -14,10 +14,10 @@ (import) (module) (type) + (arrow) ] @keyword [ - (arrow) (eq) (operator_identifier) (colon) From 62ee52a5fc6e98409aa25e9e6dd7b815068f42be Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:45:15 -0700 Subject: [PATCH 153/162] Highlight qualified Elm function calls --- crates/zed/src/languages/elm/highlights.scm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index be6657845323044c744f3e634d3c85000698a468..5838aa4796d4336603a18e63c9dd83c6945be131 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -26,6 +26,11 @@ (type_annotation(lower_case_identifier) @function) (port_annotation(lower_case_identifier) @function) (function_declaration_left(lower_case_identifier) @function.definition) + +(function_call_expr + target: (value_expr + name: (value_qid (dot) (lower_case_identifier) @function))) + (function_call_expr target: (value_expr) @identifier) (exposed_value(lower_case_identifier) @function) From 3cc88904bfa33f2fd84aa75271b4df851c2148dd Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:45:46 -0700 Subject: [PATCH 154/162] Add all Elm functions to the outline, including locals --- crates/zed/src/languages/elm/outline.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm index 88ff9a5af2a7fcd725c79b2a737f07e50fb09dd5..1d7d5a70b0183cb457ab993d864d3e9b0617254d 100644 --- a/crates/zed/src/languages/elm/outline.scm +++ b/crates/zed/src/languages/elm/outline.scm @@ -17,5 +17,6 @@ (union_variant name: (upper_case_identifier) @name) @item -(type_annotation - name: (_) @name) @item +(value_declaration + functionDeclarationLeft: + (function_declaration_left(lower_case_identifier) @name)) @item From e199a6a3a1024a4f49ed5bc9dd68c025e3d4ff66 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:55:15 -0700 Subject: [PATCH 155/162] Highlight all Elm function calls --- crates/zed/src/languages/elm/highlights.scm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index 5838aa4796d4336603a18e63c9dd83c6945be131..5723c7eecbf20eae4ea3b2d8570f4e1a7a6067ae 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -29,9 +29,7 @@ (function_call_expr target: (value_expr - name: (value_qid (dot) (lower_case_identifier) @function))) - -(function_call_expr target: (value_expr) @identifier) + name: (value_qid (lower_case_identifier) @function))) (exposed_value(lower_case_identifier) @function) (exposed_type(upper_case_identifier) @type) From bdd0b9f3878d3548c03d7f70d219172b655c7443 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 24 Jul 2023 16:35:01 -0400 Subject: [PATCH 156/162] Add open file in project finder via space (#2785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @mikayla-maki for 👀 [This PR added in the ability to rename a file via `enter`](https://github.com/zed-industries/zed/pull/2784). Previously, `enter` was used to both open a file and confirm a rename, so this PR changes the opening of a file to use `space`, which is what VS Code uses. It also makes a bit more sense because now `enter` is just used to start a rename and confirm the rename, vs being used for 2 different actions. N/A on the release notes, as I adjusted the release note in the previously-tagged PR. Release Notes: - N/A --- assets/keymaps/default.json | 1 + crates/project_panel/src/project_panel.rs | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 5c1002a5fc6015c6d31c658a57759b2686c1d671..7553c199258a2b21337e4984ecca3c818de2ba7d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -529,6 +529,7 @@ "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", "enter": "project_panel::Rename", + "space": "project_panel::Open", "backspace": "project_panel::Delete", "alt-cmd-r": "project_panel::RevealInFinder", "alt-shift-f": "project_panel::NewSearchInDirectory" diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2a6da7db28950972dd09afe7a3cb3b646b89e7c3..3e20c4986e30e037c0b408dfffba8f192ce0b1d5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -125,6 +125,7 @@ actions!( Paste, Delete, Rename, + Open, ToggleFocus, NewSearchInDirectory, ] @@ -146,6 +147,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) { cx.add_action(ProjectPanel::rename); cx.add_async_action(ProjectPanel::delete); cx.add_async_action(ProjectPanel::confirm); + cx.add_async_action(ProjectPanel::open_file); cx.add_action(ProjectPanel::cancel); cx.add_action(ProjectPanel::cut); cx.add_action(ProjectPanel::copy); @@ -560,15 +562,20 @@ impl ProjectPanel { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) -> Option>> { if let Some(task) = self.confirm_edit(cx) { - Some(task) - } else if let Some((_, entry)) = self.selected_entry(cx) { + return Some(task); + } + + None + } + + fn open_file(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { + if let Some((_, entry)) = self.selected_entry(cx) { if entry.is_file() { self.open_entry(entry.id, true, cx); } - None - } else { - None } + + None } fn confirm_edit(&mut self, cx: &mut ViewContext) -> Option>> { @@ -2382,7 +2389,7 @@ mod tests { toggle_expand_dir(&panel, "src/test", cx); select_path(&panel, "src/test/first.rs", cx); - panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); cx.foreground().run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), @@ -2410,7 +2417,7 @@ mod tests { ensure_no_open_items_and_panes(window_id, &workspace, cx); select_path(&panel, "src/test/second.rs", cx); - panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); cx.foreground().run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), From 1a8438288128bdda84dc35e6efc958aa8415b70e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 24 Jul 2023 22:30:51 -0400 Subject: [PATCH 157/162] WIP --- crates/editor/src/editor.rs | 103 ++++++++++++++++++++++++++++++ crates/editor/src/editor_tests.rs | 79 +++++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f1cf5a942b05da76cf7a85046f3a8925bf375523..fe7667cd2badfef7010dbce7de70e1db5048b1ca 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -74,6 +74,8 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; +use rand::seq::SliceRandom; +use rand::thread_rng; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -226,6 +228,10 @@ actions!( MoveLineUp, MoveLineDown, JoinLines, + SortLinesCaseSensitive, + SortLinesCaseInsensitive, + ReverseLines, + ShuffleLines, Transpose, Cut, Copy, @@ -344,6 +350,10 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::outdent); cx.add_action(Editor::delete_line); cx.add_action(Editor::join_lines); + cx.add_action(Editor::sort_lines_case_sensitive); + cx.add_action(Editor::sort_lines_case_insensitive); + cx.add_action(Editor::reverse_lines); + // cx.add_action(Editor::shuffle_lines); cx.add_action(Editor::delete_to_previous_word_start); cx.add_action(Editor::delete_to_previous_subword_start); cx.add_action(Editor::delete_to_next_word_end); @@ -4205,6 +4215,99 @@ impl Editor { }); } + pub fn sort_lines_case_sensitive( + &mut self, + _: &SortLinesCaseSensitive, + cx: &mut ViewContext, + ) { + self.manipulate_lines(cx, { + |mut lines| { + lines.sort(); + lines + } + }) + } + + pub fn sort_lines_case_insensitive( + &mut self, + _: &SortLinesCaseInsensitive, + cx: &mut ViewContext, + ) { + self.manipulate_lines(cx, { + |mut lines| { + lines.sort_by_key(|line| line.to_lowercase()); + lines + } + }) + } + + pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { + self.manipulate_lines(cx, { + |mut lines| { + lines.reverse(); + lines + } + }) + } + + // pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { + // self.manipulate_lines(cx, { + // |mut lines| { + // lines.shuffle(&mut thread_rng()); + // lines + // } + // }) + // } + + fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) + where + Fn: FnMut(Vec<&str>) -> Vec<&str>, + { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx).snapshot(cx); + + let mut edits = Vec::new(); + + let selections = self.selections.all::(cx); + let mut selections = selections.iter().peekable(); + let mut contiguous_row_selections = Vec::new(); + + while let Some(selection) = selections.next() { + // Find all the selections that span a contiguous row range + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); + let start = Point::new(start_row, 0); + let end = Point::new(end_row - 1, buffer.line_len(end_row - 1)); + let text = buffer.text_for_range(start..end).collect::(); + // TODO SORT LINES: Is there a smarter / more effificent way to obtain lines? + let lines = text.split("\n").collect::>(); + let lines = callback(lines); + edits.push((start..end, lines.join("\n"))); + } + + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); + }); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select(contiguous_row_selections); + }); + + this.request_autoscroll(Autoscroll::fit(), cx); + }); + + // TODO: + // Write tests + // - Use cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); in tests + // Mikayla check for perf stuff + // Shuffle + } + pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 247a7b021d77cac7d2386f67fc676d7723b037fc..18028eb8ee80d8fad2c937d73c2441dff3dd5150 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -2500,6 +2500,85 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { }); } +#[gpui::test] +fn test_sort_lines_with_single_selection(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + cx.add_window(|cx| { + let buffer = MultiBuffer::build_simple("dddd\nccc\nbb\na\n\n", cx); + let mut editor = build_editor(buffer.clone(), cx); + let buffer = buffer.read(cx).as_singleton().unwrap(); + + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(0, 2)..Point::new(0, 2)]) + }); + editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); + assert_eq!( + buffer.read(cx).text(), + "dddd\nccc\nbb\na\n\n", + "no sorting when single cursor parked on single line" + ); + assert_eq!( + editor.selections.ranges::(cx), + &[Point::new(0, 2)..Point::new(0, 2)] + ); + + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(0, 2)..Point::new(5, 1)]) + }); + editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); + assert_eq!( + buffer.read(cx).text(), + "a\nbb\nccc\ndddd\n\n", + "single selection is sorted" + ); + assert_eq!( + editor.selections.ranges::(cx), + &[Point::new(0, 0)..Point::new(5, 1)] + ); + + editor + }); +} + +#[gpui::test] +fn test_sort_lines_with_multi_selection(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + cx.add_window(|cx| { + let buffer = MultiBuffer::build_simple("dddd\nccc\nbb\na\n\n3\n2\n1\n\n", cx); + let mut editor = build_editor(buffer.clone(), cx); + let buffer = buffer.read(cx).as_singleton().unwrap(); + + editor.change_selections(None, cx, |s| { + s.select_ranges([ + Point::new(0, 2)..Point::new(3, 2), + Point::new(5, 0)..Point::new(7, 1), + ]) + }); + + editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); + assert_eq!(buffer.read(cx).text(), "a\nbb\nccc\ndddd\n\n1\n2\n3\n\n"); + assert_eq!( + editor.selections.ranges::(cx), + &[Point::new(0, 5)..Point::new(2, 2)] + ); + assert_eq!( + editor.selections.ranges::(cx), + &[Point::new(0, 5)..Point::new(2, 2)] + ); + + // assert_eq!( + // editor.selections.ranges::(cx), + // [ + // Point::new(0, 7)..Point::new(0, 7), + // Point::new(1, 3)..Point::new(1, 3) + // ] + // ); + editor + }); +} + #[gpui::test] fn test_duplicate_line(cx: &mut TestAppContext) { init_test(cx, |_| {}); From 299818cde0afeb4a0d83311a238f8fea6f77f199 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 25 Jul 2023 11:44:13 -0400 Subject: [PATCH 158/162] Fix rand import and tweak callbacks Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com> --- crates/editor/Cargo.toml | 3 +-- crates/editor/src/editor.rs | 53 ++++++++++--------------------------- 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 087ce81c268ffcbc11f598031b22d9ee272a114e..bc1c904404972cb847947b8c60147bcf4a41186b 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -10,7 +10,6 @@ doctest = false [features] test-support = [ - "rand", "copilot/test-support", "text/test-support", "language/test-support", @@ -62,8 +61,8 @@ serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true +rand.workspace = true -rand = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-html = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fe7667cd2badfef7010dbce7de70e1db5048b1ca..04821df62eb5433b11545be379fe9f9252d7ee29 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -74,8 +74,9 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; -use rand::seq::SliceRandom; -use rand::thread_rng; +use rand::{seq::SliceRandom, thread_rng}; +// use rand::seq::SliceRandom; +// use rand::thread_rng; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -353,7 +354,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::sort_lines_case_sensitive); cx.add_action(Editor::sort_lines_case_insensitive); cx.add_action(Editor::reverse_lines); - // cx.add_action(Editor::shuffle_lines); + cx.add_action(Editor::shuffle_lines); cx.add_action(Editor::delete_to_previous_word_start); cx.add_action(Editor::delete_to_previous_subword_start); cx.add_action(Editor::delete_to_next_word_end); @@ -4220,12 +4221,7 @@ impl Editor { _: &SortLinesCaseSensitive, cx: &mut ViewContext, ) { - self.manipulate_lines(cx, { - |mut lines| { - lines.sort(); - lines - } - }) + self.manipulate_lines(cx, |lines| lines.sort()) } pub fn sort_lines_case_insensitive( @@ -4233,35 +4229,20 @@ impl Editor { _: &SortLinesCaseInsensitive, cx: &mut ViewContext, ) { - self.manipulate_lines(cx, { - |mut lines| { - lines.sort_by_key(|line| line.to_lowercase()); - lines - } - }) + self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase())) } pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { - self.manipulate_lines(cx, { - |mut lines| { - lines.reverse(); - lines - } - }) + self.manipulate_lines(cx, |lines| lines.reverse()) } - // pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { - // self.manipulate_lines(cx, { - // |mut lines| { - // lines.shuffle(&mut thread_rng()); - // lines - // } - // }) - // } + pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { + self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) + } fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) where - Fn: FnMut(Vec<&str>) -> Vec<&str>, + Fn: FnMut(&mut Vec<&str>), { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); @@ -4280,12 +4261,12 @@ impl Editor { &display_map, &mut selections, ); + let start = Point::new(start_row, 0); let end = Point::new(end_row - 1, buffer.line_len(end_row - 1)); let text = buffer.text_for_range(start..end).collect::(); - // TODO SORT LINES: Is there a smarter / more effificent way to obtain lines? - let lines = text.split("\n").collect::>(); - let lines = callback(lines); + let mut lines = text.split("\n").collect::>(); + callback(&mut lines); edits.push((start..end, lines.join("\n"))); } @@ -4300,12 +4281,6 @@ impl Editor { this.request_autoscroll(Autoscroll::fit(), cx); }); - - // TODO: - // Write tests - // - Use cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); in tests - // Mikayla check for perf stuff - // Shuffle } pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { From 93ec73da2930daf4e4ceeb6b283787e0d2e1581e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 25 Jul 2023 14:04:25 -0400 Subject: [PATCH 159/162] Fix code computing new selections Co-Authored-By: Mikayla Maki --- crates/editor/src/editor.rs | 55 ++++++++++++++++------ crates/editor/src/editor_tests.rs | 12 ++--- crates/editor/src/selections_collection.rs | 2 +- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 04821df62eb5433b11545be379fe9f9252d7ee29..655fb39257f791ba086fede8f4f60c2449f13b31 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -75,8 +75,6 @@ pub use multi_buffer::{ use ordered_float::OrderedFloat; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; -// use rand::seq::SliceRandom; -// use rand::thread_rng; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -4221,7 +4219,7 @@ impl Editor { _: &SortLinesCaseSensitive, cx: &mut ViewContext, ) { - self.manipulate_lines(cx, |lines| lines.sort()) + self.manipulate_lines(cx, |text| text.sort()) } pub fn sort_lines_case_insensitive( @@ -4229,20 +4227,24 @@ impl Editor { _: &SortLinesCaseInsensitive, cx: &mut ViewContext, ) { - self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase())) + self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase())) } pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { - self.manipulate_lines(cx, |lines| lines.reverse()) + self.manipulate_lines(cx, |lines| { + lines.reverse(); + }) } pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { - self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) + self.manipulate_lines(cx, |lines| { + lines.shuffle(&mut thread_rng()); + }) } fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) where - Fn: FnMut(&mut Vec<&str>), + Fn: FnMut(&mut [&str]), { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); @@ -4252,9 +4254,9 @@ impl Editor { let selections = self.selections.all::(cx); let mut selections = selections.iter().peekable(); let mut contiguous_row_selections = Vec::new(); + let mut new_selections = Vec::new(); while let Some(selection) = selections.next() { - // Find all the selections that span a contiguous row range let (start_row, end_row) = consume_contiguous_rows( &mut contiguous_row_selections, selection, @@ -4262,12 +4264,35 @@ impl Editor { &mut selections, ); - let start = Point::new(start_row, 0); - let end = Point::new(end_row - 1, buffer.line_len(end_row - 1)); - let text = buffer.text_for_range(start..end).collect::(); - let mut lines = text.split("\n").collect::>(); - callback(&mut lines); - edits.push((start..end, lines.join("\n"))); + let start_point = Point::new(start_row, 0); + let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1)); + let text = buffer + .text_for_range(start_point..end_point) + .collect::(); + let mut text = text.split("\n").collect_vec(); + + let text_len = text.len(); + callback(&mut text); + + // This is a current limitation with selections. + // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections. + debug_assert!( + text.len() == text_len, + "callback should not change the number of lines" + ); + + edits.push((start_point..end_point, text.join("\n"))); + let start_anchor = buffer.anchor_after(start_point); + let end_anchor = buffer.anchor_before(end_point); + + // Make selection and push + new_selections.push(Selection { + id: selection.id, + start: start_anchor.to_offset(&buffer), + end: end_anchor.to_offset(&buffer), + goal: SelectionGoal::None, + reversed: selection.reversed, + }); } self.transact(cx, |this, cx| { @@ -4276,7 +4301,7 @@ impl Editor { }); this.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select(contiguous_row_selections); + s.select(new_selections); }); this.request_autoscroll(Autoscroll::fit(), cx); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 18028eb8ee80d8fad2c937d73c2441dff3dd5150..bc814f8c15e34b66546f0bcadea71c6c37773741 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -2501,7 +2501,7 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { } #[gpui::test] -fn test_sort_lines_with_single_selection(cx: &mut TestAppContext) { +fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); cx.add_window(|cx| { @@ -2510,7 +2510,7 @@ fn test_sort_lines_with_single_selection(cx: &mut TestAppContext) { let buffer = buffer.read(cx).as_singleton().unwrap(); editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(0, 2)..Point::new(0, 2)]) + s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) }); editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); assert_eq!( @@ -2520,13 +2520,13 @@ fn test_sort_lines_with_single_selection(cx: &mut TestAppContext) { ); assert_eq!( editor.selections.ranges::(cx), - &[Point::new(0, 2)..Point::new(0, 2)] + &[Point::new(0, 1)..Point::new(0, 2)] ); editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(0, 2)..Point::new(5, 1)]) + s.select_ranges([Point::new(0, 2)..Point::new(5, 0)]) }); - editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); + //editor.sort_lines(); assert_eq!( buffer.read(cx).text(), "a\nbb\nccc\ndddd\n\n", @@ -2542,7 +2542,7 @@ fn test_sort_lines_with_single_selection(cx: &mut TestAppContext) { } #[gpui::test] -fn test_sort_lines_with_multi_selection(cx: &mut TestAppContext) { +fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); cx.add_window(|cx| { diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index a22506f751b1ab36d518cdc13f60dc61dfecf6c7..1921bc0738ec588c8b9218c717de497232a345ec 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -138,7 +138,7 @@ impl SelectionsCollection { .collect() } - // Returns all of the selections, adjusted to take into account the selection line_mode + /// Returns all of the selections, adjusted to take into account the selection line_mode pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec> { let mut selections = self.all::(cx); if self.line_mode { From bf2ca57f55182ab8568f4d6abe4d69bbc63c9f5b Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 25 Jul 2023 14:48:11 -0400 Subject: [PATCH 160/162] Remove { and } from single-line closure --- crates/editor/src/editor.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 655fb39257f791ba086fede8f4f60c2449f13b31..e05837740d4c50fbe7e02e51272bd6340884ec8a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4231,15 +4231,11 @@ impl Editor { } pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { - self.manipulate_lines(cx, |lines| { - lines.reverse(); - }) + self.manipulate_lines(cx, |lines| lines.reverse()) } pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { - self.manipulate_lines(cx, |lines| { - lines.shuffle(&mut thread_rng()); - }) + self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) } fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) From 4085df5146f015d88c0daca229896ec32e94ee84 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 25 Jul 2023 15:17:16 -0400 Subject: [PATCH 161/162] Add tests for manipulate_lines() --- crates/editor/src/editor_tests.rs | 199 ++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 64 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index bc814f8c15e34b66546f0bcadea71c6c37773741..eb03d2bdc0f103c2b94352312d3e86df860c821c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -2501,82 +2501,153 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { } #[gpui::test] -fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) { +async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); - cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("dddd\nccc\nbb\na\n\n", cx); - let mut editor = build_editor(buffer.clone(), cx); - let buffer = buffer.read(cx).as_singleton().unwrap(); + let mut cx = EditorTestContext::new(cx).await; - editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) - }); - editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); - assert_eq!( - buffer.read(cx).text(), - "dddd\nccc\nbb\na\n\n", - "no sorting when single cursor parked on single line" - ); - assert_eq!( - editor.selections.ranges::(cx), - &[Point::new(0, 1)..Point::new(0, 2)] - ); + // Test sort_lines_case_insensitive() + cx.set_state(indoc! {" + «z + y + x + Z + Y + Xˇ» + "}); + cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx)); + cx.assert_editor_state(indoc! {" + «x + X + y + Y + z + Zˇ» + "}); - editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(0, 2)..Point::new(5, 0)]) - }); - //editor.sort_lines(); - assert_eq!( - buffer.read(cx).text(), - "a\nbb\nccc\ndddd\n\n", - "single selection is sorted" - ); - assert_eq!( - editor.selections.ranges::(cx), - &[Point::new(0, 0)..Point::new(5, 1)] - ); + // Test reverse_lines() + cx.set_state(indoc! {" + «5 + 4 + 3 + 2 + 1ˇ» + "}); + cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx)); + cx.assert_editor_state(indoc! {" + «1 + 2 + 3 + 4 + 5ˇ» + "}); - editor - }); + // Skip testing shuffle_line() + + // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive() + // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines) + + // Don't manipulate when cursor is on single line, but expand the selection + cx.set_state(indoc! {" + ddˇdd + ccc + bb + a + "}); + cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); + cx.assert_editor_state(indoc! {" + «ddddˇ» + ccc + bb + a + "}); + + // Basic manipulate case + // Start selection moves to column 0 + // End of selection shrinks to fit shorter line + cx.set_state(indoc! {" + dd«d + ccc + bb + aaaaaˇ» + "}); + cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); + cx.assert_editor_state(indoc! {" + «aaaaa + bb + ccc + dddˇ» + "}); + + // Manipulate case with newlines + cx.set_state(indoc! {" + dd«d + ccc + + bb + aaaaa + + ˇ» + "}); + cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); + cx.assert_editor_state(indoc! {" + « + + aaaaa + bb + ccc + dddˇ» + + "}); } #[gpui::test] -fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) { +async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); - cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("dddd\nccc\nbb\na\n\n3\n2\n1\n\n", cx); - let mut editor = build_editor(buffer.clone(), cx); - let buffer = buffer.read(cx).as_singleton().unwrap(); - - editor.change_selections(None, cx, |s| { - s.select_ranges([ - Point::new(0, 2)..Point::new(3, 2), - Point::new(5, 0)..Point::new(7, 1), - ]) - }); + let mut cx = EditorTestContext::new(cx).await; - editor.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx); - assert_eq!(buffer.read(cx).text(), "a\nbb\nccc\ndddd\n\n1\n2\n3\n\n"); - assert_eq!( - editor.selections.ranges::(cx), - &[Point::new(0, 5)..Point::new(2, 2)] - ); - assert_eq!( - editor.selections.ranges::(cx), - &[Point::new(0, 5)..Point::new(2, 2)] - ); + // Manipulate with multiple selections on a single line + cx.set_state(indoc! {" + dd«dd + cˇ»c«c + bb + aaaˇ»aa + "}); + cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); + cx.assert_editor_state(indoc! {" + «aaaaa + bb + ccc + ddddˇ» + "}); - // assert_eq!( - // editor.selections.ranges::(cx), - // [ - // Point::new(0, 7)..Point::new(0, 7), - // Point::new(1, 3)..Point::new(1, 3) - // ] - // ); - editor - }); + // Manipulate with multiple disjoin selections + cx.set_state(indoc! {" + 5« + 4 + 3 + 2 + 1ˇ» + + dd«dd + ccc + bb + aaaˇ»aa + "}); + cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); + cx.assert_editor_state(indoc! {" + «1 + 2 + 3 + 4 + 5ˇ» + + «aaaaa + bb + ccc + ddddˇ» + "}); } #[gpui::test] From 1b03c5d69c20ebc4dca4790ccf5be43a6afe5011 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 25 Jul 2023 17:32:31 -0600 Subject: [PATCH 162/162] Pass PaintContext to Element::paint I want to use this on another branch, but it's a sweeping change, so this prepares the ground for it. This can always be reverted if it doesn't work out. --- crates/collab_ui/src/collab_titlebar_item.rs | 6 +- crates/collab_ui/src/face_pile.rs | 4 +- crates/editor/src/element.rs | 13 ++- crates/gpui/src/app.rs | 87 +++++++++++++++++++ crates/gpui/src/app/window.rs | 6 +- crates/gpui/src/elements.rs | 26 ++++-- crates/gpui/src/elements/align.rs | 5 +- crates/gpui/src/elements/canvas.rs | 4 +- crates/gpui/src/elements/clipped.rs | 5 +- crates/gpui/src/elements/constrained_box.rs | 5 +- crates/gpui/src/elements/container.rs | 5 +- crates/gpui/src/elements/empty.rs | 4 +- crates/gpui/src/elements/expanded.rs | 5 +- crates/gpui/src/elements/flex.rs | 8 +- crates/gpui/src/elements/hook.rs | 5 +- crates/gpui/src/elements/image.rs | 6 +- crates/gpui/src/elements/keystroke_label.rs | 2 +- crates/gpui/src/elements/label.rs | 4 +- crates/gpui/src/elements/list.rs | 10 +-- .../gpui/src/elements/mouse_event_handler.rs | 6 +- crates/gpui/src/elements/overlay.rs | 6 +- crates/gpui/src/elements/resizable.rs | 6 +- crates/gpui/src/elements/stack.rs | 5 +- crates/gpui/src/elements/svg.rs | 3 +- crates/gpui/src/elements/text.rs | 6 +- crates/gpui/src/elements/tooltip.rs | 6 +- crates/gpui/src/elements/uniform_list.rs | 4 +- crates/gpui/src/fonts.rs | 26 ++++++ crates/terminal_view/src/terminal_element.rs | 7 +- crates/workspace/src/pane.rs | 6 +- crates/workspace/src/pane_group.rs | 6 +- crates/workspace/src/status_bar.rs | 6 +- 32 files changed, 223 insertions(+), 80 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index ce8d10d655d76b018aef855a934c95e1d7b1640c..04abdf8c1cc850d4913ea2f67fee9e5cfa4b1825 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -15,8 +15,8 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder, + Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use picker::PickerEvent; use project::{Project, RepositoryEntry}; @@ -1312,7 +1312,7 @@ impl Element for AvatarRibbon { _: RectF, _: &mut Self::LayoutState, _: &mut CollabTitlebarItem, - _: &mut ViewContext, + _: &mut PaintContext, ) -> Self::PaintState { let mut path = PathBuilder::new(); path.reset(bounds.lower_left()); diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index 1bbceee9af1bec406bf9b1398fde94dd230ac73d..9685d86b402db8dad693ac60af01a64bd21f141d 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -7,7 +7,7 @@ use gpui::{ }, json::ToJson, serde_json::{self, json}, - AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext, + AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, ViewContext, }; use crate::CollabTitlebarItem; @@ -54,7 +54,7 @@ impl Element for FacePile { visible_bounds: RectF, _layout: &mut Self::LayoutState, view: &mut CollabTitlebarItem, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b48fa5b56dd831f82852f224370500027302f3af..94a849b0d0519af7f0fe5831ccbd993498f53b2d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,7 +32,7 @@ use gpui::{ platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, text_layout::{self, Line, RunStyle, TextLayoutCache}, AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, - MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, + MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, }; use itertools::Itertools; use json::json; @@ -2455,7 +2455,7 @@ impl Element for EditorElement { visible_bounds: RectF, layout: &mut Self::LayoutState, editor: &mut Editor, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); scene.push_layer(Some(visible_bounds)); @@ -3051,7 +3051,14 @@ mod tests { let mut scene = SceneBuilder::new(1.0); let bounds = RectF::new(Default::default(), size); editor.update(cx, |editor, cx| { - element.paint(&mut scene, bounds, bounds, &mut state, editor, cx); + element.paint( + &mut scene, + bounds, + bounds, + &mut state, + editor, + &mut PaintContext::new(cx), + ); }); } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7af363d596b63abe06f78717d8945dcba820d7fd..fd22dc466e24be2bab1eb9d22c720218e4734380 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -44,6 +44,7 @@ use window_input_handler::WindowInputHandler; use crate::{ elements::{AnyElement, AnyRootElement, RootElement}, executor::{self, Task}, + fonts::TextStyle, json, keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult}, platform::{ @@ -3363,6 +3364,7 @@ pub struct LayoutContext<'a, 'b, 'c, V: View> { view_context: &'c mut ViewContext<'a, 'b, V>, new_parents: &'c mut HashMap, views_to_notify_if_ancestors_change: &'c mut HashMap>, + text_style_stack: Vec>, pub refreshing: bool, } @@ -3377,6 +3379,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> { view_context, new_parents, views_to_notify_if_ancestors_change, + text_style_stack: Vec::new(), refreshing, } } @@ -3428,6 +3431,24 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> { .or_default() .push(self_view_id); } + + pub fn text_style(&self) -> Arc { + self.text_style_stack + .last() + .cloned() + .unwrap_or(Default::default()) + } + + pub fn with_text_style(&mut self, style: S, f: F) -> T + where + S: Into>, + F: FnOnce(&mut Self) -> T, + { + self.text_style_stack.push(style.into()); + let result = f(self); + self.text_style_stack.pop(); + result + } } impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> { @@ -3464,6 +3485,72 @@ impl BorrowWindowContext for LayoutContext<'_, '_, '_, V> { } } +pub struct PaintContext<'a, 'b, 'c, V: View> { + view_context: &'c mut ViewContext<'a, 'b, V>, + text_style_stack: Vec>, +} + +impl<'a, 'b, 'c, V: View> PaintContext<'a, 'b, 'c, V> { + pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self { + Self { + view_context, + text_style_stack: Vec::new(), + } + } + + pub fn text_style(&self) -> Arc { + self.text_style_stack + .last() + .cloned() + .unwrap_or(Default::default()) + } + + pub fn with_text_style(&mut self, style: S, f: F) -> T + where + S: Into>, + F: FnOnce(&mut Self) -> T, + { + self.text_style_stack.push(style.into()); + let result = f(self); + self.text_style_stack.pop(); + result + } +} + +impl<'a, 'b, 'c, V: View> Deref for PaintContext<'a, 'b, 'c, V> { + type Target = ViewContext<'a, 'b, V>; + + fn deref(&self) -> &Self::Target { + &self.view_context + } +} + +impl DerefMut for PaintContext<'_, '_, '_, V> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.view_context + } +} + +impl BorrowAppContext for PaintContext<'_, '_, '_, V> { + fn read_with T>(&self, f: F) -> T { + BorrowAppContext::read_with(&*self.view_context, f) + } + + fn update T>(&mut self, f: F) -> T { + BorrowAppContext::update(&mut *self.view_context, f) + } +} + +impl BorrowWindowContext for PaintContext<'_, '_, '_, V> { + fn read_with T>(&self, window_id: usize, f: F) -> T { + BorrowWindowContext::read_with(&*self.view_context, window_id, f) + } + + fn update T>(&mut self, window_id: usize, f: F) -> T { + BorrowWindowContext::update(&mut *self.view_context, window_id, f) + } +} + pub struct EventContext<'a, 'b, 'c, V: View> { view_context: &'c mut ViewContext<'a, 'b, V>, pub(crate) handled: bool, diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 1dc88d2e717a985be4aed9984597f4abf7b92a1a..c6ab7c6ebb21d322a514ab580b58a786858a6bbe 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -14,8 +14,8 @@ use crate::{ text_layout::TextLayoutCache, util::post_inc, Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect, - Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription, - View, ViewContext, ViewHandle, WindowInvalidation, + Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, PaintContext, SceneBuilder, + Subscription, View, ViewContext, ViewHandle, WindowInvalidation, }; use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; @@ -1400,7 +1400,7 @@ impl Element for ChildView { visible_bounds: RectF, _: &mut Self::LayoutState, _: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) { rendered_view diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 78403444fff1807ee7b01ef8fe6a8f8493edfab2..5bed935319f6cb89dbdcb0955a66d404ee629e69 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,8 +33,8 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, - WindowContext, + json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext, + WeakViewHandle, WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -61,7 +61,7 @@ pub trait Element: 'static { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState; fn rect_for_text_range( @@ -298,7 +298,14 @@ impl> AnyElementState for ElementState { mut layout, } => { let bounds = RectF::new(origin, size); - let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx); + let paint = element.paint( + scene, + bounds, + visible_bounds, + &mut layout, + view, + &mut PaintContext::new(cx), + ); ElementState::PostPaint { element, constraint, @@ -316,7 +323,14 @@ impl> AnyElementState for ElementState { .. } => { let bounds = RectF::new(origin, bounds.size()); - let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx); + let paint = element.paint( + scene, + bounds, + visible_bounds, + &mut layout, + view, + &mut PaintContext::new(cx), + ); ElementState::PostPaint { element, constraint, @@ -513,7 +527,7 @@ impl Element for AnyElement { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { self.paint(scene, bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index 165cfcf190c7d69304c424f0bbed34b048905c44..e60c1ff907474b05a2d179b5f3a6fab0e8028e16 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -1,6 +1,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; use json::ToJson; @@ -69,7 +70,7 @@ impl Element for Align { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let my_center = bounds.size() / 2.; let my_target = my_center + my_center * self.alignment; diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index bbd8d0393cd038b3e1bde28aa4811280c0dcd768..2d33ba45e54f6a586ab91a41535c919f3d903355 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::Element; use crate::{ json::{self, json}, - SceneBuilder, View, ViewContext, + PaintContext, SceneBuilder, View, ViewContext, }; use json::ToJson; use pathfinder_geometry::{ @@ -56,7 +56,7 @@ where visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { self.0(scene, bounds, visible_bounds, view, cx) } diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs index a87dc3e7735c181a8716a61acf8fc7720c62532c..4e8cd4bc15e92ad870f38104bf51c4a6fe11ab31 100644 --- a/crates/gpui/src/elements/clipped.rs +++ b/crates/gpui/src/elements/clipped.rs @@ -4,7 +4,8 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use serde_json::json; use crate::{ - json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; pub struct Clipped { @@ -37,7 +38,7 @@ impl Element for Clipped { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { scene.paint_layer(Some(bounds), |scene| { self.child diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index 46916c74f1054d442e190a809d69a5fa945ad631..0d540a47b44025f4a2628df40a044325c4bdaf84 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/crates/gpui/src/elements/constrained_box.rs @@ -5,7 +5,8 @@ use serde_json::json; use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; pub struct ConstrainedBox { @@ -156,7 +157,7 @@ impl Element for ConstrainedBox { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { scene.paint_layer(Some(visible_bounds), |scene| { self.child diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index 3b95feb9ef86b7c49f6535fff38bfcc4183888cb..656847980c9095c2dcfe0914e1d3b34c3a1556be 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -10,7 +10,8 @@ use crate::{ json::ToJson, platform::CursorStyle, scene::{self, Border, CursorRegion, Quad}, - AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -214,7 +215,7 @@ impl Element for Container { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let quad_bounds = RectF::from_points( bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top), diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs index 42a3824bfcb7f610b715424af6297363a469751b..70580684538b710b96df043ca9edd9d1a78be5ec 100644 --- a/crates/gpui/src/elements/empty.rs +++ b/crates/gpui/src/elements/empty.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - LayoutContext, SceneBuilder, View, ViewContext, + LayoutContext, PaintContext, SceneBuilder, View, ViewContext, }; use crate::{Element, SizeConstraint}; @@ -57,7 +57,7 @@ impl Element for Empty { _: RectF, _: &mut Self::LayoutState, _: &mut V, - _: &mut ViewContext, + _: &mut PaintContext, ) -> Self::PaintState { } diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs index 1fb935b2b8fa272413698fb14d7cfa8ab9cdf51b..1f4f2f40a1e031a4cc3436975cad3a520f4dadae 100644 --- a/crates/gpui/src/elements/expanded.rs +++ b/crates/gpui/src/elements/expanded.rs @@ -2,7 +2,8 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; use serde_json::json; @@ -61,7 +62,7 @@ impl Element for Expanded { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { self.child .paint(scene, bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 857f3f56fc08b0b24f39011d7f4323838b97dde2..3000b9575d5824ea42fd09cb5d10de4353434b25 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, - AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, - Vector2FExt, View, ViewContext, + AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, + SizeConstraint, Vector2FExt, View, ViewContext, }; use pathfinder_geometry::{ rect::RectF, @@ -258,7 +258,7 @@ impl Element for Flex { visible_bounds: RectF, remaining_space: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -449,7 +449,7 @@ impl Element for FlexItem { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { self.child .paint(scene, bounds.origin(), visible_bounds, view, cx) diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs index 310b3c25ebeda1778daf3fd060bf24af6be10822..7ac6c95f3e5cf4c42b2728e1643c1cce38efd513 100644 --- a/crates/gpui/src/elements/hook.rs +++ b/crates/gpui/src/elements/hook.rs @@ -3,7 +3,8 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::json, - AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; pub struct Hook { @@ -52,7 +53,7 @@ impl Element for Hook { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { self.child .paint(scene, bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index df200eae7ff256424ffc9b402cc87fb6ba72b3a4..e87c06591756ccdbc463dbc03af88dd1adc3aa5f 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -5,8 +5,8 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View, - ViewContext, + scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, + View, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -97,7 +97,7 @@ impl Element for Image { _: RectF, layout: &mut Self::LayoutState, _: &mut V, - _: &mut ViewContext, + _: &mut PaintContext, ) -> Self::PaintState { if let Some(data) = layout { scene.push_image(scene::Image { diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index c011649b2e946f980cd5b3eb2e7ab448bde8e2e1..268f3ccb1c856b3f0ee5f14360a1eb625550fc69 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -66,7 +66,7 @@ impl Element for KeystrokeLabel { visible_bounds: RectF, element: &mut AnyElement, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { element.paint(scene, bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index d9cf537333c4e60b81f1cd218f678eae4f0a19ad..80555c7442cdbefb8bb9e83d30f2cfb69da8efe3 100644 --- a/crates/gpui/src/elements/label.rs +++ b/crates/gpui/src/elements/label.rs @@ -8,7 +8,7 @@ use crate::{ }, json::{ToJson, Value}, text_layout::{Line, RunStyle}, - Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -163,7 +163,7 @@ impl Element for Label { visible_bounds: RectF, line: &mut Self::LayoutState, _: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); line.paint( diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 4c6298d8f55a28abeb6cec468af63a94eec21759..54545199056d2f7e61f6ce352a8639fc91add6ee 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -4,8 +4,8 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::json, - AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, - ViewContext, + AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint, + View, ViewContext, }; use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc}; use sum_tree::{Bias, SumTree}; @@ -255,7 +255,7 @@ impl Element for List { visible_bounds: RectF, scroll_top: &mut ListOffset, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); scene.push_layer(Some(visible_bounds)); @@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { #[cfg(test)] mod tests { use super::*; - use crate::{elements::Empty, geometry::vector::vec2f, Entity}; + use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext}; use rand::prelude::*; use std::env; @@ -988,7 +988,7 @@ mod tests { _: RectF, _: &mut (), _: &mut V, - _: &mut ViewContext, + _: &mut PaintContext, ) { unimplemented!() } diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 1b8142d96464ff7c9f30230510a13c58c6f08cb6..6005277f73567381fd8e3c019b9a57789781bae1 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -10,8 +10,8 @@ use crate::{ CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, - AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder, - SizeConstraint, View, ViewContext, + AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext, + SceneBuilder, SizeConstraint, View, ViewContext, }; use serde_json::json; use std::{marker::PhantomData, ops::Range}; @@ -256,7 +256,7 @@ impl Element for MouseEventHandler { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { if self.above { self.child diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 0f7e4a35c673ec78b5001420957670f5943ffe0f..56e3d10de3cf21fa3d217976c38a9de1613e2f1b 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -3,8 +3,8 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, - AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, - ViewContext, + AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, + SizeConstraint, View, ViewContext, }; use serde_json::json; @@ -143,7 +143,7 @@ impl Element for Overlay { _: RectF, size: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { let (anchor_position, mut bounds) = match self.position_mode { OverlayPositionMode::Window => { diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index da4b3473b3069ea343d7acdd0cc85c262e7a76cf..2e252cfaabbbaffa323a5cdb26660b5f7bad8f48 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -7,8 +7,8 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, - ViewContext, + AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, + SizeConstraint, View, ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -125,7 +125,7 @@ impl Element for Resizable { visible_bounds: pathfinder_geometry::rect::RectF, constraint: &mut SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { scene.push_stacking_context(None, None); diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 196c04d2034a0efa796d9d0ac47dcda6c6f6ab51..8f9d1e4d0536536af0788c1551419c199f63d293 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/crates/gpui/src/elements/stack.rs @@ -3,7 +3,8 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::{self, json, ToJson}, - AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, + ViewContext, }; /// Element which renders it's children in a stack on top of each other. @@ -57,7 +58,7 @@ impl Element for Stack { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { for child in &mut self.children { scene.paint_layer(None, |scene| { diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 9792f16cbe19b65c7ff8d7d620f71d87d3c51f57..c4d58cd7a77df7e7d97cd8bdc16fad337d82e484 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,5 +1,6 @@ use super::constrain_size_preserving_aspect_ratio; use crate::json::ToJson; +use crate::PaintContext; use crate::{ color::Color, geometry::{ @@ -73,7 +74,7 @@ impl Element for Svg { _visible_bounds: RectF, svg: &mut Self::LayoutState, _: &mut V, - _: &mut ViewContext, + _: &mut PaintContext, ) { if let Some(svg) = svg.clone() { scene.push_icon(scene::Icon { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 66654fbe93f00065c2c9963743a1fdce9453ff41..9357c31f193f3c6bbaf0a0ca41fdda01535cf3e7 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -7,8 +7,8 @@ use crate::{ }, json::{ToJson, Value}, text_layout::{Line, RunStyle, ShapedBoundary}, - AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache, - View, ViewContext, + AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, + TextLayoutCache, View, ViewContext, }; use log::warn; use serde_json::json; @@ -171,7 +171,7 @@ impl Element for Text { visible_bounds: RectF, layout: &mut Self::LayoutState, _: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let mut origin = bounds.origin(); let empty = Vec::new(); diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index f21b1c363c0d55403654b92394d24649079cbb34..0510baa9e45e2da3764240d6406950f423e5ce0d 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -6,8 +6,8 @@ use crate::{ fonts::TextStyle, geometry::{rect::RectF, vector::Vector2F}, json::json, - Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View, - ViewContext, + Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, + Task, View, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -194,7 +194,7 @@ impl Element for Tooltip { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) { self.child .paint(scene, bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 8344914da0d8f0124fd6ddc5cc1cbc29ab2e4d1d..b9bfadb17f17e0a938431b89f4e45b182331d34c 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -6,7 +6,7 @@ use crate::{ }, json::{self, json}, platform::ScrollWheelEvent, - AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext, + AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext, }; use json::ToJson; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -278,7 +278,7 @@ impl Element for UniformList { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 3b4a94dd0e69137ab95457d3991f88e245758243..b003042866efdf21bcdb54695029765871afa31d 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -71,6 +71,32 @@ pub struct TextStyle { pub underline: Underline, } +impl TextStyle { + pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle { + TextStyle { + color: refinement.color.unwrap_or(self.color), + font_family_name: refinement + .font_family_name + .unwrap_or_else(|| self.font_family_name.clone()), + font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id), + font_id: refinement.font_id.unwrap_or(self.font_id), + font_size: refinement.font_size.unwrap_or(self.font_size), + font_properties: refinement.font_properties.unwrap_or(self.font_properties), + underline: refinement.underline.unwrap_or(self.underline), + } + } +} + +pub struct TextStyleRefinement { + pub color: Option, + pub font_family_name: Option>, + pub font_family_id: Option, + pub font_id: Option, + pub font_size: Option, + pub font_properties: Option, + pub underline: Option, +} + #[derive(JsonSchema)] #[serde(remote = "Properties")] pub struct PropertiesDef { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index e29beb3ad52592bee273a34c24aa25b60c172754..9c402d139a0d35f906f175da27334db4a60d28c1 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -10,8 +10,9 @@ use gpui::{ platform::{CursorStyle, MouseButton}, serde_json::json, text_layout::{Line, RunStyle}, - AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad, - SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle, + AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, + PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, + WeakModelHandle, }; use itertools::Itertools; use language::CursorShape; @@ -730,7 +731,7 @@ impl Element for TerminalElement { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut TerminalView, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2972c307f2624a6bb23bef0f1919aab5a43eb66d..928c9b23f82ba9198dc1d7237cefe5e7a43e1895 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -25,8 +25,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, - LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -1896,7 +1896,7 @@ impl Element for PaneBackdrop { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let background = theme::current(cx).editor.background; diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e60f6deb2ff2663918c7fa3dd8c2722a69dbb2e9..4a90d92b35065c1f51841f90fa02e3881363a8ad 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -593,8 +593,8 @@ mod element { }, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, - SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, + AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, PaintContext, + RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, }; use crate::{ @@ -765,7 +765,7 @@ mod element { visible_bounds: RectF, remaining_space: &mut Self::LayoutState, view: &mut Workspace, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let can_resize = settings::get::(cx).active_pane_magnification == 1.; let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index 6fc1467566bec3a3c93ff6807a15bf1fec988e67..3def545d710d9e7cbfbb13ef419a33ed02f9c651 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -8,8 +8,8 @@ use gpui::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription, - View, ViewContext, ViewHandle, WindowContext, + AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, + Subscription, View, ViewContext, ViewHandle, WindowContext, }; pub trait StatusItemView: View { @@ -177,7 +177,7 @@ impl Element for StatusBarElement { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut StatusBar, - cx: &mut ViewContext, + cx: &mut PaintContext, ) -> Self::PaintState { let origin_y = bounds.upper_right().y(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();