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 01/57] 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 60ed830683..9806c86c96 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 d016525af4..0e92b2e3ea 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 44e144e89b..81edd92d37 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 0000000000..e9de52745a --- /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 0000000000..666a49be2a --- /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 0000000000..16d5736beb --- /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 0000000000..57ea2ae334 --- /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 0000000000..66d594c254 --- /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 373c7c7c68..e3efb3dbf6 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 02/57] 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 16d5736beb..57abd8ea2b 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 488b41826b90102180a3c60c3a088ff1195e0749 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 12 Jul 2023 12:46:56 -0700 Subject: [PATCH 03/57] WIP --- crates/workspace/src/adjustable_flex.rs | 83 ++++++++++++++++++++++++ crates/workspace/src/dock.rs | 3 + crates/workspace/src/pane_group.rs | 84 ++++++++++++++++--------- crates/workspace/src/workspace.rs | 5 +- 4 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 crates/workspace/src/adjustable_flex.rs diff --git a/crates/workspace/src/adjustable_flex.rs b/crates/workspace/src/adjustable_flex.rs new file mode 100644 index 0000000000..4ea1b719f1 --- /dev/null +++ b/crates/workspace/src/adjustable_flex.rs @@ -0,0 +1,83 @@ +use gpui::{Element, View, Axis, AnyElement}; + +// Model for the center group: AdjustableGroup of AdjustableGroups +// Implementation notes +// - These have two representations: Exact pixel widths and ratios of elements compared to whole space +// - We have a constraint of minimum sizes for things. +// - If The space is smaller than allowed, things run off the edge +// - When doing Drag resize, we update the pixel width representation, causing a recalc of the ratios +// - If dragging past minimum, take space from next item, until out of space +// - When doing a reflow (e.g. layout) we read off the ratios and calculate pixels from that +// - When adding / removing items in an Adjustable flex, reset to default ratios (1:1) +// - By default, every item takes up as much space as possible +// + + +struct AdjustableFlex { + axis: Axis, + handle_size: f32, + items: Vec<(AnyElement, f32)> +} + +impl AdjustableFlex { + fn new(axis: Axis) -> Self { + AdjustableFlex { + axis, + handle_size: 2., + items: Vec::new(), + } + } + + fn add_item() +} + +impl Element for AdjustableFlex { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + view: &mut V, + cx: &mut gpui::LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + todo!() + } + + fn paint( + &mut self, + scene: &mut gpui::SceneBuilder, + bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + view: &mut V, + cx: &mut gpui::ViewContext, + ) -> Self::PaintState { + todo!() + } + + fn rect_for_text_range( + &self, + range_utf16: std::ops::Range, + bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &Self::LayoutState, + paint: &Self::PaintState, + view: &V, + cx: &gpui::ViewContext, + ) -> Option { + todo!() + } + + fn debug( + &self, + bounds: gpui::geometry::rect::RectF, + layout: &Self::LayoutState, + paint: &Self::PaintState, + view: &V, + cx: &gpui::ViewContext, + ) -> serde_json::Value { + todo!() + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ebaf399e22..259e343248 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -408,6 +408,9 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + + + if let Some(active_entry) = self.visible_entry() { let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 5e5a5a98ba..8160a770a3 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -9,6 +9,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; +use itertools::Itertools; use project::Project; use serde::Deserialize; use theme::Theme; @@ -385,40 +386,61 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - let last_member_ix = self.members.len() - 1; - Flex::new(self.axis) - .with_children(self.members.iter().enumerate().map(|(ix, member)| { - let mut flex = 1.0; - if member.contains(active_pane) { - flex = settings::get::(cx).active_pane_magnification; + let mut flex_container = Flex::new(self.axis); + + let mut members = self.members.iter().enumerate().peekable(); + while let Some((ix, member)) = members.next() { + let last = members.peek().is_none(); + + let mut flex = 1.0; + if member.contains(active_pane) { + flex = settings::get::(cx).active_pane_magnification; + } + + let mut member = member.render( + project, + theme, + follower_state, + active_call, + active_pane, + zoomed, + app_state, + cx, + ); + if !last { + let mut border = theme.workspace.pane_divider; + border.left = false; + border.right = false; + border.top = false; + border.bottom = false; + + match self.axis { + Axis::Vertical => border.bottom = true, + Axis::Horizontal => border.right = true, } - let mut member = member.render( - project, - theme, - follower_state, - active_call, - active_pane, - zoomed, - app_state, - cx, - ); - if ix < last_member_ix { - let mut border = theme.workspace.pane_divider; - border.left = false; - border.right = false; - border.top = false; - border.bottom = false; - match self.axis { - Axis::Vertical => border.bottom = true, - Axis::Horizontal => border.right = true, - } - member = member.contained().with_border(border).into_any(); - } + let side = match self.axis { + Axis::Horizontal => HandleSide::Right, + Axis::Vertical => HandleSide::Bottom, + }; - FlexItem::new(member).flex(flex, true) - })) - .into_any() + member = member.contained().with_border(border) + .resizable(side, 1., |workspace, size, cx| { + dbg!("resize", size); + }) + .into_any(); + + + } + + flex_container = flex_container.with_child( + FlexItem::new(member) + .flex(flex, true) + .into_any() + ); + } + + flex_container.into_any() } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 01d80d141c..cafcd191a3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,13 +1,10 @@ pub mod dock; -/// NOTE: Focus only 'takes' after an update has flushed_effects. -/// -/// This may cause issues when you're trying to write tests that use workspace focus to add items at -/// specific locations. pub mod item; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; +mod adjustable_flex; pub mod searchable; pub mod shared_screen; mod status_bar; From 5385ca411bc3b6392df023562b3ec28e492f0368 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 12 Jul 2023 17:53:01 -0700 Subject: [PATCH 04/57] Added the new elements and wired through the pointers to update the pane axis ratios --- crates/workspace/src/adjustable_flex.rs | 83 ----- crates/workspace/src/pane_group.rs | 397 ++++++++++++++++++++-- crates/workspace/src/persistence/model.rs | 1 + crates/workspace/src/workspace.rs | 3 +- 4 files changed, 375 insertions(+), 109 deletions(-) delete mode 100644 crates/workspace/src/adjustable_flex.rs diff --git a/crates/workspace/src/adjustable_flex.rs b/crates/workspace/src/adjustable_flex.rs deleted file mode 100644 index 4ea1b719f1..0000000000 --- a/crates/workspace/src/adjustable_flex.rs +++ /dev/null @@ -1,83 +0,0 @@ -use gpui::{Element, View, Axis, AnyElement}; - -// Model for the center group: AdjustableGroup of AdjustableGroups -// Implementation notes -// - These have two representations: Exact pixel widths and ratios of elements compared to whole space -// - We have a constraint of minimum sizes for things. -// - If The space is smaller than allowed, things run off the edge -// - When doing Drag resize, we update the pixel width representation, causing a recalc of the ratios -// - If dragging past minimum, take space from next item, until out of space -// - When doing a reflow (e.g. layout) we read off the ratios and calculate pixels from that -// - When adding / removing items in an Adjustable flex, reset to default ratios (1:1) -// - By default, every item takes up as much space as possible -// - - -struct AdjustableFlex { - axis: Axis, - handle_size: f32, - items: Vec<(AnyElement, f32)> -} - -impl AdjustableFlex { - fn new(axis: Axis) -> Self { - AdjustableFlex { - axis, - handle_size: 2., - items: Vec::new(), - } - } - - fn add_item() -} - -impl Element for AdjustableFlex { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut gpui::LayoutContext, - ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - todo!() - } - - fn paint( - &mut self, - scene: &mut gpui::SceneBuilder, - bounds: gpui::geometry::rect::RectF, - visible_bounds: gpui::geometry::rect::RectF, - layout: &mut Self::LayoutState, - view: &mut V, - cx: &mut gpui::ViewContext, - ) -> Self::PaintState { - todo!() - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - bounds: gpui::geometry::rect::RectF, - visible_bounds: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - todo!() - } - - fn debug( - &self, - bounds: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> serde_json::Value { - todo!() - } -} diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 8160a770a3..7198dff3bf 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,6 @@ -use std::sync::Arc; +use std::{cell::RefCell, rc::Rc, sync::Arc}; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings}; +use crate::{AppState, FollowerStatesByLeader, Pane, Workspace}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -9,12 +9,13 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; -use itertools::Itertools; use project::Project; use serde::Deserialize; use theme::Theme; -#[derive(Clone, Debug, Eq, PartialEq)] +use self::adjustable_group::{AdjustableGroupElement, AdjustableGroupItem}; + +#[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, } @@ -78,6 +79,7 @@ impl PaneGroup { ) -> AnyElement { self.root.render( project, + 0, theme, follower_states, active_call, @@ -95,7 +97,7 @@ impl PaneGroup { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum Member { Axis(PaneAxis), Pane(ViewHandle), @@ -120,7 +122,11 @@ impl Member { Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], }; - Member::Axis(PaneAxis { axis, members }) + Member::Axis(PaneAxis { + axis, + members, + ratios: Default::default(), + }) } fn contains(&self, needle: &ViewHandle) -> bool { @@ -133,6 +139,7 @@ impl Member { pub fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -273,6 +280,7 @@ impl Member { } Member::Axis(axis) => axis.render( project, + basis + 1, theme, follower_states, active_call, @@ -296,10 +304,11 @@ impl Member { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, + pub ratios: Rc>>, } impl PaneAxis { @@ -378,6 +387,7 @@ impl PaneAxis { fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -386,19 +396,29 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - let mut flex_container = Flex::new(self.axis); + let ratios = self.ratios.clone(); + let mut flex_container = AdjustableGroupElement::new(self.axis, 2., basis, move |new_flexes| { + let mut borrow = ratios.borrow_mut(); + borrow.extend(new_flexes); + borrow.truncate(10); + dbg!(borrow); + }); + let next_basis = basis + self.members.len(); let mut members = self.members.iter().enumerate().peekable(); - while let Some((ix, member)) = members.next() { + while let Some((_ix, member)) = members.next() { let last = members.peek().is_none(); let mut flex = 1.0; - if member.contains(active_pane) { - flex = settings::get::(cx).active_pane_magnification; - } + // TODO: Include minimum sizes + // TODO: Restore this + // if member.contains(active_pane) { + // flex = settings::get::(cx).active_pane_magnification; + // } let mut member = member.render( project, + next_basis, theme, follower_state, active_call, @@ -424,20 +444,11 @@ impl PaneAxis { Axis::Vertical => HandleSide::Bottom, }; - member = member.contained().with_border(border) - .resizable(side, 1., |workspace, size, cx| { - dbg!("resize", size); - }) - .into_any(); - - + member = member.contained().with_border(border).into_any(); } - flex_container = flex_container.with_child( - FlexItem::new(member) - .flex(flex, true) - .into_any() - ); + flex_container = + flex_container.with_child(AdjustableGroupItem::new(member, flex).into_any()); } flex_container.into_any() @@ -496,3 +507,341 @@ impl SplitDirection { } } } + +mod adjustable_group { + + use std::{any::Any, ops::Range, rc::Rc}; + + use gpui::{ + color::Color, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::{self, ToJson}, + platform::{CursorStyle, MouseButton}, + AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, Quad, SceneBuilder, + SizeConstraint, Vector2FExt, View, ViewContext, + }; + use serde_json::Value; + + struct AdjustableFlexData { + flex: f32, + } + + pub struct AdjustableGroupElement { + axis: Axis, + handle_size: f32, + basis: usize, + callback: Rc)>, + children: Vec>, + } + + impl AdjustableGroupElement { + pub fn new( + axis: Axis, + handle_size: f32, + basis: usize, + callback: impl Fn(Vec) + 'static, + ) -> Self { + Self { + axis, + handle_size, + basis, + callback: Rc::new(callback), + children: Default::default(), + } + } + + fn layout_flex_children( + &mut self, + constraint: SizeConstraint, + remaining_space: &mut f32, + remaining_flex: &mut f32, + cross_axis_max: &mut f32, + view: &mut V, + cx: &mut LayoutContext, + ) { + let cross_axis = self.axis.invert(); + let last_ix = self.children.len() - 1; + for (ix, child) in self.children.iter_mut().enumerate() { + let flex = child.metadata::().unwrap().flex; + + let handle_size = if ix == last_ix { 0. } else { self.handle_size }; + + let child_size = if *remaining_flex == 0.0 { + *remaining_space + } else { + let space_per_flex = *remaining_space / *remaining_flex; + space_per_flex * flex + } - handle_size; + + let child_constraint = match self.axis { + Axis::Horizontal => SizeConstraint::new( + vec2f(child_size, constraint.min.y()), + vec2f(child_size, constraint.max.y()), + ), + Axis::Vertical => SizeConstraint::new( + vec2f(constraint.min.x(), child_size), + vec2f(constraint.max.x(), child_size), + ), + }; + let child_size = child.layout(child_constraint, view, cx); + *remaining_space -= child_size.along(self.axis) + handle_size; + *remaining_flex -= flex; + *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); + } + } + } + + impl Extend> for AdjustableGroupElement { + fn extend>>(&mut self, children: T) { + self.children.extend(children); + } + } + + impl Element for AdjustableGroupElement { + type LayoutState = f32; + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut V, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + let mut remaining_flex = 0.; + + let mut cross_axis_max: f32 = 0.0; + for child in &mut self.children { + let metadata = child.metadata::(); + let flex = metadata + .map(|metadata| metadata.flex) + .expect("All children of an adjustable flex must be AdjustableFlexItems"); + remaining_flex += flex; + } + + let mut remaining_space = constraint.max_along(self.axis); + + if remaining_space.is_infinite() { + panic!("flex contains flexible children but has an infinite constraint along the flex axis"); + } + + self.layout_flex_children( + constraint, + &mut remaining_space, + &mut remaining_flex, + &mut cross_axis_max, + view, + cx, + ); + + let mut size = match self.axis { + Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), + Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), + }; + + if constraint.min.x().is_finite() { + size.set_x(size.x().max(constraint.min.x())); + } + if constraint.min.y().is_finite() { + size.set_y(size.y().max(constraint.min.y())); + } + + if size.x() > constraint.max.x() { + size.set_x(constraint.max.x()); + } + if size.y() > constraint.max.y() { + size.set_y(constraint.max.y()); + } + + (size, remaining_space) + } + + fn paint( + &mut self, + scene: &mut SceneBuilder, + bounds: RectF, + visible_bounds: RectF, + remaining_space: &mut Self::LayoutState, + view: &mut V, + cx: &mut ViewContext, + ) -> Self::PaintState { + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let overflowing = *remaining_space < 0.; + if overflowing { + scene.push_layer(Some(visible_bounds)); + } + + let mut child_origin = bounds.origin(); + + let last_ix = self.children.len() - 1; + for (ix, child) in self.children.iter_mut().enumerate() { + child.paint(scene, child_origin, visible_bounds, view, cx); + + match self.axis { + Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), + Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), + } + + if ix != last_ix { + let bounds = match self.axis { + Axis::Horizontal => RectF::new( + child_origin, + vec2f(self.handle_size, visible_bounds.height()), + ), + Axis::Vertical => RectF::new( + child_origin, + vec2f(visible_bounds.width(), self.handle_size), + ), + }; + + scene.push_quad(Quad { + bounds, + background: Some(Color::red()), + ..Default::default() + }); + + let style = match self.axis { + Axis::Horizontal => CursorStyle::ResizeLeftRight, + Axis::Vertical => CursorStyle::ResizeUpDown, + }; + + scene.push_cursor_region(CursorRegion { bounds, style }); + + enum ResizeHandle {} + let callback = self.callback.clone(); + let axis = self.axis; + let mut mouse_region = + MouseRegion::new::(cx.view_id(), self.basis + ix, bounds); + mouse_region = + mouse_region.on_drag(MouseButton::Left, move |drag, v: &mut V, cx| { + dbg!(drag); + callback({ + match axis { + Axis::Horizontal => vec![0., 1., 2.], + Axis::Vertical => vec![3., 2., 1.], + } + }) + }); + scene.push_mouse_region(mouse_region); + + match self.axis { + Axis::Horizontal => child_origin += vec2f(self.handle_size, 0.0), + Axis::Vertical => child_origin += vec2f(0.0, self.handle_size), + } + } + } + + if overflowing { + scene.pop_layer(); + } + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Option { + self.children + .iter() + .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> json::Value { + serde_json::json!({ + "type": "Flex", + "bounds": bounds.to_json(), + "axis": self.axis.to_json(), + "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() + }) + } + } + + pub struct AdjustableGroupItem { + metadata: AdjustableFlexData, + child: AnyElement, + } + + impl AdjustableGroupItem { + pub fn new(child: impl Element, flex: f32) -> Self { + Self { + metadata: AdjustableFlexData { flex }, + child: child.into_any(), + } + } + } + + impl Element for AdjustableGroupItem { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut V, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + let size = self.child.layout(constraint, view, cx); + (size, ()) + } + + fn paint( + &mut self, + scene: &mut SceneBuilder, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + view: &mut V, + cx: &mut ViewContext, + ) -> Self::PaintState { + self.child + .paint(scene, bounds.origin(), visible_bounds, view, cx) + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, view, cx) + } + + fn metadata(&self) -> Option<&dyn Any> { + Some(&self.metadata) + } + + fn debug( + &self, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Value { + serde_json::json!({ + "type": "Flexible", + "flex": self.metadata.flex, + "child": self.child.debug(view, cx) + }) + } + } +} diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 1075061853..c159ff0f42 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -187,6 +187,7 @@ impl SerializedPaneGroup { Member::Axis(PaneAxis { axis: *axis, members, + ratios: Default::default() }), current_active_pane, items, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cafcd191a3..f91204c51a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4,7 +4,6 @@ pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; -mod adjustable_flex; pub mod searchable; pub mod shared_screen; mod status_bar; @@ -2924,7 +2923,7 @@ impl Workspace { cx: &AppContext, ) -> SerializedPaneGroup { match pane_group { - Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group { + Member::Axis(PaneAxis { axis, members, .. }) => SerializedPaneGroup::Group { axis: *axis, children: members .iter() From 26b9be628ebaf238c683bb925ca155518126e5ca Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 12 Jul 2023 22:34:33 -0700 Subject: [PATCH 05/57] Add the math for pane resizing --- crates/gpui/src/app/window.rs | 13 +++ crates/gpui/src/gpui.rs | 2 +- crates/workspace/src/dock.rs | 3 - crates/workspace/src/pane_group.rs | 120 ++++++++++++++++------ crates/workspace/src/persistence/model.rs | 6 +- 5 files changed, 103 insertions(+), 41 deletions(-) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 58d7bb4c40..49b12d823e 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1268,6 +1268,19 @@ impl Vector2FExt for Vector2F { } } +pub trait RectFExt { + fn length_along(self, axis: Axis) -> f32; +} + +impl RectFExt for RectF { + fn length_along(self, axis: Axis) -> f32 { + match axis { + Axis::Horizontal => self.width(), + Axis::Vertical => self.height(), + } + } +} + #[derive(Copy, Clone, Debug)] pub struct SizeConstraint { pub min: Vector2F, diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 3442934b3a..c79c793dda 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -27,7 +27,7 @@ pub mod json; pub mod keymap_matcher; pub mod platform; pub use gpui_macros::{test, Element}; -pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext}; +pub use window::{Axis, RectFExt, SizeConstraint, Vector2FExt, WindowContext}; pub use anyhow; pub use serde_json; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 259e343248..ebaf399e22 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -408,9 +408,6 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - - - if let Some(active_entry) = self.visible_entry() { let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 7198dff3bf..fdda67ad22 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -308,10 +308,19 @@ impl Member { pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, - pub ratios: Rc>>, + ratios: Rc>>, } impl PaneAxis { + pub fn new(axis: Axis, members: Vec) -> Self { + let ratios = Rc::new(RefCell::new(vec![1.; members.len()])); + Self { + axis, + members, + ratios, + } + } + fn split( &mut self, old_pane: &ViewHandle, @@ -397,20 +406,24 @@ impl PaneAxis { cx: &mut ViewContext, ) -> AnyElement { let ratios = self.ratios.clone(); - let mut flex_container = AdjustableGroupElement::new(self.axis, 2., basis, move |new_flexes| { - let mut borrow = ratios.borrow_mut(); - borrow.extend(new_flexes); - borrow.truncate(10); - dbg!(borrow); - }); + let mut flex_container = + AdjustableGroupElement::new(self.axis, 2., basis, move |new_flexes, _, cx| { + let mut borrow = ratios.borrow_mut(); + for (ix, flex) in new_flexes { + if let Some(el) = borrow.get_mut(ix) { + *el = flex; + } + } + cx.notify(); + }); + + let ratios_borrow = self.ratios.borrow(); let next_basis = basis + self.members.len(); - let mut members = self.members.iter().enumerate().peekable(); - while let Some((_ix, member)) = members.next() { + let mut members = self.members.iter().zip(ratios_borrow.iter()).peekable(); + while let Some((member, flex)) = members.next() { let last = members.peek().is_none(); - let mut flex = 1.0; - // TODO: Include minimum sizes // TODO: Restore this // if member.contains(active_pane) { // flex = settings::get::(cx).active_pane_magnification; @@ -439,16 +452,11 @@ impl PaneAxis { Axis::Horizontal => border.right = true, } - let side = match self.axis { - Axis::Horizontal => HandleSide::Right, - Axis::Vertical => HandleSide::Bottom, - }; - member = member.contained().with_border(border).into_any(); } flex_container = - flex_container.with_child(AdjustableGroupItem::new(member, flex).into_any()); + flex_container.with_child(AdjustableGroupItem::new(member, *flex).into_any()); } flex_container.into_any() @@ -520,10 +528,11 @@ mod adjustable_group { }, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, Quad, SceneBuilder, - SizeConstraint, Vector2FExt, View, ViewContext, + AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, Quad, + RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, }; use serde_json::Value; + use smallvec::SmallVec; struct AdjustableFlexData { flex: f32, @@ -533,7 +542,7 @@ mod adjustable_group { axis: Axis, handle_size: f32, basis: usize, - callback: Rc)>, + callback: Rc, &mut V, &mut EventContext)>, children: Vec>, } @@ -542,7 +551,7 @@ mod adjustable_group { axis: Axis, handle_size: f32, basis: usize, - callback: impl Fn(Vec) + 'static, + callback: impl Fn(SmallVec<[(usize, f32); 2]>, &mut V, &mut EventContext) + 'static, ) -> Self { Self { axis, @@ -676,8 +685,9 @@ mod adjustable_group { let mut child_origin = bounds.origin(); - let last_ix = self.children.len() - 1; - for (ix, child) in self.children.iter_mut().enumerate() { + 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); match self.axis { @@ -685,7 +695,7 @@ mod adjustable_group { Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } - if ix != last_ix { + if let Some((next_ix, next_child)) = children_iter.peek() { let bounds = match self.axis { Axis::Horizontal => RectF::new( child_origin, @@ -710,20 +720,66 @@ mod adjustable_group { scene.push_cursor_region(CursorRegion { bounds, style }); - enum ResizeHandle {} let callback = self.callback.clone(); let axis = self.axis; + let child_size = child.size(); + let next_child_size = next_child.size(); + let mut drag_bounds = visible_bounds.clone(); + // Unsure why this should be needed.... + drag_bounds.set_origin_y(0.); + let current_flex = child.metadata::().unwrap().flex; + let next_flex = next_child.metadata::().unwrap().flex; + let next_ix = *next_ix; + const HORIZONTAL_MIN_SIZE: f32 = 80.; + const VERTICAL_MIN_SIZE: f32 = 100.; + enum ResizeHandle {} let mut mouse_region = MouseRegion::new::(cx.view_id(), self.basis + ix, bounds); mouse_region = mouse_region.on_drag(MouseButton::Left, move |drag, v: &mut V, cx| { - dbg!(drag); - callback({ - match axis { - Axis::Horizontal => vec![0., 1., 2.], - Axis::Vertical => vec![3., 2., 1.], - } - }) + 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 flex_position = drag.position - drag_bounds.origin(); + let mut current_target_size = (flex_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 = current_target_size.max(min_size); + } else if proposed_current_pixel_change > 0. { + // TODO: cascade this size change down, collect into a vec + let next_target_size = (next_child_size.along(axis) + - proposed_current_pixel_change) + .max(min_size); + current_target_size = current_target_size.min( + 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; + + callback( + smallvec::smallvec![ + (ix, current_target_flex), + (next_ix, next_target_flex), + ], + v, + cx, + ) }); scene.push_mouse_region(mouse_region); diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index c159ff0f42..762d7171de 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -184,11 +184,7 @@ impl SerializedPaneGroup { } Some(( - Member::Axis(PaneAxis { - axis: *axis, - members, - ratios: Default::default() - }), + Member::Axis(PaneAxis::new(*axis, members)), current_active_pane, items, )) From 8cce403c112d43a6380f3419b38f7b9245d967e3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 13 Jul 2023 11:52:35 +0300 Subject: [PATCH 06/57] Update another deprecated plugin --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe89801f04..eb9b6d1f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,8 +148,8 @@ jobs: - name: Create app bundle run: script/bundle - - name: Upload app bundle to workflow run if main branch or specifi label - uses: actions/upload-artifact@v2 + - name: Upload app bundle to workflow run if main branch or specific label + uses: actions/upload-artifact@v3 if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} with: name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg 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 07/57] 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 666a49be2a..fcb087c47d 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 57ea2ae334..4934bc494d 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 d5f7ad08fa48cd4997de64b3f98522970a2b9f9a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 11:28:21 -0700 Subject: [PATCH 08/57] Styled and refined behavior for split resizing --- crates/editor/src/element.rs | 6 +- crates/workspace/src/pane_group.rs | 259 +++++++++-------------------- 2 files changed, 86 insertions(+), 179 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f0bae9533b..074a96dfc1 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1182,8 +1182,10 @@ impl EditorElement { }); scene.push_mouse_region( MouseRegion::new::(cx.view_id(), cx.view_id(), track_bounds) - .on_move(move |_, editor: &mut Editor, cx| { - editor.scroll_manager.show_scrollbar(cx); + .on_move(move |event, editor: &mut Editor, cx| { + if event.pressed_button.is_none() { + editor.scroll_manager.show_scrollbar(cx); + } }) .on_down(MouseButton::Left, { let row_range = row_range.clone(); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index fdda67ad22..372bfd8ef4 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,8 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace}; +use crate::{ + pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace, +}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -13,8 +15,6 @@ use project::Project; use serde::Deserialize; use theme::Theme; -use self::adjustable_group::{AdjustableGroupElement, AdjustableGroupItem}; - #[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, @@ -122,11 +122,7 @@ impl Member { Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], }; - Member::Axis(PaneAxis { - axis, - members, - ratios: Default::default(), - }) + Member::Axis(PaneAxis::new(axis, members)) } fn contains(&self, needle: &ViewHandle) -> bool { @@ -308,16 +304,16 @@ impl Member { pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, - ratios: Rc>>, + flexes: Rc>>, } impl PaneAxis { pub fn new(axis: Axis, members: Vec) -> Self { - let ratios = Rc::new(RefCell::new(vec![1.; members.len()])); + let flexes = Rc::new(RefCell::new(vec![1.; members.len()])); Self { axis, members, - ratios, + flexes, } } @@ -342,6 +338,7 @@ impl PaneAxis { } self.members.insert(idx, Member::Pane(new_pane.clone())); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; } else { *member = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); @@ -381,6 +378,7 @@ impl PaneAxis { if found_pane { if let Some(idx) = remove_member { self.members.remove(idx); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; } if self.members.len() == 1 { @@ -405,23 +403,17 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - let ratios = self.ratios.clone(); - let mut flex_container = - AdjustableGroupElement::new(self.axis, 2., basis, move |new_flexes, _, cx| { - let mut borrow = ratios.borrow_mut(); - for (ix, flex) in new_flexes { - if let Some(el) = borrow.get_mut(ix) { - *el = flex; - } - } + debug_assert!(self.members.len() == self.flexes.borrow().len()); - cx.notify(); - }); + // TODO: SImplify further by just passing in the flexes pointer directly, no need to generify! + let mut flex_container = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); - let ratios_borrow = self.ratios.borrow(); - let next_basis = basis + self.members.len(); - let mut members = self.members.iter().zip(ratios_borrow.iter()).peekable(); - while let Some((member, flex)) = members.next() { + let mut members = self + .members + .iter() + .enumerate() + .peekable(); + while let Some((ix, member)) = members.next() { let last = members.peek().is_none(); // TODO: Restore this @@ -431,7 +423,7 @@ impl PaneAxis { let mut member = member.render( project, - next_basis, + (basis + ix) * 10, theme, follower_state, active_call, @@ -440,6 +432,7 @@ impl PaneAxis { app_state, cx, ); + if !last { let mut border = theme.workspace.pane_divider; border.left = false; @@ -455,8 +448,7 @@ impl PaneAxis { member = member.contained().with_border(border).into_any(); } - flex_container = - flex_container.with_child(AdjustableGroupItem::new(member, *flex).into_any()); + flex_container = flex_container.with_child(member.into_any()); } flex_container.into_any() @@ -516,48 +508,34 @@ impl SplitDirection { } } -mod adjustable_group { - - use std::{any::Any, ops::Range, rc::Rc}; +// TODO: PaneAxis element here +mod element { + use std::{cell::RefCell, ops::Range, rc::Rc}; use gpui::{ - color::Color, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, Quad, - RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, + AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, + SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, }; - use serde_json::Value; - use smallvec::SmallVec; - struct AdjustableFlexData { - flex: f32, - } - - pub struct AdjustableGroupElement { + pub struct PaneAxisElement { axis: Axis, - handle_size: f32, basis: usize, - callback: Rc, &mut V, &mut EventContext)>, + flexes: Rc>>, children: Vec>, } - impl AdjustableGroupElement { - pub fn new( - axis: Axis, - handle_size: f32, - basis: usize, - callback: impl Fn(SmallVec<[(usize, f32); 2]>, &mut V, &mut EventContext) + 'static, - ) -> Self { + impl PaneAxisElement { + pub fn new(axis: Axis, basis: usize, flexes: Rc>>) -> Self { Self { axis, - handle_size, basis, - callback: Rc::new(callback), + flexes, children: Default::default(), } } @@ -571,19 +549,17 @@ mod adjustable_group { view: &mut V, cx: &mut LayoutContext, ) { + let flexes = self.flexes.borrow(); let cross_axis = self.axis.invert(); - let last_ix = self.children.len() - 1; for (ix, child) in self.children.iter_mut().enumerate() { - let flex = child.metadata::().unwrap().flex; - - let handle_size = if ix == last_ix { 0. } else { self.handle_size }; + let flex = flexes[ix]; let child_size = if *remaining_flex == 0.0 { *remaining_space } else { let space_per_flex = *remaining_space / *remaining_flex; space_per_flex * flex - } - handle_size; + }; let child_constraint = match self.axis { Axis::Horizontal => SizeConstraint::new( @@ -596,20 +572,20 @@ mod adjustable_group { ), }; let child_size = child.layout(child_constraint, view, cx); - *remaining_space -= child_size.along(self.axis) + handle_size; + *remaining_space -= child_size.along(self.axis); *remaining_flex -= flex; *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); } } } - impl Extend> for AdjustableGroupElement { + impl Extend> for PaneAxisElement { fn extend>>(&mut self, children: T) { self.children.extend(children); } } - impl Element for AdjustableGroupElement { + impl Element for PaneAxisElement { type LayoutState = f32; type PaintState = (); @@ -619,14 +595,11 @@ mod adjustable_group { view: &mut V, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { + debug_assert!(self.children.len() == self.flexes.borrow().len()); let mut remaining_flex = 0.; let mut cross_axis_max: f32 = 0.0; - for child in &mut self.children { - let metadata = child.metadata::(); - let flex = metadata - .map(|metadata| metadata.flex) - .expect("All children of an adjustable flex must be AdjustableFlexItems"); + for flex in self.flexes.borrow().iter() { remaining_flex += flex; } @@ -695,48 +668,61 @@ mod adjustable_group { Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } + const HANDLE_HITBOX_SIZE: f32 = 4.0; if let Some((next_ix, next_child)) = children_iter.peek() { - let bounds = match self.axis { + scene.push_stacking_context(None, None); + + let handle_origin = match self.axis { + Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), + Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), + }; + + let handle_bounds = match self.axis { Axis::Horizontal => RectF::new( - child_origin, - vec2f(self.handle_size, visible_bounds.height()), + handle_origin, + vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), ), Axis::Vertical => RectF::new( - child_origin, - vec2f(visible_bounds.width(), self.handle_size), + handle_origin, + vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), ), }; - scene.push_quad(Quad { - bounds, - background: Some(Color::red()), - ..Default::default() - }); + // use gpui::color::Color, + // scene.push_quad(Quad { + // bounds: handle_bounds, + // background: Some(Color::red()), + // ..Default::default() + // }); let style = match self.axis { Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Vertical => CursorStyle::ResizeUpDown, }; - scene.push_cursor_region(CursorRegion { bounds, style }); + scene.push_cursor_region(CursorRegion { + bounds: handle_bounds, + style, + }); - let callback = self.callback.clone(); let axis = self.axis; let child_size = child.size(); let next_child_size = next_child.size(); - let mut drag_bounds = visible_bounds.clone(); - // Unsure why this should be needed.... - drag_bounds.set_origin_y(0.); - let current_flex = child.metadata::().unwrap().flex; - let next_flex = next_child.metadata::().unwrap().flex; + let drag_bounds = visible_bounds.clone(); + let flexes = self.flexes.clone(); + let current_flex = flexes.borrow()[ix]; let next_ix = *next_ix; + let next_flex = flexes.borrow()[next_ix]; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; enum ResizeHandle {} - let mut mouse_region = - MouseRegion::new::(cx.view_id(), self.basis + ix, bounds); + let mut mouse_region = MouseRegion::new::( + cx.view_id(), + self.basis + ix, + handle_bounds, + ); mouse_region = - mouse_region.on_drag(MouseButton::Left, move |drag, v: &mut V, cx| { + mouse_region.on_drag(MouseButton::Left, move |drag, _: &mut V, cx| { let min_size = match axis { Axis::Horizontal => HORIZONTAL_MIN_SIZE, Axis::Vertical => VERTICAL_MIN_SIZE, @@ -748,15 +734,15 @@ mod adjustable_group { return; } - let flex_position = drag.position - drag_bounds.origin(); - let mut current_target_size = (flex_position - child_start).along(axis); + 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 = current_target_size.max(min_size); } else if proposed_current_pixel_change > 0. { - // TODO: cascade this size change down, collect into a vec + // TODO: cascade this size change down, collect all changes into a vec let next_target_size = (next_child_size.along(axis) - proposed_current_pixel_change) .max(min_size); @@ -768,25 +754,18 @@ mod adjustable_group { 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; - callback( - smallvec::smallvec![ - (ix, current_target_flex), - (next_ix, next_target_flex), - ], - v, - cx, - ) + let mut borrow = flexes.borrow_mut(); + *borrow.get_mut(ix).unwrap() = current_target_flex; + *borrow.get_mut(next_ix).unwrap() = next_target_flex; + + cx.notify(); }); scene.push_mouse_region(mouse_region); - match self.axis { - Axis::Horizontal => child_origin += vec2f(self.handle_size, 0.0), - Axis::Vertical => child_origin += vec2f(0.0, self.handle_size), - } + scene.pop_stacking_context(); } } @@ -819,85 +798,11 @@ mod adjustable_group { cx: &ViewContext, ) -> json::Value { serde_json::json!({ - "type": "Flex", + "type": "PaneAxis", "bounds": bounds.to_json(), "axis": self.axis.to_json(), "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() }) } } - - pub struct AdjustableGroupItem { - metadata: AdjustableFlexData, - child: AnyElement, - } - - impl AdjustableGroupItem { - pub fn new(child: impl Element, flex: f32) -> Self { - Self { - metadata: AdjustableFlexData { flex }, - child: child.into_any(), - } - } - } - - impl Element for AdjustableGroupItem { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut LayoutContext, - ) -> (Vector2F, Self::LayoutState) { - let size = self.child.layout(constraint, view, cx); - (size, ()) - } - - fn paint( - &mut self, - scene: &mut SceneBuilder, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState { - self.child - .paint(scene, bounds.origin(), visible_bounds, view, cx) - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn metadata(&self) -> Option<&dyn Any> { - Some(&self.metadata) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Value { - serde_json::json!({ - "type": "Flexible", - "flex": self.metadata.flex, - "child": self.child.debug(view, cx) - }) - } - } } From 00b04f1c85301c17592deed99b08e539106931e4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 13:10:36 -0700 Subject: [PATCH 09/57] Restore active pane magnification --- crates/workspace/src/pane_group.rs | 80 ++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 372bfd8ef4..3e4ce21694 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -15,6 +15,10 @@ use project::Project; use serde::Deserialize; use theme::Theme; +const HANDLE_HITBOX_SIZE: f32 = 4.0; +const HORIZONTAL_MIN_SIZE: f32 = 80.; +const VERTICAL_MIN_SIZE: f32 = 100.; + #[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, @@ -405,21 +409,17 @@ impl PaneAxis { ) -> AnyElement { debug_assert!(self.members.len() == self.flexes.borrow().len()); - // TODO: SImplify further by just passing in the flexes pointer directly, no need to generify! let mut flex_container = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); + let mut active_pane_ix = None; - let mut members = self - .members - .iter() - .enumerate() - .peekable(); + let mut members = self.members.iter().enumerate().peekable(); while let Some((ix, member)) = members.next() { let last = members.peek().is_none(); // TODO: Restore this - // if member.contains(active_pane) { - // flex = settings::get::(cx).active_pane_magnification; - // } + if member.contains(active_pane) { + active_pane_ix = Some(ix); + } let mut member = member.render( project, @@ -450,7 +450,7 @@ impl PaneAxis { flex_container = flex_container.with_child(member.into_any()); } - + flex_container.set_active_pane(active_pane_ix); flex_container.into_any() } } @@ -508,7 +508,6 @@ impl SplitDirection { } } -// TODO: PaneAxis element here mod element { use std::{cell::RefCell, ops::Range, rc::Rc}; @@ -523,9 +522,15 @@ mod element { SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, }; + use crate::{ + pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, + WorkspaceSettings, + }; + pub struct PaneAxisElement { axis: Axis, basis: usize, + active_pane_ix: Option, flexes: Rc>>, children: Vec>, } @@ -536,12 +541,18 @@ mod element { axis, basis, flexes, + active_pane_ix: None, children: Default::default(), } } + pub fn set_active_pane(&mut self, active_pane_ix: Option) { + self.active_pane_ix = active_pane_ix; + } + fn layout_flex_children( &mut self, + active_pane_magnification: f32, constraint: SizeConstraint, remaining_space: &mut f32, remaining_flex: &mut f32, @@ -552,7 +563,19 @@ mod element { let flexes = self.flexes.borrow(); let cross_axis = self.axis.invert(); for (ix, child) in self.children.iter_mut().enumerate() { - let flex = flexes[ix]; + let flex = if active_pane_magnification != 1. { + if let Some(active_pane_ix) = self.active_pane_ix { + if ix == active_pane_ix { + active_pane_magnification + } else { + 1. + } + } else { + 1. + } + } else { + flexes[ix] + }; let child_size = if *remaining_flex == 0.0 { *remaining_space @@ -596,13 +619,25 @@ mod element { cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(self.children.len() == self.flexes.borrow().len()); + + let active_pane_magnification = + settings::get::(cx).active_pane_magnification; + let mut remaining_flex = 0.; - let mut cross_axis_max: f32 = 0.0; - for flex in self.flexes.borrow().iter() { - remaining_flex += flex; + if active_pane_magnification != 1. { + let active_pane_flex = self + .active_pane_ix + .map(|_| active_pane_magnification) + .unwrap_or(1.); + remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; + } else { + for flex in self.flexes.borrow().iter() { + remaining_flex += flex; + } } + let mut cross_axis_max: f32 = 0.0; let mut remaining_space = constraint.max_along(self.axis); if remaining_space.is_infinite() { @@ -610,6 +645,7 @@ mod element { } self.layout_flex_children( + active_pane_magnification, constraint, &mut remaining_space, &mut remaining_flex, @@ -649,6 +685,7 @@ mod element { view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { + let can_resize = settings::get::(cx).active_pane_magnification == 1.; let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let overflowing = *remaining_space < 0.; @@ -668,8 +705,8 @@ mod element { Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } - const HANDLE_HITBOX_SIZE: f32 = 4.0; - if let Some((next_ix, next_child)) = children_iter.peek() { + if let Some(Some((next_ix, next_child))) = can_resize.then(|| children_iter.peek()) + { scene.push_stacking_context(None, None); let handle_origin = match self.axis { @@ -688,13 +725,6 @@ mod element { ), }; - // use gpui::color::Color, - // scene.push_quad(Quad { - // bounds: handle_bounds, - // background: Some(Color::red()), - // ..Default::default() - // }); - let style = match self.axis { Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Vertical => CursorStyle::ResizeUpDown, @@ -713,8 +743,6 @@ mod element { let current_flex = flexes.borrow()[ix]; let next_ix = *next_ix; let next_flex = flexes.borrow()[next_ix]; - const HORIZONTAL_MIN_SIZE: f32 = 80.; - const VERTICAL_MIN_SIZE: f32 = 100.; enum ResizeHandle {} let mut mouse_region = MouseRegion::new::( cx.view_id(), From 5797282b981ddc8b527f03ef70793d1b6a534457 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 14:21:14 -0700 Subject: [PATCH 10/57] Add resising serialization --- crates/workspace/src/pane_group.rs | 67 ++++++---- crates/workspace/src/persistence.rs | 152 ++++++++++++++-------- crates/workspace/src/persistence/model.rs | 9 +- crates/workspace/src/workspace.rs | 16 ++- 4 files changed, 159 insertions(+), 85 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 3e4ce21694..e58b95d6b3 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -308,7 +308,7 @@ impl Member { pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, - flexes: Rc>>, + pub flexes: Rc>>, } impl PaneAxis { @@ -321,6 +321,18 @@ impl PaneAxis { } } + pub fn load(axis: Axis, members: Vec, flexes: Option>) -> Self { + let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); + debug_assert!(members.len() == flexes.len()); + + let flexes = Rc::new(RefCell::new(flexes)); + Self { + axis, + members, + flexes, + } + } + fn split( &mut self, old_pane: &ViewHandle, @@ -519,23 +531,23 @@ mod element { json::{self, ToJson}, platform::{CursorStyle, MouseButton}, AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, - SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, + SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, }; use crate::{ pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, - WorkspaceSettings, + WorkspaceSettings, Workspace, }; - pub struct PaneAxisElement { + pub struct PaneAxisElement { axis: Axis, basis: usize, active_pane_ix: Option, flexes: Rc>>, - children: Vec>, + children: Vec>, } - impl PaneAxisElement { + impl PaneAxisElement { pub fn new(axis: Axis, basis: usize, flexes: Rc>>) -> Self { Self { axis, @@ -557,8 +569,8 @@ mod element { remaining_space: &mut f32, remaining_flex: &mut f32, cross_axis_max: &mut f32, - view: &mut V, - cx: &mut LayoutContext, + view: &mut Workspace, + cx: &mut LayoutContext, ) { let flexes = self.flexes.borrow(); let cross_axis = self.axis.invert(); @@ -602,21 +614,21 @@ mod element { } } - impl Extend> for PaneAxisElement { - fn extend>>(&mut self, children: T) { + impl Extend> for PaneAxisElement { + fn extend>>(&mut self, children: T) { self.children.extend(children); } } - impl Element for PaneAxisElement { + impl Element for PaneAxisElement { type LayoutState = f32; type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, - view: &mut V, - cx: &mut LayoutContext, + view: &mut Workspace, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(self.children.len() == self.flexes.borrow().len()); @@ -682,8 +694,8 @@ mod element { bounds: RectF, visible_bounds: RectF, remaining_space: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, + view: &mut Workspace, + cx: &mut ViewContext, ) -> Self::PaintState { let can_resize = settings::get::(cx).active_pane_magnification == 1.; let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -750,7 +762,7 @@ mod element { handle_bounds, ); mouse_region = - mouse_region.on_drag(MouseButton::Left, move |drag, _: &mut V, cx| { + mouse_region.on_drag(MouseButton::Left, move |drag, workspace: &mut Workspace, cx| { let min_size = match axis { Axis::Horizontal => HORIZONTAL_MIN_SIZE, Axis::Vertical => VERTICAL_MIN_SIZE, @@ -768,13 +780,15 @@ mod element { current_target_size - child_size.along(axis); if proposed_current_pixel_change < 0. { - current_target_size = current_target_size.max(min_size); + current_target_size = f32::max(current_target_size, min_size); } else if proposed_current_pixel_change > 0. { - // TODO: cascade this size change down, collect all changes into a vec - let next_target_size = (next_child_size.along(axis) - - proposed_current_pixel_change) - .max(min_size); - current_target_size = current_target_size.min( + // 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, ); @@ -789,6 +803,7 @@ mod element { *borrow.get_mut(ix).unwrap() = current_target_flex; *borrow.get_mut(next_ix).unwrap() = next_target_flex; + workspace.schedule_serialize(cx); cx.notify(); }); scene.push_mouse_region(mouse_region); @@ -809,8 +824,8 @@ mod element { _: RectF, _: &Self::LayoutState, _: &Self::PaintState, - view: &V, - cx: &ViewContext, + view: &Workspace, + cx: &ViewContext, ) -> Option { self.children .iter() @@ -822,8 +837,8 @@ mod element { bounds: RectF, _: &Self::LayoutState, _: &Self::PaintState, - view: &V, - cx: &ViewContext, + view: &Workspace, + cx: &ViewContext, ) -> json::Value { serde_json::json!({ "type": "PaneAxis", diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index dd2aa5a818..0d7784093a 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -45,6 +45,7 @@ define_connection! { // parent_group_id: Option, // None indicates that this is the root node // position: Optiopn, // None indicates that this is the root node // axis: Option, // 'Vertical', 'Horizontal' + // flexes: Option>, // A JSON array of floats // ) // // panes( @@ -168,7 +169,12 @@ define_connection! { ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool - )]; + ), + // Add pane group flex data + sql!( + ALTER TABLE pane_groups ADD COLUMN flexes TEXT; + ) + ]; } impl WorkspaceDb { @@ -359,38 +365,51 @@ impl WorkspaceDb { group_id: Option, ) -> Result> { type GroupKey = (Option, WorkspaceId); - type GroupOrPane = (Option, Option, Option, Option); + type GroupOrPane = ( + Option, + Option, + Option, + Option, + Option, + ); self.select_bound::(sql!( - SELECT group_id, axis, pane_id, active + SELECT group_id, axis, pane_id, active, flexes FROM (SELECT - group_id, - axis, - NULL as pane_id, - NULL as active, - position, - parent_group_id, - workspace_id - FROM pane_groups + group_id, + axis, + NULL as pane_id, + NULL as active, + position, + parent_group_id, + workspace_id, + flexes + FROM pane_groups UNION - SELECT - NULL, - NULL, - center_panes.pane_id, - panes.active as active, - position, - parent_group_id, - panes.workspace_id as workspace_id - FROM center_panes - JOIN panes ON center_panes.pane_id = panes.pane_id) + SELECT + NULL, + NULL, + center_panes.pane_id, + panes.active as active, + position, + parent_group_id, + panes.workspace_id as workspace_id, + NULL + FROM center_panes + JOIN panes ON center_panes.pane_id = panes.pane_id) WHERE parent_group_id IS ? AND workspace_id = ? ORDER BY position ))?((group_id, workspace_id))? .into_iter() - .map(|(group_id, axis, pane_id, active)| { + .map(|(group_id, axis, pane_id, active, flexes)| { if let Some((group_id, axis)) = group_id.zip(axis) { + let flexes = flexes + .map(|flexes| serde_json::from_str::>(&flexes)) + .transpose()?; + Ok(SerializedPaneGroup::Group { axis, children: self.get_pane_group(workspace_id, Some(group_id))?, + flexes, }) } else if let Some((pane_id, active)) = pane_id.zip(active) { Ok(SerializedPaneGroup::Pane(SerializedPane::new( @@ -417,14 +436,31 @@ impl WorkspaceDb { parent: Option<(GroupId, usize)>, ) -> Result<()> { match pane_group { - SerializedPaneGroup::Group { axis, children } => { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { let (parent_id, position) = unzip_option(parent); + let flex_string = serde_json::json!(flexes).to_string(); let group_id = conn.select_row_bound::<_, i64>(sql!( - INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) - VALUES (?, ?, ?, ?) + INSERT INTO pane_groups( + workspace_id, + parent_group_id, + position, + axis, + flexes + ) + VALUES (?, ?, ?, ?, ?) RETURNING group_id - ))?((workspace_id, parent_id, position, *axis))? + ))?(( + workspace_id, + parent_id, + position, + *axis, + flex_string, + ))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; for (position, group) in children.iter().enumerate() { @@ -641,6 +677,14 @@ mod tests { assert_eq!(test_text_1, "test-text-1"); } + fn group(axis: gpui::Axis, children: Vec) -> SerializedPaneGroup { + SerializedPaneGroup::Group { + axis, + flexes: None, + children, + } + } + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); @@ -652,12 +696,12 @@ mod tests { // | - - - | | // | 3,4 | | // ----------------- - let center_group = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_group = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, false), @@ -673,7 +717,7 @@ mod tests { false, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 9, false), @@ -682,7 +726,7 @@ mod tests { false, )), ], - }; + ); let workspace = SerializedWorkspace { id: 5, @@ -811,12 +855,12 @@ mod tests { // | - - - | | // | 3,4 | | // ----------------- - let center_pane = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -832,7 +876,7 @@ mod tests { true, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, true), @@ -841,7 +885,7 @@ mod tests { false, )), ], - }; + ); let workspace = default_workspace(&["/tmp"], ¢er_pane); @@ -858,12 +902,12 @@ mod tests { let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - let center_pane = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -879,7 +923,7 @@ mod tests { true, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, false), @@ -888,7 +932,7 @@ mod tests { false, )), ], - }; + ); let id = &["/tmp"]; @@ -896,9 +940,9 @@ mod tests { db.save_workspace(workspace.clone()).await; - workspace.center_group = SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + workspace.center_group = group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -914,7 +958,7 @@ mod tests { true, )), ], - }; + ); db.save_workspace(workspace.clone()).await; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 762d7171de..aa184dbb56 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -127,10 +127,11 @@ impl Bind for DockData { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum SerializedPaneGroup { Group { axis: Axis, + flexes: Option>, children: Vec, }, Pane(SerializedPane), @@ -149,7 +150,7 @@ impl Default for SerializedPaneGroup { impl SerializedPaneGroup { #[async_recursion(?Send)] pub(crate) async fn deserialize( - &self, + self, project: &ModelHandle, workspace_id: WorkspaceId, workspace: &WeakViewHandle, @@ -160,7 +161,7 @@ impl SerializedPaneGroup { Vec>>, )> { match self { - SerializedPaneGroup::Group { axis, children } => { + SerializedPaneGroup::Group { axis, children, flexes } => { let mut current_active_pane = None; let mut members = Vec::new(); let mut items = Vec::new(); @@ -184,7 +185,7 @@ impl SerializedPaneGroup { } Some(( - Member::Axis(PaneAxis::new(*axis, members)), + Member::Axis(PaneAxis::load(axis, members, flexes)), current_active_pane, items, )) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f91204c51a..e31e0d924f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -504,6 +504,7 @@ pub struct Workspace { subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, + _schedule_serialize: Option>, pane_history_timestamp: Arc, } @@ -718,6 +719,7 @@ impl Workspace { app_state, _observe_current_user, _apply_leader_updates, + _schedule_serialize: None, leader_updates_tx, subscriptions, pane_history_timestamp, @@ -2893,6 +2895,13 @@ impl Workspace { cx.notify(); } + fn schedule_serialize(&mut self, cx: &mut ViewContext) { + self._schedule_serialize = Some(cx.spawn(|this, cx| async move { + cx.background().timer(Duration::from_millis(100)).await; + this.read_with(&cx, |this, cx| this.serialize_workspace(cx)).ok(); + })); + } + fn serialize_workspace(&self, cx: &ViewContext) { fn serialize_pane_handle( pane_handle: &ViewHandle, @@ -2923,12 +2932,17 @@ impl Workspace { cx: &AppContext, ) -> SerializedPaneGroup { match pane_group { - Member::Axis(PaneAxis { axis, members, .. }) => SerializedPaneGroup::Group { + Member::Axis(PaneAxis { + axis, + members, + flexes, + }) => SerializedPaneGroup::Group { axis: *axis, children: members .iter() .map(|member| build_serialized_pane_group(member, cx)) .collect::>(), + flexes: Some(flexes.borrow().clone()), }, Member::Pane(pane_handle) => { SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) From 331fd896b56e90f27c7c3296633303b0ba3685fa Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 14:21:30 -0700 Subject: [PATCH 11/57] fmt --- crates/workspace/src/pane_group.rs | 12 +++++++----- crates/workspace/src/persistence/model.rs | 6 +++++- crates/workspace/src/workspace.rs | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e58b95d6b3..1edee1bc42 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -536,7 +536,7 @@ mod element { use crate::{ pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, - WorkspaceSettings, Workspace, + Workspace, WorkspaceSettings, }; pub struct PaneAxisElement { @@ -547,7 +547,7 @@ mod element { children: Vec>, } - impl PaneAxisElement { + impl PaneAxisElement { pub fn new(axis: Axis, basis: usize, flexes: Rc>>) -> Self { Self { axis, @@ -761,8 +761,9 @@ mod element { self.basis + ix, handle_bounds, ); - mouse_region = - mouse_region.on_drag(MouseButton::Left, move |drag, workspace: &mut Workspace, cx| { + mouse_region = mouse_region.on_drag( + MouseButton::Left, + move |drag, workspace: &mut Workspace, cx| { let min_size = match axis { Axis::Horizontal => HORIZONTAL_MIN_SIZE, Axis::Vertical => VERTICAL_MIN_SIZE, @@ -805,7 +806,8 @@ mod element { workspace.schedule_serialize(cx); cx.notify(); - }); + }, + ); scene.push_mouse_region(mouse_region); scene.pop_stacking_context(); diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index aa184dbb56..5f4c29cd5b 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -161,7 +161,11 @@ impl SerializedPaneGroup { Vec>>, )> { match self { - SerializedPaneGroup::Group { axis, children, flexes } => { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { let mut current_active_pane = None; let mut members = Vec::new(); let mut items = Vec::new(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e31e0d924f..885c686ddc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2898,7 +2898,8 @@ impl Workspace { fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, cx| async move { cx.background().timer(Duration::from_millis(100)).await; - this.read_with(&cx, |this, cx| this.serialize_workspace(cx)).ok(); + this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) + .ok(); })); } From 9da8f609cf71d9bc0ee9e5686d5b6caef0d50465 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 14:34:32 -0700 Subject: [PATCH 12/57] tidy up names --- crates/workspace/src/pane_group.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 1edee1bc42..2ece5030f3 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -421,14 +421,13 @@ impl PaneAxis { ) -> AnyElement { debug_assert!(self.members.len() == self.flexes.borrow().len()); - let mut flex_container = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); + let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); let mut active_pane_ix = None; let mut members = self.members.iter().enumerate().peekable(); while let Some((ix, member)) = members.next() { let last = members.peek().is_none(); - // TODO: Restore this if member.contains(active_pane) { active_pane_ix = Some(ix); } @@ -460,10 +459,10 @@ impl PaneAxis { member = member.contained().with_border(border).into_any(); } - flex_container = flex_container.with_child(member.into_any()); + pane_axis = pane_axis.with_child(member.into_any()); } - flex_container.set_active_pane(active_pane_ix); - flex_container.into_any() + pane_axis.set_active_pane(active_pane_ix); + pane_axis.into_any() } } @@ -562,7 +561,7 @@ mod element { self.active_pane_ix = active_pane_ix; } - fn layout_flex_children( + fn layout_children( &mut self, active_pane_magnification: f32, constraint: SizeConstraint, @@ -656,7 +655,7 @@ mod element { panic!("flex contains flexible children but has an infinite constraint along the flex axis"); } - self.layout_flex_children( + self.layout_children( active_pane_magnification, constraint, &mut remaining_space, @@ -846,6 +845,7 @@ mod element { "type": "PaneAxis", "bounds": bounds.to_json(), "axis": self.axis.to_json(), + "flexes": *self.flexes.borrow(), "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() }) } From 50623c018cb04b1080025f074b8bf45c1a8e7af2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 14:47:37 -0700 Subject: [PATCH 13/57] Fix serialization error --- crates/workspace/src/persistence.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 0d7784093a..2a4062c079 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -443,7 +443,10 @@ impl WorkspaceDb { } => { let (parent_id, position) = unzip_option(parent); - let flex_string = serde_json::json!(flexes).to_string(); + let flex_string = flexes + .as_ref() + .map(|flexes| serde_json::json!(flexes).to_string()); + let group_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO pane_groups( workspace_id, From 77c4fc98bdfc4bdef5ac5c2f63147887341407b2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 16:13:57 -0700 Subject: [PATCH 14/57] Add line height settings for the editor --- assets/settings/default.json | 12 +++++++++++- crates/editor/src/editor.rs | 5 ++++- crates/editor/src/element.rs | 2 +- crates/terminal/src/terminal.rs | 2 +- crates/theme/src/theme_settings.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 1f8d12a3d9..2e6361ce7e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -24,6 +24,17 @@ }, // The default font size for text in the editor "buffer_font_size": 15, + // Set the buffer's line height. + // May take 3 values: + // 1. Use a line height that's comfortable for reading (1.618) + // "line_height": "comfortable" + // 2. Use a standard line height, (1.3) + // "line_height": "standard", + // 3. Use a custom line height + // "line_height": { + // "custom": 2 + // }, + "buffer_line_height": "comfortable", // The factor to grow the active pane by. Defaults to 1.0 // which gives the same size as all other panes. "active_pane_magnification": 1.0, @@ -282,7 +293,6 @@ // "line_height": { // "custom": 2 // }, - // "line_height": "comfortable" // Set the terminal's font size. If this option is not included, // the terminal will default to matching the buffer's font size. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 85a428d801..388f1aae88 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -494,6 +494,7 @@ pub enum SoftWrap { #[derive(Clone)] pub struct EditorStyle { pub text: TextStyle, + pub line_height_scalar: f32, pub placeholder_text: Option, pub theme: theme::Editor, pub theme_id: usize, @@ -8101,7 +8102,7 @@ fn build_style( cx: &AppContext, ) -> EditorStyle { let font_cache = cx.font_cache(); - + 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 { @@ -8115,6 +8116,7 @@ fn build_style( EditorStyle { text: field_editor_theme.text, placeholder_text: field_editor_theme.placeholder_text, + line_height_scalar, theme, theme_id, } @@ -8137,6 +8139,7 @@ fn build_style( underline: Default::default(), }, placeholder_text: None, + line_height_scalar, theme, theme_id, } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 074a96dfc1..6420c56ece 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1975,7 +1975,7 @@ impl Element for EditorElement { let snapshot = editor.snapshot(cx); let style = self.style.clone(); - let line_height = style.text.line_height(cx.font_cache()); + let line_height = (style.text.font_size * style.line_height_scalar).round(); let gutter_padding; let gutter_width; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 576719526d..d14118bb18 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -198,7 +198,7 @@ impl TerminalLineHeight { match self { TerminalLineHeight::Comfortable => 1.618, TerminalLineHeight::Standard => 1.3, - TerminalLineHeight::Custom(line_height) => *line_height, + TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.), } } } diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index b9e6f7a133..359ed8e511 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use util::ResultExt as _; const MIN_FONT_SIZE: f32 = 6.0; +const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone, JsonSchema)] pub struct ThemeSettings { @@ -20,6 +21,7 @@ pub struct ThemeSettings { pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub(crate) buffer_font_size: f32, + pub(crate) buffer_line_height: BufferLineHeight, #[serde(skip)] pub theme: Arc, } @@ -33,11 +35,32 @@ pub struct ThemeSettingsContent { #[serde(default)] pub buffer_font_size: Option, #[serde(default)] + pub buffer_line_height: Option, + #[serde(default)] pub buffer_font_features: Option, #[serde(default)] pub theme: Option, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum BufferLineHeight { + #[default] + Comfortable, + Standard, + Custom(f32), +} + +impl BufferLineHeight { + pub fn value(&self) -> f32 { + match self { + BufferLineHeight::Comfortable => 1.618, + BufferLineHeight::Standard => 1.3, + BufferLineHeight::Custom(line_height) => *line_height, + } + } +} + impl ThemeSettings { pub fn buffer_font_size(&self, cx: &AppContext) -> f32 { if cx.has_global::() { @@ -47,6 +70,10 @@ impl ThemeSettings { } .max(MIN_FONT_SIZE) } + + pub fn line_height(&self) -> f32 { + f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT) + } } pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 { @@ -106,6 +133,7 @@ impl settings::Setting for ThemeSettings { buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), + buffer_line_height: defaults.buffer_line_height.unwrap(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; @@ -136,6 +164,7 @@ impl settings::Setting for ThemeSettings { } merge(&mut this.buffer_font_size, value.buffer_font_size); + merge(&mut this.buffer_line_height, value.buffer_line_height); } Ok(this) From 1424a7a56a318d3401ca20a703905492a32e4be9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 21:43:53 -0700 Subject: [PATCH 15/57] Add svelte language server Add svelte tree sitter Add svelte config file Add svelte highlighting --- Cargo.lock | 10 ++ crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 8 +- crates/zed/src/languages/svelte.rs | 126 ++++++++++++++++++ crates/zed/src/languages/svelte/config.toml | 18 +++ crates/zed/src/languages/svelte/folds.scm | 9 ++ .../zed/src/languages/svelte/highlights.scm | 68 ++++++++++ crates/zed/src/languages/svelte/indents.scm | 20 +++ .../zed/src/languages/svelte/injections.scm | 9 ++ 9 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 crates/zed/src/languages/svelte.rs create mode 100644 crates/zed/src/languages/svelte/config.toml create mode 100755 crates/zed/src/languages/svelte/folds.scm create mode 100755 crates/zed/src/languages/svelte/highlights.scm create mode 100755 crates/zed/src/languages/svelte/indents.scm create mode 100755 crates/zed/src/languages/svelte/injections.scm diff --git a/Cargo.lock b/Cargo.lock index 0ac6a2ee89..b2f05a116d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8125,6 +8125,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-svelte" +version = "0.10.2" +source = "git+https://github.com/Himujjal/tree-sitter-svelte?rev=697bb515471871e85ff799ea57a76298a71a9cca#697bb515471871e85ff799ea57a76298a71a9cca" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-toml" version = "0.5.1" @@ -9555,6 +9564,7 @@ dependencies = [ "tree-sitter-ruby", "tree-sitter-rust", "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-yaml", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 597e40161f..bf5d3bc8be 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -122,6 +122,7 @@ tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} 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" url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 820f564151..a7590001d7 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -17,6 +17,7 @@ mod python; mod ruby; mod rust; mod typescript; +mod svelte; mod yaml; // 1. Add tree-sitter-{language} parser to zed crate @@ -135,7 +136,12 @@ pub fn init(languages: Arc, node_runtime: Arc) { language( "yaml", tree_sitter_yaml::language(), - vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime))], + vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))], + ); + language( + "svelte", + tree_sitter_svelte::language(), + vec![Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone()))], ); } diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs new file mode 100644 index 0000000000..35f6945285 --- /dev/null +++ b/crates/zed/src/languages/svelte.rs @@ -0,0 +1,126 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use node_runtime::NodeRuntime; +use serde_json::json; +use smol::fs; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; + +const SERVER_PATH: &'static str = + "node_modules/svelte-language-server/bin/server.js"; + +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +pub struct SvelteLspAdapter { + node: Arc, +} + +impl SvelteLspAdapter { + pub fn new(node: Arc) -> Self { + SvelteLspAdapter { node } + } +} + +#[async_trait] +impl LspAdapter for SvelteLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("svelte-language-server".into()) + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new( + self.node + .npm_package_latest_version("svelte-language-server") + .await?, + ) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + [("svelte-language-server", version.as_str())], + ) + .await?; + } + + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await + } + + async fn initialization_options(&self) -> Option { + Some(json!({ + "provideFormatter": true + })) + } +} + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/svelte/config.toml b/crates/zed/src/languages/svelte/config.toml new file mode 100644 index 0000000000..41bb21a45d --- /dev/null +++ b/crates/zed/src/languages/svelte/config.toml @@ -0,0 +1,18 @@ +name = "Svelte" +path_suffixes = ["svelte"] +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 = false, newline = true, not_in = ["string", "comment"] }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "`", end = "`", close = true, newline = false, not_in = ["string"] }, + { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, +] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] diff --git a/crates/zed/src/languages/svelte/folds.scm b/crates/zed/src/languages/svelte/folds.scm new file mode 100755 index 0000000000..795c32fc4a --- /dev/null +++ b/crates/zed/src/languages/svelte/folds.scm @@ -0,0 +1,9 @@ +[ + (style_element) + (script_element) + (element) + (if_statement) + (else_statement) + (each_statement) + (await_statement) +] @fold diff --git a/crates/zed/src/languages/svelte/highlights.scm b/crates/zed/src/languages/svelte/highlights.scm new file mode 100755 index 0000000000..0b868b7591 --- /dev/null +++ b/crates/zed/src/languages/svelte/highlights.scm @@ -0,0 +1,68 @@ +; Special identifiers +;-------------------- +[ + "<" + ">" + "" + "#" + ":" + "/" + "@" +] @tag.delimiter + +[ + "{" + "}" +] @punctuation.bracket + +[ + (special_block_keyword) + (then) + (as) +] @keyword + +[ + (text) + (raw_text_expr) +] @none + +[ + (attribute_value) + (quoted_attribute_value) +] @string + +(tag_name) @tag +(attribute_name) @property +(erroneous_end_tag_name) @error +(comment) @comment + +((attribute + (attribute_name) @_attr + (quoted_attribute_value (attribute_value) @text.uri)) + (#match? @_attr "^(href|src)$")) + +; TODO: + +((element (start_tag (tag_name) @_tag) (text) @text.uri) + (#eq? @_tag "a")) + +((element (start_tag (tag_name) @_tag) (text) @text.literal) + (#match? @_tag "^(code|kbd)$")) + +((element (start_tag (tag_name) @_tag) (text) @text.underline) + (#eq? @_tag "u")) + +((element (start_tag (tag_name) @_tag) (text) @text.strike) + (#match? @_tag "^(s|del)$")) + +((element (start_tag (tag_name) @_tag) (text) @text.emphasis) + (#match? @_tag "^(em|i)$")) + +((element (start_tag (tag_name) @_tag) (text) @text.strong) + (#match? @_tag "^(strong|b)$")) + +((element (start_tag (tag_name) @_tag) (text) @text.title) + (#match? @_tag "^(h[0-9]|title)$")) + +"=" @operator diff --git a/crates/zed/src/languages/svelte/indents.scm b/crates/zed/src/languages/svelte/indents.scm new file mode 100755 index 0000000000..2f6f5c32e7 --- /dev/null +++ b/crates/zed/src/languages/svelte/indents.scm @@ -0,0 +1,20 @@ +[ + (element) + (if_statement) + (each_statement) + (await_statement) + (script_element) + (style_element) +] @indent + +[ + (end_tag) + (else_statement) + (if_end_expr) + (each_end_expr) + (await_end_expr) + ">" + "/>" +] @branch + +(comment) @ignore diff --git a/crates/zed/src/languages/svelte/injections.scm b/crates/zed/src/languages/svelte/injections.scm new file mode 100755 index 0000000000..84043c0061 --- /dev/null +++ b/crates/zed/src/languages/svelte/injections.scm @@ -0,0 +1,9 @@ +; injections.scm +; -------------- +(script_element + (raw_text) @content + (#set! "language" "javascript")) + +(style_element + (raw_text) @content + (#set! "language" "css")) From ff8a89a0758119ba62eb64ef5120361b90c24edc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 22:30:41 -0700 Subject: [PATCH 16/57] Refine svelte queries to work with zed-style highlights Bump scheme dependency: --- Cargo.lock | 4 +- crates/zed/Cargo.toml | 2 +- .../zed/src/languages/svelte/highlights.scm | 86 +++++++------------ crates/zed/src/languages/svelte/indents.scm | 12 --- .../zed/src/languages/svelte/injections.scm | 19 ++++ 5 files changed, 52 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2f05a116d..1a4562954f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8118,8 +8118,8 @@ dependencies = [ [[package]] name = "tree-sitter-scheme" -version = "0.2.0" -source = "git+https://github.com/6cdh/tree-sitter-scheme?rev=af0fd1fa452cb2562dc7b5c8a8c55551c39273b9#af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" +version = "0.5.0" +source = "git+https://github.com/6cdh/tree-sitter-scheme?rev=ca8af220aaf2a80aaf609bfb0df193817e4f064b#ca8af220aaf2a80aaf609bfb0df193817e4f064b" dependencies = [ "cc", "tree-sitter", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index bf5d3bc8be..28970b1a0f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -119,7 +119,7 @@ tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", re 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-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"} diff --git a/crates/zed/src/languages/svelte/highlights.scm b/crates/zed/src/languages/svelte/highlights.scm index 0b868b7591..de873684e4 100755 --- a/crates/zed/src/languages/svelte/highlights.scm +++ b/crates/zed/src/languages/svelte/highlights.scm @@ -1,5 +1,35 @@ ; Special identifiers ;-------------------- + +; TODO: +(tag_name) @tag +(attribute_name) @property +(erroneous_end_tag_name) @keyword +(comment) @comment + +[ + (attribute_value) + (quoted_attribute_value) +] @string + +[ + (text) + (raw_text_expr) +] @none + +[ + (special_block_keyword) + (then) + (as) +] @keyword + +[ + "{" + "}" +] @punctuation.bracket + +"=" @operator + [ "<" ">" @@ -10,59 +40,3 @@ "/" "@" ] @tag.delimiter - -[ - "{" - "}" -] @punctuation.bracket - -[ - (special_block_keyword) - (then) - (as) -] @keyword - -[ - (text) - (raw_text_expr) -] @none - -[ - (attribute_value) - (quoted_attribute_value) -] @string - -(tag_name) @tag -(attribute_name) @property -(erroneous_end_tag_name) @error -(comment) @comment - -((attribute - (attribute_name) @_attr - (quoted_attribute_value (attribute_value) @text.uri)) - (#match? @_attr "^(href|src)$")) - -; TODO: - -((element (start_tag (tag_name) @_tag) (text) @text.uri) - (#eq? @_tag "a")) - -((element (start_tag (tag_name) @_tag) (text) @text.literal) - (#match? @_tag "^(code|kbd)$")) - -((element (start_tag (tag_name) @_tag) (text) @text.underline) - (#eq? @_tag "u")) - -((element (start_tag (tag_name) @_tag) (text) @text.strike) - (#match? @_tag "^(s|del)$")) - -((element (start_tag (tag_name) @_tag) (text) @text.emphasis) - (#match? @_tag "^(em|i)$")) - -((element (start_tag (tag_name) @_tag) (text) @text.strong) - (#match? @_tag "^(strong|b)$")) - -((element (start_tag (tag_name) @_tag) (text) @text.title) - (#match? @_tag "^(h[0-9]|title)$")) - -"=" @operator diff --git a/crates/zed/src/languages/svelte/indents.scm b/crates/zed/src/languages/svelte/indents.scm index 2f6f5c32e7..886d8ca867 100755 --- a/crates/zed/src/languages/svelte/indents.scm +++ b/crates/zed/src/languages/svelte/indents.scm @@ -6,15 +6,3 @@ (script_element) (style_element) ] @indent - -[ - (end_tag) - (else_statement) - (if_end_expr) - (each_end_expr) - (await_end_expr) - ">" - "/>" -] @branch - -(comment) @ignore diff --git a/crates/zed/src/languages/svelte/injections.scm b/crates/zed/src/languages/svelte/injections.scm index 84043c0061..8c1ac9fcd0 100755 --- a/crates/zed/src/languages/svelte/injections.scm +++ b/crates/zed/src/languages/svelte/injections.scm @@ -4,6 +4,25 @@ (raw_text) @content (#set! "language" "javascript")) + ((script_element + (start_tag + (attribute + (quoted_attribute_value (attribute_value) @_language))) + (raw_text) @content) + (#eq? @_language "ts") + (#set! "language" "typescript")) + +((script_element + (start_tag + (attribute + (quoted_attribute_value (attribute_value) @_language))) + (raw_text) @content) + (#eq? @_language "typescript") + (#set! "language" "typescript")) + (style_element (raw_text) @content (#set! "language" "css")) + +((raw_text_expr) @content + (#set! "language" "javascript")) From f1b034d4f8963f40720007ab6538cf6c33d8691c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 13 Jul 2023 22:32:29 -0700 Subject: [PATCH 17/57] fmt --- crates/zed/src/languages.rs | 6 ++++-- crates/zed/src/languages/svelte.rs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index a7590001d7..8e0e21faba 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -16,8 +16,8 @@ mod lua; mod python; mod ruby; mod rust; -mod typescript; mod svelte; +mod typescript; mod yaml; // 1. Add tree-sitter-{language} parser to zed crate @@ -141,7 +141,9 @@ pub fn init(languages: Arc, node_runtime: Arc) { language( "svelte", tree_sitter_svelte::language(), - vec![Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone()))], + vec![Arc::new(svelte::SvelteLspAdapter::new( + node_runtime.clone(), + ))], ); } diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 35f6945285..8416859f5a 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -14,8 +14,7 @@ use std::{ }; use util::ResultExt; -const SERVER_PATH: &'static str = - "node_modules/svelte-language-server/bin/server.js"; +const SERVER_PATH: &'static str = "node_modules/svelte-language-server/bin/server.js"; fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] From 29cbeb39bddd67b8846da75241595f809ff2c767 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 13 Jul 2023 14:29:02 +0300 Subject: [PATCH 18/57] 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 4726c67aea..006719e5f5 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 431ccf0bfe..cc24cd35da 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 d82ce5e216..a22506f751 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 5a4f912e3a..663164dd07 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 12d8c6b34d..b27349f412 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 59d25c2659..22778f85e8 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 90ea508cc6..da679d191e 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 d14118bb18..39e77b590b 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 36be6bee7f..8e1e4ad62f 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 7e3f7227b0..4ebfe69c21 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 19/57] 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 006719e5f5..611d3633e3 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 20/57] 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 611d3633e3..c044f4b600 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 22778f85e8..a87587a92f 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 da679d191e..7080b4c07e 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 21/57] 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 5c16d03233..28940a8367 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 22/57] 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 cc24cd35da..9498be1844 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 663164dd07..bea398d3eb 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 b27349f412..0dc594a13f 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 a87587a92f..c3790116d3 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 8e1e4ad62f..3dd401e392 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 4ebfe69c21..3a3ba02e06 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 23/57] 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 c3790116d3..f6466c85af 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 4e8ece1c8f..cdf3cadf59 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 24/57] 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 5041ab759d..0b10432a9f 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 38cefbcef9..db9693a530 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 b6431c2286..526f99ec95 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 3bb5457b1c..fded9ec309 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 2c3c9d5304..b113af34ad 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 fafd75dc8d..b5d74f80f3 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 25/57] 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 1a4562954f..57604d28b8 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 529f297f70..48a9a51cd1 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 dcc2220227..087ce81c26 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 260b0ccc40..247a7b021d 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 c1f7e79d58..4771fc7083 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 db9693a530..399ca85e56 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 dbd35f0e87..af80069e15 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 28970b1a0f..848c07d500 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 26/57] 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 526f99ec95..1590294b1a 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 272501f2d0..a6d35d0e2f 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 b5d74f80f3..c9f952ee3c 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 8728110d58..5252b71fac 100644 --- a/crates/zed/src/languages/heex/highlights.scm +++ b/crates/zed/src/languages/heex/highlights.scm @@ -1,10 +1,7 @@ ; HEEx delimiters [ - "--%>" - "-->" "/>" "" + "--%>" + "-->" + "