diff --git a/.gitignore b/.gitignore index dbffa0f829..15a0a9f5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/target +**/cargo-target /zed.xcworkspace .DS_Store /plugins/bin diff --git a/Cargo.lock b/Cargo.lock index bf0ed9b163..eb7543491d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3164,6 +3164,7 @@ dependencies = [ "sqlez", "sum_tree", "taffy", + "thiserror", "time 0.3.27", "tiny-skia", "usvg", @@ -3172,6 +3173,36 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "gpui2" +version = "0.1.0" +dependencies = [ + "anyhow", + "derive_more", + "futures 0.3.28", + "gpui", + "gpui2_macros", + "log", + "parking_lot 0.11.2", + "refineable", + "rust-embed", + "serde", + "settings", + "simplelog", + "smallvec", + "theme", + "util", +] + +[[package]] +name = "gpui2_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "gpui_macros" version = "0.1.0" @@ -5232,33 +5263,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "playground" -version = "0.1.0" -dependencies = [ - "anyhow", - "derive_more", - "gpui", - "log", - "parking_lot 0.11.2", - "playground_macros", - "refineable", - "serde", - "simplelog", - "smallvec", - "taffy", - "util", -] - -[[package]] -name = "playground_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "plist" version = "1.5.0" @@ -7368,6 +7372,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "storybook" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui2", + "log", + "rust-embed", + "serde", + "settings", + "simplelog", + "theme", + "util", +] + [[package]] name = "stringprep" version = "0.1.3" @@ -7567,7 +7586,7 @@ dependencies = [ [[package]] name = "taffy" version = "0.3.11" -source = "git+https://github.com/DioxusLabs/taffy?rev=dab541d6104d58e2e10ce90c4a1dad0b703160cd#dab541d6104d58e2e10ce90c4a1dad0b703160cd" +source = "git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e#4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" dependencies = [ "arrayvec 0.7.4", "grid", @@ -8407,6 +8426,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-nu" +version = "0.0.1" +source = "git+https://github.com/nushell/tree-sitter-nu?rev=786689b0562b9799ce53e824cb45a1a2a04dc673#786689b0562b9799ce53e824cb45a1a2a04dc673" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-php" version = "0.19.1" @@ -8830,12 +8858,14 @@ dependencies = [ "collections", "command_palette", "editor", + "futures 0.3.28", "gpui", "indoc", "itertools", "language", "language_selector", "log", + "lsp", "nvim-rs", "parking_lot 0.11.2", "project", @@ -9867,6 +9897,7 @@ dependencies = [ "tree-sitter-lua", "tree-sitter-markdown", "tree-sitter-nix", + "tree-sitter-nu", "tree-sitter-php", "tree-sitter-python", "tree-sitter-racket", diff --git a/Cargo.toml b/Cargo.toml index 5938ecb402..96070658b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,9 +32,9 @@ members = [ "crates/git", "crates/go_to_line", "crates/gpui", - "crates/gpui/playground", - "crates/gpui/playground_macros", "crates/gpui_macros", + "crates/gpui2", + "crates/gpui2_macros", "crates/install_cli", "crates/journal", "crates/language", @@ -63,6 +63,7 @@ members = [ "crates/sqlez", "crates/sqlez_macros", "crates/feature_flags", + "crates/storybook", "crates/sum_tree", "crates/terminal", "crates/text", @@ -141,6 +142,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } +tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } diff --git a/README.md b/README.md index 8849f1aa73..2ee426a2a6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,26 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea ### Dependencies -* Install [Postgres.app](https://postgresapp.com) and start it. +* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license: + ``` + sudo xcodebuild -license + ``` + +* Install homebrew, node and rustup-init (rutup, rust, cargo, etc.) + ``` + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install node rustup-init + rustup-init # follow the installation steps + ``` + +* Install postgres and configure the database + ``` + brew install postgresql@15 + brew services start postgresql@15 + psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres + psql -U postgres -c "CREATE DATABASE zed" + ``` + * Install the `LiveKit` server and the `foreman` process supervisor: ``` @@ -41,6 +60,17 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea GITHUB_TOKEN=<$token> script/bootstrap ``` +* Now try running zed with collaboration disabled: + ``` + cargo run + ``` + +### Common errors + +* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` + * You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer` + * (see https://github.com/gfx-rs/gfx/issues/2309) + ### Testing against locally-running servers Start the web and collab servers: diff --git a/assets/icons/Icons/exit.svg b/assets/icons/Icons/exit.svg new file mode 100644 index 0000000000..6d76849248 --- /dev/null +++ b/assets/icons/Icons/exit.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/select-all.svg b/assets/icons/select-all.svg new file mode 100644 index 0000000000..45a10bba42 --- /dev/null +++ b/assets/icons/select-all.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/stop_sharing.svg b/assets/icons/stop_sharing.svg new file mode 100644 index 0000000000..e9aa7eac5a --- /dev/null +++ b/assets/icons/stop_sharing.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index fa62a74f3f..2211f9563d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -231,7 +231,14 @@ } }, { - "context": "BufferSearchBar > Editor", + "context": "BufferSearchBar && in_replace", + "bindings": { + "enter": "search::ReplaceNext", + "cmd-enter": "search::ReplaceAll" + } + }, + { + "context": "BufferSearchBar && !in_replace > Editor", "bindings": { "up": "search::PreviousHistoryQuery", "down": "search::NextHistoryQuery" @@ -533,7 +540,7 @@ // TODO: Move this to a dock open action "cmd-shift-c": "collab_panel::ToggleFocus", "cmd-alt-i": "zed::DebugElements", - "ctrl-:": "editor::ToggleInlayHints", + "ctrl-:": "editor::ToggleInlayHints" } }, { diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index da094ea7e4..1a7b81ee8f 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -32,6 +32,8 @@ "right": "vim::Right", "$": "vim::EndOfLine", "^": "vim::FirstNonWhitespace", + "_": "vim::StartOfLineDownward", + "g _": "vim::EndOfLineDownward", "shift-g": "vim::EndOfDocument", "w": "vim::NextWordStart", "{": "vim::StartOfParagraph", @@ -198,6 +200,18 @@ "z c": "editor::Fold", "z o": "editor::UnfoldLines", "z f": "editor::FoldSelectedRanges", + "shift-z shift-q": [ + "pane::CloseActiveItem", + { + "saveBehavior": "dontSave" + } + ], + "shift-z shift-z": [ + "pane::CloseActiveItem", + { + "saveBehavior": "promptOnConflict" + } + ], // Count support "1": [ "vim::Number", @@ -314,8 +328,9 @@ } }, { - "context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting", + "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting", "bindings": { + ".": "vim::Repeat", "c": [ "vim::PushOperator", "Change" @@ -326,15 +341,12 @@ "Delete" ], "shift-d": "vim::DeleteToEndOfLine", - "shift-j": "editor::JoinLines", + "shift-j": "vim::JoinLines", "y": [ "vim::PushOperator", "Yank" ], - "i": [ - "vim::SwitchMode", - "Insert" - ], + "i": "vim::InsertBefore", "shift-i": "vim::InsertFirstNonWhitespace", "a": "vim::InsertAfter", "shift-a": "vim::InsertEndOfLine", @@ -379,7 +391,7 @@ } }, { - "context": "Editor && vim_operator == n", + "context": "Editor && VimCount", "bindings": { "0": [ "vim::Number", @@ -448,13 +460,12 @@ ], "s": "vim::Substitute", "shift-s": "vim::SubstituteLine", + "shift-r": "vim::SubstituteLine", "c": "vim::Substitute", "~": "vim::ChangeCase", - "shift-i": [ - "vim::SwitchMode", - "Insert" - ], + "shift-i": "vim::InsertBefore", "shift-a": "vim::InsertAfter", + "shift-j": "vim::JoinLines", "r": [ "vim::PushOperator", "Replace" @@ -488,7 +499,7 @@ "around": true } } - ], + ] } }, { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index fba10c61ba..8e0252ec60 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2434,14 +2434,14 @@ fn render_tree_branch( let cap_height = row_style.cap_height(font_cache); let baseline_offset = row_style.baseline_offset(font_cache) + (size.y() - line_height) / 2.; - Canvas::new(move |scene, bounds, _, _, _| { - scene.paint_layer(None, |scene| { + Canvas::new(move |bounds, _, _, cx| { + cx.paint_layer(None, |cx| { let start_x = bounds.min_x() + (bounds.width() / 2.) - (branch_style.width / 2.); let end_x = bounds.max_x(); let start_y = bounds.min_y(); let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.); - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds: RectF::from_points( vec2f(start_x, start_y), vec2f( @@ -2453,7 +2453,7 @@ fn render_tree_branch( border: gpui::Border::default(), corner_radii: (0.).into(), }); - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds: RectF::from_points( vec2f(start_x, end_y), vec2f(end_x, end_y + branch_style.width), diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 95b9868937..f0e09e139e 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -13,8 +13,8 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder, - Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, }; use picker::PickerEvent; use project::{Project, RepositoryEntry}; @@ -771,7 +771,7 @@ impl CollabTitlebarItem { }) .with_tooltip::( 0, - "Toggle user menu".to_owned(), + "Toggle User Menu".to_owned(), Some(Box::new(ToggleUserMenu)), tooltip, cx, @@ -1165,19 +1165,18 @@ impl Element for AvatarRibbon { &mut self, constraint: gpui::SizeConstraint, _: &mut CollabTitlebarItem, - _: &mut LayoutContext, + _: &mut ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { (constraint.max, ()) } fn paint( &mut self, - scene: &mut SceneBuilder, bounds: RectF, _: RectF, _: &mut Self::LayoutState, _: &mut CollabTitlebarItem, - _: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let mut path = PathBuilder::new(); path.reset(bounds.lower_left()); @@ -1188,7 +1187,7 @@ impl Element for AvatarRibbon { path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.)); path.curve_to(bounds.lower_right(), bounds.upper_right()); path.line_to(bounds.lower_left()); - scene.push_path(path.build(self.color, None)); + cx.scene().push_path(path.build(self.color, None)); } fn rect_for_text_range( diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index a86b257686..5017666f7b 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -7,7 +7,7 @@ use gpui::{ }, json::ToJson, serde_json::{self, json}, - AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, View, ViewContext, + AnyElement, Axis, Element, View, ViewContext, }; pub(crate) struct FacePile { @@ -32,7 +32,7 @@ impl Element for FacePile { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); @@ -53,12 +53,11 @@ impl Element for FacePile { fn paint( &mut self, - scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, _layout: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -69,9 +68,10 @@ impl Element for FacePile { let size = face.size(); origin_x -= size.x(); let origin_y = origin_y + (bounds.height() - size.y()) / 2.0; - scene.paint_layer(None, |scene| { - face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx); - }); + + cx.scene().push_layer(None); + face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx); + cx.scene().pop_layer(); origin_x += self.overlap; } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 101d4dc545..4f9bb231ce 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -1,11 +1,11 @@ -use collections::CommandPaletteFilter; +use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle, AppContext, Element, MouseState, ViewContext, }; use picker::{Picker, PickerDelegate, PickerEvent}; -use std::cmp; +use std::cmp::{self, Reverse}; use util::ResultExt; use workspace::Workspace; @@ -33,13 +33,18 @@ pub enum Event { action: Box, }, } - struct Command { name: String, action: Box, keystrokes: Vec, } +/// Hit count for each command in the palette. +/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because +/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it. +#[derive(Default)] +struct HitCounts(HashMap); + fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id()); workspace.toggle_modal(cx, |_, cx| { @@ -83,7 +88,7 @@ impl PickerDelegate for CommandPaletteDelegate { let view_id = self.focused_view_id; let window = cx.window(); cx.spawn(move |picker, mut cx| async move { - let actions = window + let mut actions = window .available_actions(view_id, &cx) .into_iter() .flatten() @@ -112,6 +117,16 @@ impl PickerDelegate for CommandPaletteDelegate { } }) .collect::>(); + let actions = cx.read(move |cx| { + let hit_counts = cx.optional_global::(); + actions.sort_by_key(|action| { + ( + Reverse(hit_counts.and_then(|map| map.0.get(&action.name)).cloned()), + action.name.clone(), + ) + }); + actions + }); let candidates = actions .iter() .enumerate() @@ -166,7 +181,12 @@ impl PickerDelegate for CommandPaletteDelegate { let window = cx.window(); let focused_view_id = self.focused_view_id; let action_ix = self.matches[self.selected_ix].candidate_id; - let action = self.actions.remove(action_ix).action; + let command = self.actions.remove(action_ix); + cx.update_default_global(|hit_counts: &mut HitCounts, _| { + *hit_counts.0.entry(command.name).or_default() += 1; + }); + let action = command.action; + cx.app_context() .spawn(move |mut cx| async move { window @@ -319,6 +339,21 @@ mod tests { workspace.modal::().unwrap() }); + palette + .update(cx, |palette, cx| { + // Fill up palette's command list by running an empty query; + // we only need it to subsequently assert that the palette is initially + // sorted by command's name. + palette.delegate_mut().update_matches("".to_string(), cx) + }) + .await; + + palette.update(cx, |palette, _| { + let is_sorted = + |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name); + assert!(is_sorted(&palette.delegate().actions)); + }); + palette .update(cx, |palette, cx| { palette diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 89b4469d42..c3733018b6 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -32,7 +32,8 @@ impl DiagnosticIndicator { this.in_progress_checks.insert(*language_server_id); cx.notify(); } - project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { + project::Event::DiskBasedDiagnosticsFinished { language_server_id } + | project::Event::LanguageServerRemoved(language_server_id) => { this.summary = project.read(cx).diagnostic_summary(cx); this.in_progress_checks.remove(language_server_id); cx.notify(); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e8e15a927e..f306692b5e 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -555,67 +555,6 @@ impl DisplaySnapshot { }) } - /// Returns an iterator of the start positions of the occurrences of `target` in the `self` after `from` - /// Stops if `condition` returns false for any of the character position pairs observed. - pub fn find_while<'a>( - &'a self, - from: DisplayPoint, - target: &str, - condition: impl FnMut(char, DisplayPoint) -> bool + 'a, - ) -> impl Iterator + 'a { - Self::find_internal(self.chars_at(from), target.chars().collect(), condition) - } - - /// Returns an iterator of the end positions of the occurrences of `target` in the `self` before `from` - /// Stops if `condition` returns false for any of the character position pairs observed. - pub fn reverse_find_while<'a>( - &'a self, - from: DisplayPoint, - target: &str, - condition: impl FnMut(char, DisplayPoint) -> bool + 'a, - ) -> impl Iterator + 'a { - Self::find_internal( - self.reverse_chars_at(from), - target.chars().rev().collect(), - condition, - ) - } - - fn find_internal<'a>( - iterator: impl Iterator + 'a, - target: Vec, - mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a, - ) -> impl Iterator + 'a { - // List of partial matches with the index of the last seen character in target and the starting point of the match - let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new(); - iterator - .take_while(move |(ch, point)| condition(*ch, *point)) - .filter_map(move |(ch, point)| { - if Some(&ch) == target.get(0) { - partial_matches.push((0, point)); - } - - let mut found = None; - // Keep partial matches that have the correct next character - partial_matches.retain_mut(|(match_position, match_start)| { - if target.get(*match_position) == Some(&ch) { - *match_position += 1; - if *match_position == target.len() { - found = Some(match_start.clone()); - // This match is completed. No need to keep tracking it - false - } else { - true - } - } else { - false - } - }); - - found - }) - } - pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 { let mut count = 0; let mut column = 0; @@ -933,7 +872,7 @@ pub mod tests { use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::SyntaxTheme; - use util::test::{marked_text_offsets, marked_text_ranges, sample_text}; + use util::test::{marked_text_ranges, sample_text}; use Bias::*; #[gpui::test(iterations = 100)] @@ -1744,32 +1683,6 @@ pub mod tests { ) } - #[test] - fn test_find_internal() { - assert("This is a ˇtest of find internal", "test"); - assert("Some text ˇaˇaˇaa with repeated characters", "aa"); - - fn assert(marked_text: &str, target: &str) { - let (text, expected_offsets) = marked_text_offsets(marked_text); - - let chars = text - .chars() - .enumerate() - .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32))); - let target = target.chars(); - - assert_eq!( - expected_offsets - .into_iter() - .map(|offset| offset as u32) - .collect::>(), - DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true) - .map(|point| point.column()) - .collect::>() - ) - } - } - fn syntax_chunks<'a>( rows: Range, map: &ModelHandle, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 12df29df1d..d651980f33 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -572,7 +572,7 @@ pub struct Editor { project: Option>, focused: bool, blink_manager: ModelHandle, - show_local_selections: bool, + pub show_local_selections: bool, mode: EditorMode, replica_id_mapping: Option>, show_gutter: bool, @@ -2273,10 +2273,6 @@ impl Editor { if self.read_only { return; } - if !self.input_enabled { - cx.emit(Event::InputIgnored { text }); - return; - } let selections = self.selections.all_adjusted(cx); let mut brace_inserted = false; @@ -3211,17 +3207,30 @@ impl Editor { .count(); let snapshot = self.buffer.read(cx).snapshot(cx); + let mut range_to_replace: Option> = None; let mut ranges = Vec::new(); for selection in &selections { if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { let start = selection.start.saturating_sub(lookbehind); let end = selection.end + lookahead; + if selection.id == newest_selection.id { + range_to_replace = Some( + ((start + common_prefix_len) as isize - selection.start as isize) + ..(end as isize - selection.start as isize), + ); + } ranges.push(start + common_prefix_len..end); } else { common_prefix_len = 0; ranges.clear(); ranges.extend(selections.iter().map(|s| { if s.id == newest_selection.id { + range_to_replace = Some( + old_range.start.to_offset_utf16(&snapshot).0 as isize + - selection.start as isize + ..old_range.end.to_offset_utf16(&snapshot).0 as isize + - selection.start as isize, + ); old_range.clone() } else { s.start..s.end @@ -3232,6 +3241,11 @@ impl Editor { } let text = &text[common_prefix_len..]; + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); + self.transact(cx, |this, cx| { if let Some(mut snippet) = snippet { snippet.text = text.to_string(); @@ -3689,6 +3703,10 @@ impl Editor { self.report_copilot_event(Some(completion.uuid.clone()), true, cx) } + cx.emit(Event::InputHandled { + utf16_range_to_replace: None, + text: suggestion.text.to_string().into(), + }); self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx); cx.notify(); true @@ -8437,6 +8455,41 @@ impl Editor { pub fn inlay_hint_cache(&self) -> &InlayHintCache { &self.inlay_hint_cache } + + pub fn replay_insert_event( + &mut self, + text: &str, + relative_utf16_range: Option>, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } + if let Some(relative_utf16_range) = relative_utf16_range { + let selections = self.selections.all::(cx); + self.change_selections(None, cx, |s| { + let new_ranges = selections.into_iter().map(|range| { + let start = OffsetUtf16( + range + .head() + .0 + .saturating_add_signed(relative_utf16_range.start), + ); + let end = OffsetUtf16( + range + .head() + .0 + .saturating_add_signed(relative_utf16_range.end), + ); + start..end + }); + s.select_ranges(new_ranges); + }); + } + + self.handle_input(text, cx); + } } fn document_to_inlay_range( @@ -8525,6 +8578,10 @@ pub enum Event { InputIgnored { text: Arc, }, + InputHandled { + utf16_range_to_replace: Option>, + text: Arc, + }, ExcerptsAdded { buffer: ModelHandle, predecessor: ExcerptId, @@ -8742,29 +8799,51 @@ impl View for Editor { text: &str, cx: &mut ViewContext, ) { - self.transact(cx, |this, cx| { - if this.input_enabled { - let new_selected_ranges = if let Some(range_utf16) = range_utf16 { - let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); - Some(this.selection_replacement_ranges(range_utf16, cx)) - } else { - this.marked_text_ranges(cx) - }; + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } - if let Some(new_selected_ranges) = new_selected_ranges { - this.change_selections(None, cx, |selections| { - selections.select_ranges(new_selected_ranges) - }); - } + self.transact(cx, |this, cx| { + let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + this.marked_text_ranges(cx) + }; + + let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); + + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); + + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); } this.handle_input(text, cx); }); - if !self.input_enabled { - return; - } - if let Some(transaction) = self.ime_transaction { self.buffer.update(cx, |buffer, cx| { buffer.group_until_transaction(transaction, cx); @@ -8782,6 +8861,7 @@ impl View for Editor { cx: &mut ViewContext, ) { if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); return; } @@ -8806,6 +8886,29 @@ impl View for Editor { None }; + let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); + + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); + if let Some(ranges) = ranges_to_replace { this.change_selections(None, cx, |s| s.select_ranges(ranges)); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 74bd67e03a..f11639a770 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7807,7 +7807,7 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo /// Handle completion request passing a marked string specifying where the completion /// should be triggered from using '|' character, what range should be replaced, and what completions /// should be returned using '<' and '>' to delimit the range -fn handle_completion_request<'a>( +pub fn handle_completion_request<'a>( cx: &mut EditorLspTestContext<'a>, marked_string: &str, completions: Vec<&'static str>, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 90fe6ccc52..b7e34fda53 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,8 +32,8 @@ use gpui::{ json::{self, ToJson}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, text_layout::{self, Line, RunStyle, TextLayoutCache}, - AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, - MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, + AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad, + SizeConstraint, ViewContext, WindowContext, }; use itertools::Itertools; use json::json; @@ -131,7 +131,6 @@ impl EditorElement { } fn attach_mouse_handlers( - scene: &mut SceneBuilder, position_map: &Arc, has_popovers: bool, visible_bounds: RectF, @@ -141,124 +140,124 @@ impl EditorElement { cx: &mut ViewContext, ) { enum EditorElementMouseHandlers {} - scene.push_mouse_region( - MouseRegion::new::( - cx.view_id(), - cx.view_id(), - visible_bounds, - ) - .on_down(MouseButton::Left, { - let position_map = position_map.clone(); - move |event, editor, cx| { - if !Self::mouse_down( - editor, - event.platform_event, - position_map.as_ref(), - text_bounds, - gutter_bounds, - cx, - ) { - cx.propagate_event(); + let view_id = cx.view_id(); + cx.scene().push_mouse_region( + MouseRegion::new::(view_id, view_id, visible_bounds) + .on_down(MouseButton::Left, { + let position_map = position_map.clone(); + move |event, editor, cx| { + if !Self::mouse_down( + editor, + event.platform_event, + position_map.as_ref(), + text_bounds, + gutter_bounds, + cx, + ) { + cx.propagate_event(); + } } - } - }) - .on_down(MouseButton::Right, { - let position_map = position_map.clone(); - move |event, editor, cx| { - if !Self::mouse_right_down( - editor, - event.position, - position_map.as_ref(), - text_bounds, - cx, - ) { - cx.propagate_event(); + }) + .on_down(MouseButton::Right, { + let position_map = position_map.clone(); + move |event, editor, cx| { + if !Self::mouse_right_down( + editor, + event.position, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propagate_event(); + } } - } - }) - .on_up(MouseButton::Left, { - let position_map = position_map.clone(); - move |event, editor, cx| { - if !Self::mouse_up( - editor, - event.position, - event.cmd, - event.shift, - event.alt, - position_map.as_ref(), - text_bounds, - cx, - ) { - cx.propagate_event() - } - } - }) - .on_drag(MouseButton::Left, { - let position_map = position_map.clone(); - move |event, editor, cx| { - if event.end { - return; + }) + .on_up(MouseButton::Left, { + let position_map = position_map.clone(); + move |event, editor, cx| { + if !Self::mouse_up( + editor, + event.position, + event.cmd, + event.shift, + event.alt, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propagate_event() + } } + }) + .on_drag(MouseButton::Left, { + let position_map = position_map.clone(); + move |event, editor, cx| { + if event.end { + return; + } - if !Self::mouse_dragged( - editor, - event.platform_event, - position_map.as_ref(), - text_bounds, - cx, - ) { - cx.propagate_event() + if !Self::mouse_dragged( + editor, + event.platform_event, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propagate_event() + } } - } - }) - .on_move({ - let position_map = position_map.clone(); - move |event, editor, cx| { - if !Self::mouse_moved( - editor, - event.platform_event, - &position_map, - text_bounds, - cx, - ) { - cx.propagate_event() + }) + .on_move({ + let position_map = position_map.clone(); + move |event, editor, cx| { + if !Self::mouse_moved( + editor, + event.platform_event, + &position_map, + text_bounds, + cx, + ) { + cx.propagate_event() + } } - } - }) - .on_move_out(move |_, editor: &mut Editor, cx| { - if has_popovers { - hide_hover(editor, cx); - } - }) - .on_scroll({ - let position_map = position_map.clone(); - move |event, editor, cx| { - if !Self::scroll( - editor, - event.position, - *event.delta.raw(), - event.delta.precise(), - &position_map, - bounds, - cx, - ) { - cx.propagate_event() + }) + .on_move_out(move |_, editor: &mut Editor, cx| { + if has_popovers { + hide_hover(editor, cx); } - } - }), + }) + .on_scroll({ + let position_map = position_map.clone(); + move |event, editor, cx| { + if !Self::scroll( + editor, + event.position, + *event.delta.raw(), + event.delta.precise(), + &position_map, + bounds, + cx, + ) { + cx.propagate_event() + } + } + }), ); enum GutterHandlers {} - scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), cx.view_id() + 1, gutter_bounds) - .on_hover(|hover, editor: &mut Editor, cx| { + let view_id = cx.view_id(); + let region_id = cx.view_id() + 1; + cx.scene().push_mouse_region( + MouseRegion::new::(view_id, region_id, gutter_bounds).on_hover( + |hover, editor: &mut Editor, cx| { editor.gutter_hover( &GutterHover { hovered: hover.started, }, cx, ); - }), + }, + ), ) } @@ -528,24 +527,24 @@ impl EditorElement { fn paint_background( &self, - scene: &mut SceneBuilder, gutter_bounds: RectF, text_bounds: RectF, layout: &LayoutState, + cx: &mut ViewContext, ) { let bounds = gutter_bounds.union_rect(text_bounds); let scroll_top = layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height; - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: gutter_bounds, background: Some(self.style.gutter_background), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: text_bounds, background: Some(self.style.background), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); @@ -570,10 +569,10 @@ impl EditorElement { bounds.width(), layout.position_map.line_height * (end_row - start_row + 1) as f32, ); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(origin, size), background: Some(self.style.active_line_background), - border: Border::default(), + border: Border::default().into(), corner_radii: Default::default(), }); } @@ -590,10 +589,10 @@ impl EditorElement { bounds.width(), layout.position_map.line_height * highlighted_rows.len() as f32, ); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(origin, size), background: Some(self.style.highlighted_line_background), - border: Border::default(), + border: Border::default().into(), corner_radii: Default::default(), }); } @@ -617,13 +616,13 @@ impl EditorElement { } else { self.style.wrap_guide }; - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new( vec2f(x, text_bounds.origin_y()), vec2f(1., text_bounds.height()), ), background: Some(color), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); } @@ -632,12 +631,11 @@ impl EditorElement { fn paint_gutter( &mut self, - scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let line_height = layout.position_map.line_height; @@ -650,7 +648,7 @@ impl EditorElement { ); if show_gutter { - Self::paint_diff_hunks(scene, bounds, layout, cx); + Self::paint_diff_hunks(bounds, layout, cx); } for (ix, line) in layout.line_number_layouts.iter().enumerate() { @@ -661,7 +659,7 @@ impl EditorElement { ix as f32 * line_height - (scroll_top % line_height), ); - line.paint(scene, line_origin, visible_bounds, line_height, cx); + line.paint(line_origin, visible_bounds, line_height, cx); } } @@ -678,7 +676,7 @@ impl EditorElement { let indicator_origin = bounds.origin() + position + centering_offset; - indicator.paint(scene, indicator_origin, visible_bounds, editor, cx); + indicator.paint(indicator_origin, visible_bounds, editor, cx); } } @@ -687,22 +685,11 @@ impl EditorElement { let mut y = *row as f32 * line_height - scroll_top; x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; y += (line_height - indicator.size().y()) / 2.; - indicator.paint( - scene, - bounds.origin() + vec2f(x, y), - visible_bounds, - editor, - cx, - ); + indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx); } } - fn paint_diff_hunks( - scene: &mut SceneBuilder, - bounds: RectF, - layout: &mut LayoutState, - cx: &mut ViewContext, - ) { + fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut ViewContext) { let diff_style = &theme::current(cx).editor.diff.clone(); let line_height = layout.position_map.line_height; @@ -721,10 +708,10 @@ impl EditorElement { let highlight_size = vec2f(width * 2., end_y - start_y); let highlight_bounds = RectF::new(highlight_origin, highlight_size); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: highlight_bounds, background: Some(diff_style.modified), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (1. * line_height).into(), }); @@ -754,10 +741,10 @@ impl EditorElement { let highlight_size = vec2f(width * 2., end_y - start_y); let highlight_bounds = RectF::new(highlight_origin, highlight_size); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: highlight_bounds, background: Some(diff_style.deleted), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (1. * line_height).into(), }); @@ -776,10 +763,10 @@ impl EditorElement { let highlight_size = vec2f(width * 2., end_y - start_y); let highlight_bounds = RectF::new(highlight_origin, highlight_size); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: highlight_bounds, background: Some(color), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (diff_style.corner_radius * line_height).into(), }); } @@ -787,12 +774,11 @@ impl EditorElement { fn paint_text( &mut self, - scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let style = &self.style; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -804,9 +790,9 @@ impl EditorElement { let line_end_overshoot = 0.15 * layout.position_map.line_height; let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; - scene.push_layer(Some(bounds)); + cx.scene().push_layer(Some(bounds)); - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds, style: if !editor.link_go_to_definition_state.definitions.is_empty() { CursorStyle::PointingHand @@ -819,7 +805,6 @@ impl EditorElement { self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height; for (id, range, color) in layout.fold_ranges.iter() { self.paint_highlighted_range( - scene, range.clone(), *color, fold_corner_radius, @@ -829,6 +814,7 @@ impl EditorElement { scroll_top, scroll_left, bounds, + cx, ); for bound in range_to_bounds( @@ -840,7 +826,7 @@ impl EditorElement { line_end_overshoot, &layout.position_map, ) { - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds: bound, style: CursorStyle::PointingHand, }); @@ -851,8 +837,9 @@ impl EditorElement { .to_point(&layout.position_map.snapshot.display_snapshot) .row; - scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), *id as usize, bound) + let view_id = cx.view_id(); + cx.scene().push_mouse_region( + MouseRegion::new::(view_id, *id as usize, bound) .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| { editor.unfold_at(&UnfoldAt { buffer_row }, cx) }) @@ -864,7 +851,6 @@ impl EditorElement { for (range, color) in &layout.highlighted_ranges { self.paint_highlighted_range( - scene, range.clone(), *color, 0., @@ -874,6 +860,7 @@ impl EditorElement { scroll_top, scroll_left, bounds, + cx, ); } @@ -891,7 +878,6 @@ impl EditorElement { for selection in selections { self.paint_highlighted_range( - scene, selection.range.clone(), selection_style.selection, corner_radius, @@ -901,6 +887,7 @@ impl EditorElement { scroll_top, scroll_left, bounds, + cx, ); if selection.is_local && !selection.range.is_empty() { @@ -980,7 +967,6 @@ impl EditorElement { layout, row, scroll_top, - scene, content_origin, scroll_left, visible_text_bounds, @@ -992,14 +978,14 @@ impl EditorElement { } } - scene.paint_layer(Some(bounds), |scene| { - for cursor in cursors { - cursor.paint(scene, content_origin, cx); - } - }); + cx.scene().push_layer(Some(bounds)); + for cursor in cursors { + cursor.paint(content_origin, cx); + } + cx.scene().pop_layer(); if let Some((position, context_menu)) = layout.context_menu.as_mut() { - scene.push_stacking_context(None, None); + cx.scene().push_stacking_context(None, None); let cursor_row_layout = &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; @@ -1019,18 +1005,17 @@ impl EditorElement { } context_menu.paint( - scene, list_origin, RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor editor, cx, ); - scene.pop_stacking_context(); + cx.scene().pop_stacking_context(); } if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() { - scene.push_stacking_context(None, None); + cx.scene().push_stacking_context(None, None); // This is safe because we check on layout whether the required row is available let hovered_row_layout = @@ -1061,7 +1046,6 @@ impl EditorElement { } hover_popover.paint( - scene, popover_origin, RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor editor, @@ -1083,7 +1067,6 @@ impl EditorElement { } hover_popover.paint( - scene, popover_origin, RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor editor, @@ -1094,10 +1077,10 @@ impl EditorElement { } } - scene.pop_stacking_context(); + cx.scene().pop_stacking_context(); } - scene.pop_layer(); + cx.scene().pop_layer(); } fn scrollbar_left(&self, bounds: &RectF) -> f32 { @@ -1106,11 +1089,10 @@ impl EditorElement { fn paint_scrollbar( &mut self, - scene: &mut SceneBuilder, bounds: RectF, layout: &mut LayoutState, - cx: &mut ViewContext, editor: &Editor, + cx: &mut ViewContext, ) { enum ScrollbarMouseHandlers {} if layout.mode != EditorMode::Full { @@ -1147,9 +1129,9 @@ impl EditorElement { let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom)); if layout.show_scrollbars { - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: track_bounds, - border: style.track.border, + border: style.track.border.into(), background: style.track.background_color, ..Default::default() }); @@ -1177,10 +1159,10 @@ impl EditorElement { } let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y)); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds, background: Some(color), - border, + border: border.into(), corner_radii: style.thumb.corner_radii.into(), }) }; @@ -1237,29 +1219,30 @@ impl EditorElement { left: true, }; - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds, background: Some(color), - border, + border: border.into(), corner_radii: style.thumb.corner_radii.into(), }) } } - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: thumb_bounds, - border: style.thumb.border, + border: style.thumb.border.into(), background: style.thumb.background_color, corner_radii: style.thumb.corner_radii.into(), }); } - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds: track_bounds, style: CursorStyle::Arrow, }); - scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), cx.view_id(), track_bounds) + let region_id = cx.view_id(); + cx.scene().push_mouse_region( + MouseRegion::new::(region_id, region_id, track_bounds) .on_move(move |event, editor: &mut Editor, cx| { if event.pressed_button.is_none() { editor.scroll_manager.show_scrollbar(cx); @@ -1305,7 +1288,6 @@ impl EditorElement { #[allow(clippy::too_many_arguments)] fn paint_highlighted_range( &self, - scene: &mut SceneBuilder, range: Range, color: Color, corner_radius: f32, @@ -1315,6 +1297,7 @@ impl EditorElement { scroll_top: f32, scroll_left: f32, bounds: RectF, + cx: &mut ViewContext, ) { let start_row = layout.visible_display_row_range.start; let end_row = layout.visible_display_row_range.end; @@ -1358,18 +1341,17 @@ impl EditorElement { .collect(), }; - highlighted_range.paint(bounds, scene); + highlighted_range.paint(bounds, cx); } } fn paint_blocks( &mut self, - scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_left = scroll_position.x() * layout.position_map.em_width; @@ -1384,9 +1366,7 @@ impl EditorElement { if !matches!(block.style, BlockStyle::Sticky) { origin += vec2f(-scroll_left, 0.); } - block - .element - .paint(scene, origin, visible_bounds, editor, cx); + block.element.paint(origin, visible_bounds, editor, cx); } } @@ -1690,7 +1670,7 @@ impl EditorElement { style: &EditorStyle, line_layouts: &[LineWithInvisibles], editor: &mut Editor, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (f32, Vec) { let mut block_id = 0; let scroll_x = snapshot.scroll_anchor.offset.x(); @@ -2022,7 +2002,6 @@ impl LineWithInvisibles { layout: &LayoutState, row: u32, scroll_top: f32, - scene: &mut SceneBuilder, content_origin: Vector2F, scroll_left: f32, visible_text_bounds: RectF, @@ -2035,7 +2014,6 @@ impl LineWithInvisibles { let line_y = row as f32 * line_height - scroll_top; self.line.paint( - scene, content_origin + vec2f(-scroll_left, line_y), visible_text_bounds, line_height, @@ -2049,7 +2027,6 @@ impl LineWithInvisibles { scroll_left, line_y, row, - scene, visible_bounds, line_height, whitespace_setting, @@ -2065,7 +2042,6 @@ impl LineWithInvisibles { scroll_left: f32, line_y: f32, row: u32, - scene: &mut SceneBuilder, visible_bounds: RectF, line_height: f32, whitespace_setting: ShowWhitespaceSetting, @@ -2097,7 +2073,7 @@ impl LineWithInvisibles { continue; } } - invisible_symbol.paint(scene, origin, visible_bounds, line_height, cx); + invisible_symbol.paint(origin, visible_bounds, line_height, cx); } } } @@ -2116,7 +2092,7 @@ impl Element for EditorElement { &mut self, constraint: SizeConstraint, editor: &mut Editor, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.max; if size.x().is_infinite() { @@ -2590,15 +2566,14 @@ impl Element for EditorElement { fn paint( &mut self, - scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, layout: &mut Self::LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - scene.push_layer(Some(visible_bounds)); + cx.scene().push_layer(Some(visible_bounds)); let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size); let text_bounds = RectF::new( @@ -2607,7 +2582,6 @@ impl Element for EditorElement { ); Self::attach_mouse_handlers( - scene, &layout.position_map, layout.hover_popovers.is_some(), visible_bounds, @@ -2617,20 +2591,19 @@ impl Element for EditorElement { cx, ); - self.paint_background(scene, gutter_bounds, text_bounds, layout); + self.paint_background(gutter_bounds, text_bounds, layout, cx); if layout.gutter_size.x() > 0. { - self.paint_gutter(scene, gutter_bounds, visible_bounds, layout, editor, cx); + self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx); } - self.paint_text(scene, text_bounds, visible_bounds, layout, editor, cx); + self.paint_text(text_bounds, visible_bounds, layout, editor, cx); - scene.push_layer(Some(bounds)); + cx.scene().push_layer(Some(bounds)); if !layout.blocks.is_empty() { - self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx); + self.paint_blocks(bounds, visible_bounds, layout, editor, cx); } - self.paint_scrollbar(scene, bounds, layout, cx, &editor); - scene.pop_layer(); - - scene.pop_layer(); + self.paint_scrollbar(bounds, layout, &editor, cx); + cx.scene().pop_layer(); + cx.scene().pop_layer(); } fn rect_for_text_range( @@ -2873,7 +2846,7 @@ impl Cursor { ) } - pub fn paint(&self, scene: &mut SceneBuilder, origin: Vector2F, cx: &mut WindowContext) { + pub fn paint(&self, origin: Vector2F, cx: &mut WindowContext) { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)), CursorShape::Block | CursorShape::Hollow => RectF::new( @@ -2888,14 +2861,14 @@ impl Cursor { //Draw background or border quad if matches!(self.shape, CursorShape::Hollow) { - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds, background: None, - border: Border::all(1., self.color), + border: Border::all(1., self.color).into(), corner_radii: Default::default(), }); } else { - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds, background: Some(self.color), border: Default::default(), @@ -2904,7 +2877,7 @@ impl Cursor { } if let Some(block_text) = &self.block_text { - block_text.paint(scene, self.origin + origin, bounds, self.line_height, cx); + block_text.paint(self.origin + origin, bounds, self.line_height, cx); } } @@ -2929,17 +2902,17 @@ pub struct HighlightedRangeLine { } impl HighlightedRange { - pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) { + pub fn paint(&self, bounds: RectF, cx: &mut WindowContext) { if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { - self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene); + self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx); self.paint_lines( self.start_y + self.line_height, &self.lines[1..], bounds, - scene, + cx, ); } else { - self.paint_lines(self.start_y, &self.lines, bounds, scene); + self.paint_lines(self.start_y, &self.lines, bounds, cx); } } @@ -2948,7 +2921,7 @@ impl HighlightedRange { start_y: f32, lines: &[HighlightedRangeLine], bounds: RectF, - scene: &mut SceneBuilder, + cx: &mut WindowContext, ) { if lines.is_empty() { return; @@ -3046,7 +3019,7 @@ impl HighlightedRange { } path.line_to(first_top_right - top_curve_width); - scene.push_path(path.build(self.color, Some(bounds))); + cx.scene().push_path(path.build(self.color, Some(bounds))); } } @@ -3204,18 +3177,10 @@ mod tests { Point::new(5, 6)..Point::new(6, 0), ]); }); - let mut new_parents = Default::default(); - let mut notify_views_if_parents_change = Default::default(); - let mut layout_cx = LayoutContext::new( - cx, - &mut new_parents, - &mut notify_views_if_parents_change, - false, - ); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); assert_eq!(state.selections.len(), 1); @@ -3296,18 +3261,10 @@ mod tests { DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0), ]); }); - let mut new_parents = Default::default(); - let mut notify_views_if_parents_change = Default::default(); - let mut layout_cx = LayoutContext::new( - cx, - &mut new_parents, - &mut notify_views_if_parents_change, - false, - ); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); @@ -3363,18 +3320,10 @@ mod tests { let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (size, mut state) = editor.update(cx, |editor, cx| { - let mut new_parents = Default::default(); - let mut notify_views_if_parents_change = Default::default(); - let mut layout_cx = LayoutContext::new( - cx, - &mut new_parents, - &mut notify_views_if_parents_change, - false, - ); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); @@ -3389,17 +3338,9 @@ mod tests { ); // Don't panic. - let mut scene = SceneBuilder::new(1.0); let bounds = RectF::new(Default::default(), size); editor.update(cx, |editor, cx| { - element.paint( - &mut scene, - bounds, - bounds, - &mut state, - editor, - &mut PaintContext::new(cx), - ); + element.paint(bounds, bounds, &mut state, editor, cx); }); } @@ -3567,18 +3508,10 @@ mod tests { editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); - let mut new_parents = Default::default(); - let mut notify_views_if_parents_change = Default::default(); - let mut layout_cx = LayoutContext::new( - cx, - &mut new_parents, - &mut notify_views_if_parents_change, - false, - ); element.layout( SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)), editor, - &mut layout_cx, + cx, ) }); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 2f278ce262..3d5b1d2113 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -691,15 +691,15 @@ impl InfoPopover { .with_highlights(rendered_content.highlights.clone()) .with_custom_runs( rendered_content.region_ranges.clone(), - move |ix, bounds, scene, _| { + move |ix, bounds, cx| { region_id += 1; let region = regions[ix].clone(); if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); - scene.push_mouse_region( + cx.scene().push_mouse_region( MouseRegion::new::(view_id, region_id, bounds) .on_click::( MouseButton::Left, @@ -708,7 +708,7 @@ impl InfoPopover { ); } if region.code { - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds, background: Some(code_span_background_color), border: Default::default(), diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d999872592..b31c9dcd1b 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -16,7 +16,7 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, SelectionGoal, }; -use project::{FormatTrigger, Item as _, Project, ProjectPath}; +use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use smallvec::SmallVec; use std::{ @@ -26,6 +26,7 @@ use std::{ iter, ops::Range, path::{Path, PathBuf}, + sync::Arc, }; use text::Selection; use util::{ @@ -978,7 +979,26 @@ impl SearchableItem for Editor { } self.change_selections(None, cx, |s| s.select_ranges(ranges)); } + fn replace( + &mut self, + identifier: &Self::Match, + query: &SearchQuery, + cx: &mut ViewContext, + ) { + let text = self.buffer.read(cx); + let text = text.snapshot(cx); + let text = text.text_for_range(identifier.clone()).collect::>(); + let text: Cow<_> = if text.len() == 1 { + text.first().cloned().unwrap().into() + } else { + let joined_chunks = text.join(""); + joined_chunks.into() + }; + if let Some(replacement) = query.replacement(&text) { + self.edit([(identifier.clone(), Arc::from(&*replacement))], cx); + } + } fn match_index_for_direction( &mut self, matches: &Vec>, @@ -1030,7 +1050,7 @@ impl SearchableItem for Editor { fn find_matches( &mut self, - query: project::search::SearchQuery, + query: Arc, cx: &mut ViewContext, ) -> Task>> { let buffer = self.buffer().read(cx).snapshot(cx); diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index a717223f6d..0b8a29e114 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -13,7 +13,7 @@ use gpui::{ use isahc::Request; use language::Buffer; use postage::prelude::Stream; -use project::Project; +use project::{search::SearchQuery, Project}; use regex::Regex; use serde::Serialize; use smallvec::SmallVec; @@ -418,10 +418,13 @@ impl SearchableItem for FeedbackEditor { self.editor .update(cx, |e, cx| e.select_matches(matches, cx)) } - + fn replace(&mut self, matches: &Self::Match, query: &SearchQuery, cx: &mut ViewContext) { + self.editor + .update(cx, |e, cx| e.replace(matches, query, cx)); + } fn find_matches( &mut self, - query: project::search::SearchQuery, + query: Arc, cx: &mut ViewContext, ) -> Task> { self.editor diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 523d6e8a5c..c2d8cc52b2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1528,8 +1528,13 @@ mod tests { let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); active_pane .update(cx, |pane, cx| { - pane.close_active_item(&workspace::CloseActiveItem, cx) - .unwrap() + pane.close_active_item( + &workspace::CloseActiveItem { + save_behavior: None, + }, + cx, + ) + .unwrap() }) .await .unwrap(); diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 24397f6193..705feb351d 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -48,7 +48,8 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true smol.workspace = true -taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] } +taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" } +thiserror.workspace = true time.workspace = true tiny-skia = "0.5" usvg = { version = "0.14", features = [] } diff --git a/crates/gpui/examples/corner_radii.rs b/crates/gpui/examples/corner_radii.rs index 1f33917529..75ea3aeec6 100644 --- a/crates/gpui/examples/corner_radii.rs +++ b/crates/gpui/examples/corner_radii.rs @@ -42,29 +42,28 @@ impl gpui::Element for CornersElement { &mut self, constraint: gpui::SizeConstraint, _: &mut V, - _: &mut gpui::LayoutContext, + _: &mut gpui::ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { (constraint.max, ()) } fn paint( &mut self, - scene: &mut gpui::SceneBuilder, bounds: pathfinder_geometry::rect::RectF, _: pathfinder_geometry::rect::RectF, _: &mut Self::LayoutState, _: &mut V, - _: &mut gpui::PaintContext, + cx: &mut gpui::ViewContext, ) -> Self::PaintState { - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds, background: Some(Color::white()), ..Default::default() }); - scene.push_layer(None); + cx.scene().push_layer(None); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)), background: Some(Color::red()), border: Default::default(), @@ -74,7 +73,7 @@ impl gpui::Element for CornersElement { }, }); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)), background: Some(Color::green()), border: Default::default(), @@ -84,7 +83,7 @@ impl gpui::Element for CornersElement { }, }); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)), background: Some(Color::blue()), border: Default::default(), @@ -94,7 +93,7 @@ impl gpui::Element for CornersElement { }, }); - scene.push_quad(Quad { + cx.scene().push_quad(Quad { bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)), background: Some(Color::yellow()), border: Default::default(), @@ -104,7 +103,7 @@ impl gpui::Element for CornersElement { }, }); - scene.push_shadow(Shadow { + cx.scene().push_shadow(Shadow { bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)), corner_radii: gpui::scene::CornerRadii { bottom_right: 20., @@ -114,8 +113,8 @@ impl gpui::Element for CornersElement { color: Color::black(), }); - scene.push_layer(None); - scene.push_quad(Quad { + cx.scene().push_layer(None); + cx.scene().push_quad(Quad { bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)), background: Some(Color::red()), border: Default::default(), @@ -125,8 +124,8 @@ impl gpui::Element for CornersElement { }, }); - scene.pop_layer(); - scene.pop_layer(); + cx.scene().pop_layer(); + cx.scene().pop_layer(); } fn rect_for_text_range( diff --git a/crates/gpui/examples/text.rs b/crates/gpui/examples/text.rs index bda70a49dc..bc62d75ec2 100644 --- a/crates/gpui/examples/text.rs +++ b/crates/gpui/examples/text.rs @@ -62,12 +62,12 @@ impl gpui::View for TextView { }, ) .with_highlights(vec![(17..26, underline), (34..40, underline)]) - .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, scene, _| { - scene.push_cursor_region(CursorRegion { + .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| { + cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); - scene.push_mouse_region( + cx.scene().push_mouse_region( MouseRegion::new::(view_id, ix, bounds).on_click::( MouseButton::Left, move |_, _, _| { diff --git a/crates/gpui/playground/Cargo.toml b/crates/gpui/playground/Cargo.toml deleted file mode 100644 index 3e5a5e5606..0000000000 --- a/crates/gpui/playground/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "playground" -version = "0.1.0" -edition = "2021" -publish = false - -[[bin]] -name = "playground" -path = "src/playground.rs" - -[dependencies] -anyhow.workspace = true -derive_more.workspace = true -gpui = { path = ".." } -log.workspace = true -playground_macros = { path = "../playground_macros" } -parking_lot.workspace = true -refineable.workspace = true -serde.workspace = true -simplelog = "0.9" -smallvec.workspace = true -taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] } -util = { path = "../../util" } - -[dev-dependencies] -gpui = { path = "..", features = ["test-support"] } diff --git a/crates/gpui/playground/src/div.rs b/crates/gpui/playground/src/div.rs deleted file mode 100644 index 8efe359025..0000000000 --- a/crates/gpui/playground/src/div.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::{ - element::{AnyElement, Element, Layout, ParentElement}, - interactive::{InteractionHandlers, Interactive}, - layout_context::LayoutContext, - paint_context::PaintContext, - style::{Style, StyleHelpers, StyleRefinement, Styleable}, -}; -use anyhow::Result; -use gpui::LayoutId; -use smallvec::SmallVec; - -pub struct Div { - style: StyleRefinement, - handlers: InteractionHandlers, - children: SmallVec<[AnyElement; 2]>, -} - -pub fn div() -> Div { - Div { - style: Default::default(), - handlers: Default::default(), - children: Default::default(), - } -} - -impl Element for Div { - type Layout = (); - - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result> - where - Self: Sized, - { - let children = self - .children - .iter_mut() - .map(|child| child.layout(view, cx)) - .collect::>>()?; - - cx.add_layout_node(self.style(), (), children) - } - - fn paint(&mut self, view: &mut V, layout: &mut Layout, cx: &mut PaintContext) - where - Self: Sized, - { - let style = self.style(); - - style.paint_background::(layout, cx); - for child in &mut self.children { - child.paint(view, cx); - } - } -} - -impl Styleable for Div { - type Style = Style; - - fn declared_style(&mut self) -> &mut StyleRefinement { - &mut self.style - } -} - -impl StyleHelpers for Div {} - -impl Interactive for Div { - fn interaction_handlers(&mut self) -> &mut InteractionHandlers { - &mut self.handlers - } -} - -impl ParentElement for Div { - fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { - &mut self.children - } -} - -#[test] -fn test() { - // let elt = div().w_auto(); -} - -// trait Element { -// type Style; - -// fn layout() -// } - -// trait Stylable: Element { -// type Style; - -// fn with_style(self, style: Self::Style) -> Self; -// } - -// pub struct HoverStyle { -// default: S, -// hovered: S, -// } - -// struct Hover> { -// child: C, -// style: HoverStyle, -// } - -// impl> Hover { -// fn new(child: C, style: HoverStyle) -> Self { -// Self { child, style } -// } -// } diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs deleted file mode 100644 index 082f3b02b0..0000000000 --- a/crates/gpui/playground/src/element.rs +++ /dev/null @@ -1,158 +0,0 @@ -use anyhow::Result; -use derive_more::{Deref, DerefMut}; -use gpui::{geometry::rect::RectF, EngineLayout}; -use smallvec::SmallVec; -use std::marker::PhantomData; -use util::ResultExt; - -pub use crate::layout_context::LayoutContext; -pub use crate::paint_context::PaintContext; - -type LayoutId = gpui::LayoutId; - -pub trait Element: 'static { - type Layout; - - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result> - where - Self: Sized; - - fn paint( - &mut self, - view: &mut V, - layout: &mut Layout, - cx: &mut PaintContext, - ) where - Self: Sized; - - fn into_any(self) -> AnyElement - where - Self: 'static + Sized, - { - AnyElement(Box::new(ElementState { - element: self, - layout: None, - })) - } -} - -/// Used to make ElementState into a trait object, so we can wrap it in AnyElement. -trait ElementStateObject { - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result; - fn paint(&mut self, view: &mut V, cx: &mut PaintContext); -} - -/// A wrapper around an element that stores its layout state. -struct ElementState> { - element: E, - layout: Option>, -} - -/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects -impl> ElementStateObject for ElementState { - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { - let layout = self.element.layout(view, cx)?; - let layout_id = layout.id; - self.layout = Some(layout); - Ok(layout_id) - } - - fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { - let layout = self.layout.as_mut().expect("paint called before layout"); - if layout.engine_layout.is_none() { - layout.engine_layout = cx.computed_layout(layout.id).log_err() - } - self.element.paint(view, layout, cx) - } -} - -/// A dynamic element. -pub struct AnyElement(Box>); - -impl AnyElement { - pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { - self.0.layout(view, cx) - } - - pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { - self.0.paint(view, cx) - } -} - -#[derive(Deref, DerefMut)] -pub struct Layout { - id: LayoutId, - engine_layout: Option, - #[deref] - #[deref_mut] - element_data: D, - view_type: PhantomData, -} - -impl Layout { - pub fn new(id: LayoutId, element_data: D) -> Self { - Self { - id, - engine_layout: None, - element_data: element_data, - view_type: PhantomData, - } - } - - pub fn bounds(&mut self, cx: &mut PaintContext) -> RectF { - self.engine_layout(cx).bounds - } - - pub fn order(&mut self, cx: &mut PaintContext) -> u32 { - self.engine_layout(cx).order - } - - fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout { - self.engine_layout - .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default()) - } -} - -impl Layout>> { - pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { - let mut element = self.element_data.take().unwrap(); - element.paint(view, cx); - self.element_data = Some(element); - } -} - -pub trait ParentElement { - fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; - - fn child(mut self, child: impl IntoElement) -> Self - where - Self: Sized, - { - self.children_mut().push(child.into_element().into_any()); - self - } - - fn children(mut self, children: I) -> Self - where - I: IntoIterator, - E: IntoElement, - Self: Sized, - { - self.children_mut().extend( - children - .into_iter() - .map(|child| child.into_element().into_any()), - ); - self - } -} - -pub trait IntoElement { - type Element: Element; - - fn into_element(self) -> Self::Element; -} diff --git a/crates/gpui/playground/src/hoverable.rs b/crates/gpui/playground/src/hoverable.rs deleted file mode 100644 index 5545155a60..0000000000 --- a/crates/gpui/playground/src/hoverable.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::{ - element::{Element, Layout}, - layout_context::LayoutContext, - paint_context::PaintContext, - style::{StyleRefinement, Styleable}, -}; -use anyhow::Result; -use gpui::platform::MouseMovedEvent; -use refineable::Refineable; -use std::{cell::Cell, marker::PhantomData}; - -pub struct Hoverable + Styleable> { - hovered: Cell, - child_style: StyleRefinement, - hovered_style: StyleRefinement, - child: E, - view_type: PhantomData, -} - -pub fn hoverable + Styleable>(mut child: E) -> Hoverable { - Hoverable { - hovered: Cell::new(false), - child_style: child.declared_style().clone(), - hovered_style: Default::default(), - child, - view_type: PhantomData, - } -} - -impl + Styleable> Styleable for Hoverable { - type Style = E::Style; - - fn declared_style(&mut self) -> &mut crate::style::StyleRefinement { - self.child.declared_style() - } -} - -impl + Styleable> Element for Hoverable { - type Layout = E::Layout; - - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result> - where - Self: Sized, - { - self.child.layout(view, cx) - } - - fn paint( - &mut self, - view: &mut V, - layout: &mut Layout, - cx: &mut PaintContext, - ) where - Self: Sized, - { - if self.hovered.get() { - // If hovered, refine the child's style with this element's style. - self.child.declared_style().refine(&self.hovered_style); - } else { - // Otherwise, set the child's style back to its original style. - *self.child.declared_style() = self.child_style.clone(); - } - - let bounds = layout.bounds(cx); - let order = layout.order(cx); - self.hovered.set(bounds.contains_point(cx.mouse_position())); - let was_hovered = self.hovered.clone(); - cx.on_event(order, move |view, event: &MouseMovedEvent, cx| { - let is_hovered = bounds.contains_point(event.position); - if is_hovered != was_hovered.get() { - was_hovered.set(is_hovered); - cx.repaint(); - } - }); - } -} diff --git a/crates/gpui/playground/src/interactive.rs b/crates/gpui/playground/src/interactive.rs deleted file mode 100644 index 8debcb1692..0000000000 --- a/crates/gpui/playground/src/interactive.rs +++ /dev/null @@ -1,34 +0,0 @@ -use gpui::{platform::MouseMovedEvent, EventContext}; -use smallvec::SmallVec; -use std::rc::Rc; - -pub trait Interactive { - fn interaction_handlers(&mut self) -> &mut InteractionHandlers; - - fn on_mouse_move(mut self, handler: H) -> Self - where - H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext), - Self: Sized, - { - self.interaction_handlers() - .mouse_moved - .push(Rc::new(move |view, event, hit_test, cx| { - handler(view, event, hit_test, cx); - cx.bubble - })); - self - } -} - -pub struct InteractionHandlers { - mouse_moved: - SmallVec<[Rc) -> bool>; 2]>, -} - -impl Default for InteractionHandlers { - fn default() -> Self { - Self { - mouse_moved: Default::default(), - } - } -} diff --git a/crates/gpui/playground/src/layout_context.rs b/crates/gpui/playground/src/layout_context.rs deleted file mode 100644 index 7c9f13e7f0..0000000000 --- a/crates/gpui/playground/src/layout_context.rs +++ /dev/null @@ -1,54 +0,0 @@ -use anyhow::{anyhow, Result}; -use derive_more::{Deref, DerefMut}; -pub use gpui::LayoutContext as LegacyLayoutContext; -use gpui::{RenderContext, ViewContext}; -pub use taffy::tree::NodeId; - -use crate::{element::Layout, style::Style}; - -#[derive(Deref, DerefMut)] -pub struct LayoutContext<'a, 'b, 'c, 'd, V> { - #[deref] - #[deref_mut] - pub(crate) legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>, -} - -impl<'a, 'b, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, '_, '_, V> { - fn text_style(&self) -> gpui::fonts::TextStyle { - self.legacy_cx.text_style() - } - - fn push_text_style(&mut self, style: gpui::fonts::TextStyle) { - self.legacy_cx.push_text_style(style) - } - - fn pop_text_style(&mut self) { - self.legacy_cx.pop_text_style() - } - - fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> { - &mut self.view_context - } -} - -impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> { - pub fn new(legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>) -> Self { - Self { legacy_cx } - } - - pub fn add_layout_node( - &mut self, - style: Style, - element_data: D, - children: impl IntoIterator, - ) -> Result> { - let rem_size = self.rem_pixels(); - let id = self - .legacy_cx - .layout_engine() - .ok_or_else(|| anyhow!("no layout engine"))? - .add_node(style.to_taffy(rem_size), children)?; - - Ok(Layout::new(id, element_data)) - } -} diff --git a/crates/gpui/playground/src/paint_context.rs b/crates/gpui/playground/src/paint_context.rs deleted file mode 100644 index d853aff7f8..0000000000 --- a/crates/gpui/playground/src/paint_context.rs +++ /dev/null @@ -1,71 +0,0 @@ -use anyhow::{anyhow, Result}; -use derive_more::{Deref, DerefMut}; -use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext}; -pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext}; -use std::{any::TypeId, rc::Rc}; -pub use taffy::tree::NodeId; - -#[derive(Deref, DerefMut)] -pub struct PaintContext<'a, 'b, 'c, 'd, V> { - #[deref] - #[deref_mut] - pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>, - pub(crate) scene: &'d mut gpui::SceneBuilder, -} - -impl<'a, 'b, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, '_, '_, V> { - fn text_style(&self) -> gpui::fonts::TextStyle { - self.legacy_cx.text_style() - } - - fn push_text_style(&mut self, style: gpui::fonts::TextStyle) { - self.legacy_cx.push_text_style(style) - } - - fn pop_text_style(&mut self) { - self.legacy_cx.pop_text_style() - } - - fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> { - &mut self.view_context - } -} - -impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> { - pub fn new( - legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>, - scene: &'d mut gpui::SceneBuilder, - ) -> Self { - Self { legacy_cx, scene } - } - - pub fn on_event( - &mut self, - order: u32, - handler: impl Fn(&mut V, &E, &mut ViewContext) + 'static, - ) { - let view = self.weak_handle(); - - self.scene.event_handlers.push(EventHandler { - order, - handler: Rc::new(move |event, window_cx| { - if let Some(view) = view.upgrade(window_cx) { - view.update(window_cx, |view, view_cx| { - let mut event_cx = EventContext::new(view_cx); - handler(view, event.downcast_ref().unwrap(), &mut event_cx); - event_cx.bubble - }) - } else { - true - } - }), - event_type: TypeId::of::(), - }) - } - - pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { - self.layout_engine() - .ok_or_else(|| anyhow!("no layout engine present"))? - .computed_layout(layout_id) - } -} diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs deleted file mode 100644 index 2462ac99f5..0000000000 --- a/crates/gpui/playground/src/playground.rs +++ /dev/null @@ -1,83 +0,0 @@ -#![allow(dead_code, unused_variables)] -use crate::{color::black, style::StyleHelpers}; -use element::Element; -use gpui::{ - geometry::{rect::RectF, vector::vec2f}, - platform::WindowOptions, -}; -use log::LevelFilter; -use simplelog::SimpleLogger; -use themes::{rose_pine, ThemeColors}; -use view::view; - -mod adapter; -mod color; -mod components; -mod div; -mod element; -mod hoverable; -mod interactive; -mod layout_context; -mod paint_context; -mod style; -mod text; -mod themes; -mod view; - -fn main() { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - gpui::App::new(()).unwrap().run(|cx| { - cx.add_window( - WindowOptions { - bounds: gpui::platform::WindowBounds::Fixed(RectF::new( - vec2f(0., 0.), - vec2f(400., 300.), - )), - center: true, - ..Default::default() - }, - |_| view(|_| playground(&rose_pine::moon())), - ); - cx.platform().activate(true); - }); -} - -fn playground(theme: &ThemeColors) -> impl Element { - use div::div; - - div() - .text_color(black()) - .h_full() - .w_1_2() - .fill(theme.success(0.5)) - // .hover() - // .fill(theme.error(0.5)) - // .child(button().label("Hello").click(|_, _, _| println!("click!"))) -} - -// todo!() -// // column() -// // .size(auto()) -// // .fill(theme.base(0.5)) -// // .text_color(theme.text(0.5)) -// // .child(title_bar(theme)) -// // .child(stage(theme)) -// // .child(status_bar(theme)) -// } - -// fn title_bar(theme: &ThemeColors) -> impl Element { -// row() -// .fill(theme.base(0.2)) -// .justify(0.) -// .width(auto()) -// .child(text("Zed Playground")) -// } - -// fn stage(theme: &ThemeColors) -> impl Element { -// row().fill(theme.surface(0.9)) -// } - -// fn status_bar(theme: &ThemeColors) -> impl Element { -// row().fill(theme.surface(0.1)) -// } diff --git a/crates/gpui/playground/src/style.rs b/crates/gpui/playground/src/style.rs deleted file mode 100644 index 9216702f7f..0000000000 --- a/crates/gpui/playground/src/style.rs +++ /dev/null @@ -1,286 +0,0 @@ -use crate::{ - color::Hsla, - element::{Element, Layout}, - paint_context::PaintContext, -}; -use gpui::{ - fonts::TextStyleRefinement, - geometry::{ - AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement, - Size, SizeRefinement, - }, -}; -use playground_macros::styleable_helpers; -use refineable::Refineable; -pub use taffy::style::{ - AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, - Overflow, Position, -}; - -#[derive(Clone, Refineable)] -pub struct Style { - /// What layout strategy should be used? - pub display: Display, - - // Overflow properties - /// How children overflowing their container should affect layout - #[refineable] - pub overflow: Point, - /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes. - pub scrollbar_width: f32, - - // Position properties - /// What should the `position` value of this struct use as a base offset? - pub position: Position, - /// How should the position of this element be tweaked relative to the layout defined? - #[refineable] - pub inset: Edges, - - // Size properies - /// Sets the initial size of the item - #[refineable] - pub size: Size, - /// Controls the minimum size of the item - #[refineable] - pub min_size: Size, - /// Controls the maximum size of the item - #[refineable] - pub max_size: Size, - /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height. - pub aspect_ratio: Option, - - // Spacing Properties - /// How large should the margin be on each side? - #[refineable] - pub margin: Edges, - /// How large should the padding be on each side? - #[refineable] - pub padding: Edges, - /// How large should the border be on each side? - #[refineable] - pub border: Edges, - - // Alignment properties - /// How this node's children aligned in the cross/block axis? - pub align_items: Option, - /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set - pub align_self: Option, - /// How should content contained within this item be aligned in the cross/block axis - pub align_content: Option, - /// How should contained within this item be aligned in the main/inline axis - pub justify_content: Option, - /// How large should the gaps between items in a flex container be? - #[refineable] - pub gap: Size, - - // Flexbox properies - /// Which direction does the main axis flow in? - pub flex_direction: FlexDirection, - /// Should elements wrap, or stay in a single line? - pub flex_wrap: FlexWrap, - /// Sets the initial main axis size of the item - pub flex_basis: Length, - /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive. - pub flex_grow: f32, - /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive. - pub flex_shrink: f32, - - /// The fill color of this element - pub fill: Option, - /// The radius of the corners of this element - #[refineable] - pub corner_radii: CornerRadii, - /// The color of text within this element. Cascades to children unless overridden. - pub text_color: Option, -} - -impl Style { - pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style { - taffy::style::Style { - display: self.display, - overflow: self.overflow.clone().into(), - scrollbar_width: self.scrollbar_width, - position: self.position, - inset: self.inset.to_taffy(rem_size), - size: self.size.to_taffy(rem_size), - min_size: self.min_size.to_taffy(rem_size), - max_size: self.max_size.to_taffy(rem_size), - aspect_ratio: self.aspect_ratio, - margin: self.margin.to_taffy(rem_size), - padding: self.padding.to_taffy(rem_size), - border: self.border.to_taffy(rem_size), - align_items: self.align_items, - align_self: self.align_self, - align_content: self.align_content, - justify_content: self.justify_content, - gap: self.gap.to_taffy(rem_size), - flex_direction: self.flex_direction, - flex_wrap: self.flex_wrap, - flex_basis: self.flex_basis.to_taffy(rem_size).into(), - flex_grow: self.flex_grow, - flex_shrink: self.flex_shrink, - ..Default::default() // Ignore grid properties for now - } - } - - /// Paints the background of an element styled with this style. - /// Return the bounds in which to paint the content. - pub fn paint_background>( - &self, - layout: &mut Layout, - cx: &mut PaintContext, - ) { - let bounds = layout.bounds(cx); - let rem_size = cx.rem_pixels(); - if let Some(color) = self.fill.as_ref().and_then(Fill::color) { - cx.scene.push_quad(gpui::Quad { - bounds, - background: Some(color.into()), - corner_radii: self.corner_radii.to_gpui(rem_size), - border: Default::default(), - }); - } - } -} - -impl Default for Style { - fn default() -> Self { - Style { - display: Display::DEFAULT, - overflow: Point { - x: Overflow::Visible, - y: Overflow::Visible, - }, - scrollbar_width: 0.0, - position: Position::Relative, - inset: Edges::auto(), - margin: Edges::::zero(), - padding: Edges::::zero(), - border: Edges::::zero(), - size: Size::auto(), - min_size: Size::auto(), - max_size: Size::auto(), - aspect_ratio: None, - gap: Size::zero(), - // Aligment - align_items: None, - align_self: None, - align_content: None, - justify_content: None, - // Flexbox - flex_direction: FlexDirection::Row, - flex_wrap: FlexWrap::NoWrap, - flex_grow: 0.0, - flex_shrink: 1.0, - flex_basis: Length::Auto, - fill: None, - text_color: None, - corner_radii: CornerRadii::default(), - } - } -} - -impl StyleRefinement { - pub fn text_style(&self) -> Option { - self.text_color.map(|color| TextStyleRefinement { - color: Some(color.into()), - ..Default::default() - }) - } -} - -pub struct OptionalTextStyle { - color: Option, -} - -impl OptionalTextStyle { - pub fn apply(&self, style: &mut gpui::fonts::TextStyle) { - if let Some(color) = self.color { - style.color = color.into(); - } - } -} - -#[derive(Clone)] -pub enum Fill { - Color(Hsla), -} - -impl Fill { - pub fn color(&self) -> Option { - match self { - Fill::Color(color) => Some(*color), - } - } -} - -impl Default for Fill { - fn default() -> Self { - Self::Color(Hsla::default()) - } -} - -impl From for Fill { - fn from(color: Hsla) -> Self { - Self::Color(color) - } -} - -#[derive(Clone, Refineable, Default)] -pub struct CornerRadii { - top_left: AbsoluteLength, - top_right: AbsoluteLength, - bottom_left: AbsoluteLength, - bottom_right: AbsoluteLength, -} - -impl CornerRadii { - pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii { - gpui::scene::CornerRadii { - top_left: self.top_left.to_pixels(rem_size), - top_right: self.top_right.to_pixels(rem_size), - bottom_left: self.bottom_left.to_pixels(rem_size), - bottom_right: self.bottom_right.to_pixels(rem_size), - } - } -} - -pub trait Styleable { - type Style: refineable::Refineable; - - fn declared_style(&mut self) -> &mut playground::style::StyleRefinement; - - fn style(&mut self) -> playground::style::Style { - let mut style = playground::style::Style::default(); - style.refine(self.declared_style()); - style - } -} - -// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc. -// -// Example: -// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind. -// fn p_2(mut self) -> Self where Self: Sized; -use crate as playground; // Macro invocation references this crate as playground. -pub trait StyleHelpers: Styleable