Merge branch 'main' into go-to-line2

Mikayla created

Change summary

.github/workflows/ci.yml                        |   13 
Cargo.lock                                      |   26 
Cargo.toml                                      |    1 
crates/client2/src/client2.rs                   |    3 
crates/copilot2/src/copilot2.rs                 |   16 
crates/editor2/src/editor.rs                    | 1192 +++++++++---------
crates/editor2/src/element.rs                   |  815 ++++++------
crates/editor2/src/hover_popover.rs             |   36 
crates/editor2/src/link_go_to_definition.rs     |  329 ++--
crates/editor2/src/scroll.rs                    |  109 
crates/editor2/src/scroll/actions.rs            |  203 +-
crates/editor2/src/selections_collection.rs     |   66 
crates/go_to_line2/src/go_to_line.rs            |    1 
crates/gpui2/src/action.rs                      |  134 +
crates/gpui2/src/app.rs                         |   39 
crates/gpui2/src/app/entity_map.rs              |    6 
crates/gpui2/src/color.rs                       |    9 
crates/gpui2/src/elements.rs                    |    2 
crates/gpui2/src/elements/div.rs                |   80 
crates/gpui2/src/elements/img.rs                |   40 
crates/gpui2/src/elements/svg.rs                |   36 
crates/gpui2/src/elements/text.rs               |    1 
crates/gpui2/src/elements/uniform_list.rs       |  244 +++
crates/gpui2/src/executor.rs                    |   33 
crates/gpui2/src/geometry.rs                    |   41 
crates/gpui2/src/gpui2.rs                       |    3 
crates/gpui2/src/interactive.rs                 |  181 +-
crates/gpui2/src/platform/mac/metal_renderer.rs |   37 
crates/gpui2/src/platform/mac/shaders.metal     |  150 +
crates/gpui2/src/scene.rs                       |    6 
crates/gpui2/src/style.rs                       |    4 
crates/gpui2/src/styled.rs                      |    7 
crates/gpui2/src/text_system/line.rs            |    5 
crates/gpui2/src/window.rs                      |  289 ++-
crates/gpui2/src/window_input_handler.rs        |   89 +
crates/gpui2_macros/src/action.rs               |   55 
crates/gpui2_macros/src/gpui2_macros.rs         |   16 
crates/gpui2_macros/src/register_action.rs      |   33 
crates/menu2/Cargo.toml                         |    1 
crates/menu2/src/menu2.rs                       |   37 
crates/picker2/Cargo.toml                       |   28 
crates/picker2/src/picker2.rs                   |  163 ++
crates/settings2/src/keymap_file.rs             |    4 
crates/storybook2/Cargo.toml                    |    5 
crates/storybook2/src/stories.rs                |    2 
crates/storybook2/src/stories/focus.rs          |   56 
crates/storybook2/src/stories/kitchen_sink.rs   |    4 
crates/storybook2/src/stories/picker.rs         |  214 +++
crates/storybook2/src/stories/scroll.rs         |    8 
crates/storybook2/src/story_selector.rs         |    4 
crates/storybook2/src/storybook2.rs             |    2 
crates/terminal2/src/mappings/mouse.rs          |    4 
crates/terminal2/src/terminal2.rs               |   11 
crates/theme2/Cargo.toml                        |    3 
crates/theme2/src/colors.rs                     |   41 
crates/theme2/src/default_colors.rs             |  166 +
crates/theme2/src/default_theme.rs              |    8 
crates/theme2/src/players.rs                    |  170 ++
crates/theme2/src/registry.rs                   |    5 
crates/theme2/src/story.rs                      |   38 
crates/theme2/src/theme2.rs                     |   15 
crates/theme_importer/src/theme_printer.rs      |   19 
crates/ui2/src/components/checkbox.rs           |    4 
crates/ui2/src/prelude.rs                       |   12 
crates/workspace2/src/modal_layer.rs            |   19 
crates/workspace2/src/workspace2.rs             |    2 
crates/zed/Cargo.toml                           |    2 
crates/zed/src/languages/racket/highlights.scm  |    1 
crates/zed2/src/languages/json.rs               |    2 
crates/zed2/src/languages/racket/highlights.scm |    1 
script/get-preview-channel-changes              |   63 
71 files changed, 3,390 insertions(+), 2,074 deletions(-)

Detailed changes

.github/workflows/ci.yml 🔗

@@ -32,7 +32,7 @@ jobs:
         uses: actions/checkout@v3
         with:
           clean: false
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: cargo fmt
         run: cargo fmt --all -- --check
@@ -56,13 +56,13 @@ jobs:
       - name: Install Node
         uses: actions/setup-node@v3
         with:
-          node-version: '18'
+          node-version: "18"
 
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
           clean: false
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: Limit target directory size
         run: script/clear-target-dir-if-larger-than 70
@@ -79,9 +79,6 @@ jobs:
       - name: Build other binaries
         run: cargo build --workspace --bins --all-features
 
-      - name: Generate license file
-        run: script/generate-licenses
-
   bundle:
     name: Bundle app
     runs-on:
@@ -106,13 +103,13 @@ jobs:
       - name: Install Node
         uses: actions/setup-node@v3
         with:
-          node-version: '18'
+          node-version: "18"
 
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
           clean: false
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: Limit target directory size
         run: script/clear-target-dir-if-larger-than 70

Cargo.lock 🔗

@@ -4988,6 +4988,7 @@ name = "menu2"
 version = "0.1.0"
 dependencies = [
  "gpui2",
+ "serde",
 ]
 
 [[package]]
@@ -6023,6 +6024,23 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "picker2"
+version = "0.1.0"
+dependencies = [
+ "ctor",
+ "editor2",
+ "env_logger 0.9.3",
+ "gpui2",
+ "menu2",
+ "parking_lot 0.11.2",
+ "serde_json",
+ "settings2",
+ "theme2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "pico-args"
 version = "0.4.2"
@@ -8539,9 +8557,14 @@ dependencies = [
  "backtrace-on-stack-overflow",
  "chrono",
  "clap 4.4.4",
+ "editor2",
+ "fuzzy2",
  "gpui2",
  "itertools 0.11.0",
+ "language2",
  "log",
+ "menu2",
+ "picker2",
  "rust-embed",
  "serde",
  "settings2",
@@ -9005,6 +9028,7 @@ dependencies = [
  "fs2",
  "gpui2",
  "indexmap 1.9.3",
+ "itertools 0.11.0",
  "parking_lot 0.11.2",
  "refineable",
  "schemars",
@@ -11077,7 +11101,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.112.0"
+version = "0.113.0"
 dependencies = [
  "activity_indicator",
  "ai",

Cargo.toml 🔗

@@ -68,6 +68,7 @@ members = [
     "crates/notifications",
     "crates/outline",
     "crates/picker",
+    "crates/picker2",
     "crates/plugin",
     "crates/plugin_macros",
     "crates/plugin_runtime",

crates/client2/src/client2.rs 🔗

@@ -80,7 +80,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
     init_settings(cx);
 
     let client = Arc::downgrade(client);
-    cx.register_action_type::<SignIn>();
     cx.on_action({
         let client = client.clone();
         move |_: &SignIn, cx| {
@@ -93,7 +92,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
         }
     });
 
-    cx.register_action_type::<SignOut>();
     cx.on_action({
         let client = client.clone();
         move |_: &SignOut, cx| {
@@ -106,7 +104,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
         }
     });
 
-    cx.register_action_type::<Reconnect>();
     cx.on_action({
         let client = client.clone();
         move |_: &Reconnect, cx| {

crates/copilot2/src/copilot2.rs 🔗

@@ -7,8 +7,8 @@ use async_tar::Archive;
 use collections::{HashMap, HashSet};
 use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
 use gpui::{
-    AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext,
-    Task, WeakModel,
+    actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
+    ModelContext, Task, WeakModel,
 };
 use language::{
     language_settings::{all_language_settings, language_settings},
@@ -34,19 +34,11 @@ use util::{
 
 // todo!()
 // const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
-// actions!(copilot_auth, [SignIn, SignOut]);
+actions!(SignIn, SignOut);
 
 // todo!()
 // const COPILOT_NAMESPACE: &'static str = "copilot";
-// actions!(
-//     copilot,
-//     [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
-// );
-//
-pub struct Suggest;
-pub struct NextSuggestion;
-pub struct PreviousSuggestion;
-pub struct Reinstall;
+actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
 
 pub fn init(
     new_server_id: LanguageServerId,

crates/editor2/src/editor.rs 🔗

@@ -20,12 +20,14 @@ pub mod selections_collection;
 mod editor_tests;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
+use ::git::diff::DiffHunk;
 use aho_corasick::AhoCorasick;
 use anyhow::{Context as _, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings};
 use clock::ReplicaId;
-use collections::{BTreeMap, HashMap, HashSet, VecDeque};
+use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use convert_case::{Case, Casing};
 use copilot::Copilot;
 pub use display_map::DisplayPoint;
 use display_map::*;
@@ -35,11 +37,13 @@ pub use element::{
 };
 use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
+use git::diff_hunk_to_display;
 use gpui::{
-    actions, div, hsla, px, relative, rems, AnyElement, AppContext, BackgroundExecutor, Context,
-    DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontFeatures, FontStyle,
-    FontWeight, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    action, actions, div, px, relative, rems, AnyElement, AppContext, BackgroundExecutor,
+    ClipboardItem, Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle,
+    FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels,
+    PlatformInputHandler, Render, Styled, Subscription, Task, TextStyle, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -50,12 +54,13 @@ pub use language::{char_kind, CharKind};
 use language::{
     language_settings::{self, all_language_settings, InlayHintSettings},
     point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
-    Diagnostic, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
-    SelectionGoal, TransactionId,
+    Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
+    OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
 };
 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
 use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
 use movement::TextLayoutDetails;
+use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
     ToPoint,
@@ -63,26 +68,30 @@ pub use multi_buffer::{
 use ordered_float::OrderedFloat;
 use parking_lot::RwLock;
 use project::{FormatTrigger, Location, Project};
+use rand::prelude::*;
 use rpc::proto::*;
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
-use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
+use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use smallvec::SmallVec;
+use snippet::Snippet;
 use std::{
     any::TypeId,
     borrow::Cow,
     cmp::{self, Ordering, Reverse},
-    ops::{ControlFlow, Deref, DerefMut, Range},
+    mem,
+    num::NonZeroU32,
+    ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
     path::Path,
     sync::Arc,
     time::{Duration, Instant},
 };
 pub use sum_tree::Bias;
 use sum_tree::TreeMap;
-use text::Rope;
+use text::{OffsetUtf16, Rope};
 use theme::{
     ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
 };
@@ -164,87 +173,82 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
 //     //     .with_soft_wrap(true)
 // }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
 pub struct SelectNext {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
 pub struct SelectPrevious {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
 pub struct SelectAllMatches {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
 pub struct SelectToBeginningOfLine {
     #[serde(default)]
     stop_at_soft_wraps: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct MovePageUp {
     #[serde(default)]
     center_cursor: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct MovePageDown {
     #[serde(default)]
     center_cursor: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
 pub struct SelectToEndOfLine {
     #[serde(default)]
     stop_at_soft_wraps: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
 pub struct ToggleCodeActions {
     #[serde(default)]
     pub deployed_from_indicator: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct ConfirmCompletion {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct ConfirmCodeAction {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct ToggleComments {
     #[serde(default)]
     pub advance_downwards: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct FoldAt {
     pub buffer_row: u32,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
 pub struct UnfoldAt {
     pub buffer_row: u32,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
-pub struct GutterHover {
-    pub hovered: bool,
-}
-
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum InlayId {
     Suggestion(usize),
@@ -261,113 +265,121 @@ impl InlayId {
 }
 
 actions!(
-    Cancel,
+    AddSelectionAbove,
+    AddSelectionBelow,
     Backspace,
+    Cancel,
+    ConfirmRename,
+    ContextMenuFirst,
+    ContextMenuLast,
+    ContextMenuNext,
+    ContextMenuPrev,
+    ConvertToKebabCase,
+    ConvertToLowerCamelCase,
+    ConvertToLowerCase,
+    ConvertToSnakeCase,
+    ConvertToTitleCase,
+    ConvertToUpperCamelCase,
+    ConvertToUpperCase,
+    Copy,
+    CopyHighlightJson,
+    CopyPath,
+    CopyRelativePath,
+    Cut,
+    CutToEndOfLine,
     Delete,
-    Newline,
-    NewlineAbove,
-    NewlineBelow,
-    GoToDiagnostic,
-    GoToPrevDiagnostic,
-    GoToHunk,
-    GoToPrevHunk,
-    Indent,
-    Outdent,
     DeleteLine,
-    DeleteToPreviousWordStart,
-    DeleteToPreviousSubwordStart,
-    DeleteToNextWordEnd,
-    DeleteToNextSubwordEnd,
     DeleteToBeginningOfLine,
     DeleteToEndOfLine,
-    CutToEndOfLine,
+    DeleteToNextSubwordEnd,
+    DeleteToNextWordEnd,
+    DeleteToPreviousSubwordStart,
+    DeleteToPreviousWordStart,
     DuplicateLine,
-    MoveLineUp,
-    MoveLineDown,
+    FindAllReferences,
+    Fold,
+    FoldSelectedRanges,
+    Format,
+    GoToDefinition,
+    GoToDefinitionSplit,
+    GoToDiagnostic,
+    GoToHunk,
+    GoToPrevDiagnostic,
+    GoToPrevHunk,
+    GoToTypeDefinition,
+    GoToTypeDefinitionSplit,
+    HalfPageDown,
+    HalfPageUp,
+    Hover,
+    Indent,
     JoinLines,
-    SortLinesCaseSensitive,
-    SortLinesCaseInsensitive,
-    ReverseLines,
-    ShuffleLines,
-    ConvertToUpperCase,
-    ConvertToLowerCase,
-    ConvertToTitleCase,
-    ConvertToSnakeCase,
-    ConvertToKebabCase,
-    ConvertToUpperCamelCase,
-    ConvertToLowerCamelCase,
-    Transpose,
-    Cut,
-    Copy,
-    Paste,
-    Undo,
-    Redo,
-    MoveUp,
-    PageUp,
+    LineDown,
+    LineUp,
     MoveDown,
-    PageDown,
     MoveLeft,
+    MoveLineDown,
+    MoveLineUp,
     MoveRight,
-    MoveToPreviousWordStart,
-    MoveToPreviousSubwordStart,
-    MoveToNextWordEnd,
-    MoveToNextSubwordEnd,
+    MoveToBeginning,
     MoveToBeginningOfLine,
+    MoveToEnclosingBracket,
+    MoveToEnd,
     MoveToEndOfLine,
-    MoveToStartOfParagraph,
     MoveToEndOfParagraph,
-    MoveToBeginning,
-    MoveToEnd,
-    SelectUp,
+    MoveToNextSubwordEnd,
+    MoveToNextWordEnd,
+    MoveToPreviousSubwordStart,
+    MoveToPreviousWordStart,
+    MoveToStartOfParagraph,
+    MoveUp,
+    Newline,
+    NewlineAbove,
+    NewlineBelow,
+    NextScreen,
+    OpenExcerpts,
+    Outdent,
+    PageDown,
+    PageUp,
+    Paste,
+    Redo,
+    RedoSelection,
+    Rename,
+    RestartLanguageServer,
+    RevealInFinder,
+    ReverseLines,
+    ScrollCursorBottom,
+    ScrollCursorCenter,
+    ScrollCursorTop,
+    SelectAll,
     SelectDown,
+    SelectLargerSyntaxNode,
     SelectLeft,
+    SelectLine,
     SelectRight,
-    SelectToPreviousWordStart,
-    SelectToPreviousSubwordStart,
-    SelectToNextWordEnd,
-    SelectToNextSubwordEnd,
-    SelectToStartOfParagraph,
-    SelectToEndOfParagraph,
+    SelectSmallerSyntaxNode,
     SelectToBeginning,
     SelectToEnd,
-    SelectAll,
-    SelectLine,
+    SelectToEndOfParagraph,
+    SelectToNextSubwordEnd,
+    SelectToNextWordEnd,
+    SelectToPreviousSubwordStart,
+    SelectToPreviousWordStart,
+    SelectToStartOfParagraph,
+    SelectUp,
+    ShowCharacterPalette,
+    ShowCompletions,
+    ShuffleLines,
+    SortLinesCaseInsensitive,
+    SortLinesCaseSensitive,
     SplitSelectionIntoLines,
-    AddSelectionAbove,
-    AddSelectionBelow,
     Tab,
     TabPrev,
-    ShowCharacterPalette,
-    SelectLargerSyntaxNode,
-    SelectSmallerSyntaxNode,
-    GoToDefinition,
-    GoToDefinitionSplit,
-    GoToTypeDefinition,
-    GoToTypeDefinitionSplit,
-    MoveToEnclosingBracket,
+    ToggleInlayHints,
+    ToggleSoftWrap,
+    Transpose,
+    Undo,
     UndoSelection,
-    RedoSelection,
-    FindAllReferences,
-    Rename,
-    ConfirmRename,
-    Fold,
     UnfoldLines,
-    FoldSelectedRanges,
-    ShowCompletions,
-    OpenExcerpts,
-    RestartLanguageServer,
-    Hover,
-    Format,
-    ToggleSoftWrap,
-    ToggleInlayHints,
-    RevealInFinder,
-    CopyPath,
-    CopyRelativePath,
-    CopyHighlightJson,
-    ContextMenuFirst,
-    ContextMenuPrev,
-    ContextMenuNext,
-    ContextMenuLast,
 );
 
 // impl_actions!(
@@ -447,13 +459,12 @@ pub fn init(cx: &mut AppContext) {
     // cx.register_action_type(Editor::paste);
     // cx.register_action_type(Editor::undo);
     // cx.register_action_type(Editor::redo);
-    cx.register_action_type::<MoveUp>();
     // cx.register_action_type(Editor::move_page_up);
-    cx.register_action_type::<MoveDown>();
+    // cx.register_action_type::<MoveDown>();
     // cx.register_action_type(Editor::move_page_down);
     // cx.register_action_type(Editor::next_screen);
-    cx.register_action_type::<MoveLeft>();
-    cx.register_action_type::<MoveRight>();
+    // cx.register_action_type::<MoveLeft>();
+    // cx.register_action_type::<MoveRight>();
     // cx.register_action_type(Editor::move_to_previous_word_start);
     // cx.register_action_type(Editor::move_to_previous_subword_start);
     // cx.register_action_type(Editor::move_to_next_word_end);
@@ -532,7 +543,6 @@ pub fn init(cx: &mut AppContext) {
     // cx.register_action_type(Editor::context_menu_last);
 
     hover_popover::init(cx);
-    scroll::actions::init(cx);
 
     workspace::register_project_item::<Editor>(cx);
     workspace::register_followable_item::<Editor>(cx);
@@ -988,7 +998,7 @@ impl CompletionsMenu {
         project: Option<Model<Project>>,
         cx: &mut ViewContext<Editor>,
     ) {
-        todo!("implementation below ");
+        // todo!("implementation below ");
     }
     // ) {
     //     let settings = EditorSettings::get_global(cx);
@@ -1155,7 +1165,7 @@ impl CompletionsMenu {
         client: Arc<Client>,
         language_registry: Arc<LanguageRegistry>,
     ) {
-        todo!()
+        // todo!()
         // let request = proto::ResolveCompletionDocumentation {
         //     project_id,
         //     language_server_id: server_id.0 as u64,
@@ -1199,7 +1209,7 @@ impl CompletionsMenu {
         completion: lsp::CompletionItem,
         language_registry: Arc<LanguageRegistry>,
     ) {
-        todo!()
+        // todo!()
         // let can_resolve = server
         //     .capabilities()
         //     .completion_provider
@@ -2383,45 +2393,45 @@ impl Editor {
             .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
     }
 
-    //     pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
-    //     where
-    //         I: IntoIterator<Item = (Range<S>, T)>,
-    //         S: ToOffset,
-    //         T: Into<Arc<str>>,
-    //     {
-    //         if self.read_only {
-    //             return;
-    //         }
+    pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
+    where
+        I: IntoIterator<Item = (Range<S>, T)>,
+        S: ToOffset,
+        T: Into<Arc<str>>,
+    {
+        if self.read_only {
+            return;
+        }
 
-    //         self.buffer.update(cx, |buffer, cx| {
-    //             buffer.edit(edits, self.autoindent_mode.clone(), cx)
-    //         });
-    //     }
+        self.buffer.update(cx, |buffer, cx| {
+            buffer.edit(edits, self.autoindent_mode.clone(), cx)
+        });
+    }
 
-    //     pub fn edit_with_block_indent<I, S, T>(
-    //         &mut self,
-    //         edits: I,
-    //         original_indent_columns: Vec<u32>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) where
-    //         I: IntoIterator<Item = (Range<S>, T)>,
-    //         S: ToOffset,
-    //         T: Into<Arc<str>>,
-    //     {
-    //         if self.read_only {
-    //             return;
-    //         }
+    pub fn edit_with_block_indent<I, S, T>(
+        &mut self,
+        edits: I,
+        original_indent_columns: Vec<u32>,
+        cx: &mut ViewContext<Self>,
+    ) where
+        I: IntoIterator<Item = (Range<S>, T)>,
+        S: ToOffset,
+        T: Into<Arc<str>>,
+    {
+        if self.read_only {
+            return;
+        }
 
-    //         self.buffer.update(cx, |buffer, cx| {
-    //             buffer.edit(
-    //                 edits,
-    //                 Some(AutoindentMode::Block {
-    //                     original_indent_columns,
-    //                 }),
-    //                 cx,
-    //             )
-    //         });
-    //     }
+        self.buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                edits,
+                Some(AutoindentMode::Block {
+                    original_indent_columns,
+                }),
+                cx,
+            )
+        });
+    }
 
     fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
         self.hide_context_menu(cx);
@@ -2734,490 +2744,490 @@ impl Editor {
         self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
     }
 
-    //     pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-    //         if self.take_rename(false, cx).is_some() {
-    //             return;
-    //         }
+    pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+        if self.take_rename(false, cx).is_some() {
+            return;
+        }
 
-    //         if hide_hover(self, cx) {
-    //             return;
-    //         }
+        if hide_hover(self, cx) {
+            return;
+        }
 
-    //         if self.hide_context_menu(cx).is_some() {
-    //             return;
-    //         }
+        if self.hide_context_menu(cx).is_some() {
+            return;
+        }
 
-    //         if self.discard_copilot_suggestion(cx) {
-    //             return;
-    //         }
+        if self.discard_copilot_suggestion(cx) {
+            return;
+        }
 
-    //         if self.snippet_stack.pop().is_some() {
-    //             return;
-    //         }
+        if self.snippet_stack.pop().is_some() {
+            return;
+        }
 
-    //         if self.mode == EditorMode::Full {
-    //             if self.active_diagnostics.is_some() {
-    //                 self.dismiss_diagnostics(cx);
-    //                 return;
-    //             }
+        if self.mode == EditorMode::Full {
+            if self.active_diagnostics.is_some() {
+                self.dismiss_diagnostics(cx);
+                return;
+            }
 
-    //             if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
-    //                 return;
-    //             }
-    //         }
+            if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
+                return;
+            }
+        }
 
-    //         cx.propagate();
-    //     }
+        cx.propagate();
+    }
 
-    //     pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-    //         let text: Arc<str> = text.into();
+    pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
+        let text: Arc<str> = text.into();
 
-    //         if self.read_only {
-    //             return;
-    //         }
+        if self.read_only {
+            return;
+        }
 
-    //         let selections = self.selections.all_adjusted(cx);
-    //         let mut brace_inserted = false;
-    //         let mut edits = Vec::new();
-    //         let mut new_selections = Vec::with_capacity(selections.len());
-    //         let mut new_autoclose_regions = Vec::new();
-    //         let snapshot = self.buffer.read(cx).read(cx);
+        let selections = self.selections.all_adjusted(cx);
+        let mut brace_inserted = false;
+        let mut edits = Vec::new();
+        let mut new_selections = Vec::with_capacity(selections.len());
+        let mut new_autoclose_regions = Vec::new();
+        let snapshot = self.buffer.read(cx).read(cx);
 
-    //         for (selection, autoclose_region) in
-    //             self.selections_with_autoclose_regions(selections, &snapshot)
-    //         {
-    //             if let Some(scope) = snapshot.language_scope_at(selection.head()) {
-    //                 // Determine if the inserted text matches the opening or closing
-    //                 // bracket of any of this language's bracket pairs.
-    //                 let mut bracket_pair = None;
-    //                 let mut is_bracket_pair_start = false;
-    //                 if !text.is_empty() {
-    //                     // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
-    //                     //  and they are removing the character that triggered IME popup.
-    //                     for (pair, enabled) in scope.brackets() {
-    //                         if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
-    //                             bracket_pair = Some(pair.clone());
-    //                             is_bracket_pair_start = true;
-    //                             break;
-    //                         } else if pair.end.as_str() == text.as_ref() {
-    //                             bracket_pair = Some(pair.clone());
-    //                             break;
-    //                         }
-    //                     }
-    //                 }
+        for (selection, autoclose_region) in
+            self.selections_with_autoclose_regions(selections, &snapshot)
+        {
+            if let Some(scope) = snapshot.language_scope_at(selection.head()) {
+                // Determine if the inserted text matches the opening or closing
+                // bracket of any of this language's bracket pairs.
+                let mut bracket_pair = None;
+                let mut is_bracket_pair_start = false;
+                if !text.is_empty() {
+                    // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
+                    //  and they are removing the character that triggered IME popup.
+                    for (pair, enabled) in scope.brackets() {
+                        if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
+                            bracket_pair = Some(pair.clone());
+                            is_bracket_pair_start = true;
+                            break;
+                        } else if pair.end.as_str() == text.as_ref() {
+                            bracket_pair = Some(pair.clone());
+                            break;
+                        }
+                    }
+                }
 
-    //                 if let Some(bracket_pair) = bracket_pair {
-    //                     if selection.is_empty() {
-    //                         if is_bracket_pair_start {
-    //                             let prefix_len = bracket_pair.start.len() - text.len();
-
-    //                             // If the inserted text is a suffix of an opening bracket and the
-    //                             // selection is preceded by the rest of the opening bracket, then
-    //                             // insert the closing bracket.
-    //                             let following_text_allows_autoclose = snapshot
-    //                                 .chars_at(selection.start)
-    //                                 .next()
-    //                                 .map_or(true, |c| scope.should_autoclose_before(c));
-    //                             let preceding_text_matches_prefix = prefix_len == 0
-    //                                 || (selection.start.column >= (prefix_len as u32)
-    //                                     && snapshot.contains_str_at(
-    //                                         Point::new(
-    //                                             selection.start.row,
-    //                                             selection.start.column - (prefix_len as u32),
-    //                                         ),
-    //                                         &bracket_pair.start[..prefix_len],
-    //                                     ));
-    //                             if following_text_allows_autoclose && preceding_text_matches_prefix {
-    //                                 let anchor = snapshot.anchor_before(selection.end);
-    //                                 new_selections.push((selection.map(|_| anchor), text.len()));
-    //                                 new_autoclose_regions.push((
-    //                                     anchor,
-    //                                     text.len(),
-    //                                     selection.id,
-    //                                     bracket_pair.clone(),
-    //                                 ));
-    //                                 edits.push((
-    //                                     selection.range(),
-    //                                     format!("{}{}", text, bracket_pair.end).into(),
-    //                                 ));
-    //                                 brace_inserted = true;
-    //                                 continue;
-    //                             }
-    //                         }
+                if let Some(bracket_pair) = bracket_pair {
+                    if selection.is_empty() {
+                        if is_bracket_pair_start {
+                            let prefix_len = bracket_pair.start.len() - text.len();
+
+                            // If the inserted text is a suffix of an opening bracket and the
+                            // selection is preceded by the rest of the opening bracket, then
+                            // insert the closing bracket.
+                            let following_text_allows_autoclose = snapshot
+                                .chars_at(selection.start)
+                                .next()
+                                .map_or(true, |c| scope.should_autoclose_before(c));
+                            let preceding_text_matches_prefix = prefix_len == 0
+                                || (selection.start.column >= (prefix_len as u32)
+                                    && snapshot.contains_str_at(
+                                        Point::new(
+                                            selection.start.row,
+                                            selection.start.column - (prefix_len as u32),
+                                        ),
+                                        &bracket_pair.start[..prefix_len],
+                                    ));
+                            if following_text_allows_autoclose && preceding_text_matches_prefix {
+                                let anchor = snapshot.anchor_before(selection.end);
+                                new_selections.push((selection.map(|_| anchor), text.len()));
+                                new_autoclose_regions.push((
+                                    anchor,
+                                    text.len(),
+                                    selection.id,
+                                    bracket_pair.clone(),
+                                ));
+                                edits.push((
+                                    selection.range(),
+                                    format!("{}{}", text, bracket_pair.end).into(),
+                                ));
+                                brace_inserted = true;
+                                continue;
+                            }
+                        }
 
-    //                         if let Some(region) = autoclose_region {
-    //                             // If the selection is followed by an auto-inserted closing bracket,
-    //                             // then don't insert that closing bracket again; just move the selection
-    //                             // past the closing bracket.
-    //                             let should_skip = selection.end == region.range.end.to_point(&snapshot)
-    //                                 && text.as_ref() == region.pair.end.as_str();
-    //                             if should_skip {
-    //                                 let anchor = snapshot.anchor_after(selection.end);
-    //                                 new_selections
-    //                                     .push((selection.map(|_| anchor), region.pair.end.len()));
-    //                                 continue;
-    //                             }
-    //                         }
-    //                     }
-    //                     // If an opening bracket is 1 character long and is typed while
-    //                     // text is selected, then surround that text with the bracket pair.
-    //                     else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 {
-    //                         edits.push((selection.start..selection.start, text.clone()));
-    //                         edits.push((
-    //                             selection.end..selection.end,
-    //                             bracket_pair.end.as_str().into(),
-    //                         ));
-    //                         brace_inserted = true;
-    //                         new_selections.push((
-    //                             Selection {
-    //                                 id: selection.id,
-    //                                 start: snapshot.anchor_after(selection.start),
-    //                                 end: snapshot.anchor_before(selection.end),
-    //                                 reversed: selection.reversed,
-    //                                 goal: selection.goal,
-    //                             },
-    //                             0,
-    //                         ));
-    //                         continue;
-    //                     }
-    //                 }
-    //             }
+                        if let Some(region) = autoclose_region {
+                            // If the selection is followed by an auto-inserted closing bracket,
+                            // then don't insert that closing bracket again; just move the selection
+                            // past the closing bracket.
+                            let should_skip = selection.end == region.range.end.to_point(&snapshot)
+                                && text.as_ref() == region.pair.end.as_str();
+                            if should_skip {
+                                let anchor = snapshot.anchor_after(selection.end);
+                                new_selections
+                                    .push((selection.map(|_| anchor), region.pair.end.len()));
+                                continue;
+                            }
+                        }
+                    }
+                    // If an opening bracket is 1 character long and is typed while
+                    // text is selected, then surround that text with the bracket pair.
+                    else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 {
+                        edits.push((selection.start..selection.start, text.clone()));
+                        edits.push((
+                            selection.end..selection.end,
+                            bracket_pair.end.as_str().into(),
+                        ));
+                        brace_inserted = true;
+                        new_selections.push((
+                            Selection {
+                                id: selection.id,
+                                start: snapshot.anchor_after(selection.start),
+                                end: snapshot.anchor_before(selection.end),
+                                reversed: selection.reversed,
+                                goal: selection.goal,
+                            },
+                            0,
+                        ));
+                        continue;
+                    }
+                }
+            }
 
-    //             // If not handling any auto-close operation, then just replace the selected
-    //             // text with the given input and move the selection to the end of the
-    //             // newly inserted text.
-    //             let anchor = snapshot.anchor_after(selection.end);
-    //             new_selections.push((selection.map(|_| anchor), 0));
-    //             edits.push((selection.start..selection.end, text.clone()));
-    //         }
+            // If not handling any auto-close operation, then just replace the selected
+            // text with the given input and move the selection to the end of the
+            // newly inserted text.
+            let anchor = snapshot.anchor_after(selection.end);
+            new_selections.push((selection.map(|_| anchor), 0));
+            edits.push((selection.start..selection.end, text.clone()));
+        }
 
-    //         drop(snapshot);
-    //         self.transact(cx, |this, cx| {
-    //             this.buffer.update(cx, |buffer, cx| {
-    //                 buffer.edit(edits, this.autoindent_mode.clone(), cx);
-    //             });
+        drop(snapshot);
+        self.transact(cx, |this, cx| {
+            this.buffer.update(cx, |buffer, cx| {
+                buffer.edit(edits, this.autoindent_mode.clone(), cx);
+            });
 
-    //             let new_anchor_selections = new_selections.iter().map(|e| &e.0);
-    //             let new_selection_deltas = new_selections.iter().map(|e| e.1);
-    //             let snapshot = this.buffer.read(cx).read(cx);
-    //             let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
-    //                 .zip(new_selection_deltas)
-    //                 .map(|(selection, delta)| Selection {
-    //                     id: selection.id,
-    //                     start: selection.start + delta,
-    //                     end: selection.end + delta,
-    //                     reversed: selection.reversed,
-    //                     goal: SelectionGoal::None,
-    //                 })
-    //                 .collect::<Vec<_>>();
-
-    //             let mut i = 0;
-    //             for (position, delta, selection_id, pair) in new_autoclose_regions {
-    //                 let position = position.to_offset(&snapshot) + delta;
-    //                 let start = snapshot.anchor_before(position);
-    //                 let end = snapshot.anchor_after(position);
-    //                 while let Some(existing_state) = this.autoclose_regions.get(i) {
-    //                     match existing_state.range.start.cmp(&start, &snapshot) {
-    //                         Ordering::Less => i += 1,
-    //                         Ordering::Greater => break,
-    //                         Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
-    //                             Ordering::Less => i += 1,
-    //                             Ordering::Equal => break,
-    //                             Ordering::Greater => break,
-    //                         },
-    //                     }
-    //                 }
-    //                 this.autoclose_regions.insert(
-    //                     i,
-    //                     AutocloseRegion {
-    //                         selection_id,
-    //                         range: start..end,
-    //                         pair,
-    //                     },
-    //                 );
-    //             }
+            let new_anchor_selections = new_selections.iter().map(|e| &e.0);
+            let new_selection_deltas = new_selections.iter().map(|e| e.1);
+            let snapshot = this.buffer.read(cx).read(cx);
+            let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
+                .zip(new_selection_deltas)
+                .map(|(selection, delta)| Selection {
+                    id: selection.id,
+                    start: selection.start + delta,
+                    end: selection.end + delta,
+                    reversed: selection.reversed,
+                    goal: SelectionGoal::None,
+                })
+                .collect::<Vec<_>>();
 
-    //             drop(snapshot);
-    //             let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
-    //             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
+            let mut i = 0;
+            for (position, delta, selection_id, pair) in new_autoclose_regions {
+                let position = position.to_offset(&snapshot) + delta;
+                let start = snapshot.anchor_before(position);
+                let end = snapshot.anchor_after(position);
+                while let Some(existing_state) = this.autoclose_regions.get(i) {
+                    match existing_state.range.start.cmp(&start, &snapshot) {
+                        Ordering::Less => i += 1,
+                        Ordering::Greater => break,
+                        Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
+                            Ordering::Less => i += 1,
+                            Ordering::Equal => break,
+                            Ordering::Greater => break,
+                        },
+                    }
+                }
+                this.autoclose_regions.insert(
+                    i,
+                    AutocloseRegion {
+                        selection_id,
+                        range: start..end,
+                        pair,
+                    },
+                );
+            }
 
-    //             if !brace_inserted && EditorSettings>(cx).use_on_type_format {
-    //                 if let Some(on_type_format_task) =
-    //                     this.trigger_on_type_formatting(text.to_string(), cx)
-    //                 {
-    //                     on_type_format_task.detach_and_log_err(cx);
-    //                 }
-    //             }
+            drop(snapshot);
+            let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
+            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
 
-    //             if had_active_copilot_suggestion {
-    //                 this.refresh_copilot_suggestions(true, cx);
-    //                 if !this.has_active_copilot_suggestion(cx) {
-    //                     this.trigger_completion_on_input(&text, cx);
-    //                 }
-    //             } else {
-    //                 this.trigger_completion_on_input(&text, cx);
-    //                 this.refresh_copilot_suggestions(true, cx);
-    //             }
-    //         });
-    //     }
+            if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format {
+                if let Some(on_type_format_task) =
+                    this.trigger_on_type_formatting(text.to_string(), cx)
+                {
+                    on_type_format_task.detach_and_log_err(cx);
+                }
+            }
 
-    //     pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
-    //         self.transact(cx, |this, cx| {
-    //             let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
-    //                 let selections = this.selections.all::<usize>(cx);
-    //                 let multi_buffer = this.buffer.read(cx);
-    //                 let buffer = multi_buffer.snapshot(cx);
-    //                 selections
-    //                     .iter()
-    //                     .map(|selection| {
-    //                         let start_point = selection.start.to_point(&buffer);
-    //                         let mut indent = buffer.indent_size_for_line(start_point.row);
-    //                         indent.len = cmp::min(indent.len, start_point.column);
-    //                         let start = selection.start;
-    //                         let end = selection.end;
-    //                         let is_cursor = start == end;
-    //                         let language_scope = buffer.language_scope_at(start);
-    //                         let (comment_delimiter, insert_extra_newline) = if let Some(language) =
-    //                             &language_scope
-    //                         {
-    //                             let leading_whitespace_len = buffer
-    //                                 .reversed_chars_at(start)
-    //                                 .take_while(|c| c.is_whitespace() && *c != '\n')
-    //                                 .map(|c| c.len_utf8())
-    //                                 .sum::<usize>();
-
-    //                             let trailing_whitespace_len = buffer
-    //                                 .chars_at(end)
-    //                                 .take_while(|c| c.is_whitespace() && *c != '\n')
-    //                                 .map(|c| c.len_utf8())
-    //                                 .sum::<usize>();
-
-    //                             let insert_extra_newline =
-    //                                 language.brackets().any(|(pair, enabled)| {
-    //                                     let pair_start = pair.start.trim_end();
-    //                                     let pair_end = pair.end.trim_start();
-
-    //                                     enabled
-    //                                         && pair.newline
-    //                                         && buffer.contains_str_at(
-    //                                             end + trailing_whitespace_len,
-    //                                             pair_end,
-    //                                         )
-    //                                         && buffer.contains_str_at(
-    //                                             (start - leading_whitespace_len)
-    //                                                 .saturating_sub(pair_start.len()),
-    //                                             pair_start,
-    //                                         )
-    //                                 });
-    //                             // Comment extension on newline is allowed only for cursor selections
-    //                             let comment_delimiter = language.line_comment_prefix().filter(|_| {
-    //                                 let is_comment_extension_enabled =
-    //                                     multi_buffer.settings_at(0, cx).extend_comment_on_newline;
-    //                                 is_cursor && is_comment_extension_enabled
-    //                             });
-    //                             let comment_delimiter = if let Some(delimiter) = comment_delimiter {
-    //                                 buffer
-    //                                     .buffer_line_for_row(start_point.row)
-    //                                     .is_some_and(|(snapshot, range)| {
-    //                                         let mut index_of_first_non_whitespace = 0;
-    //                                         let line_starts_with_comment = snapshot
-    //                                             .chars_for_range(range)
-    //                                             .skip_while(|c| {
-    //                                                 let should_skip = c.is_whitespace();
-    //                                                 if should_skip {
-    //                                                     index_of_first_non_whitespace += 1;
-    //                                                 }
-    //                                                 should_skip
-    //                                             })
-    //                                             .take(delimiter.len())
-    //                                             .eq(delimiter.chars());
-    //                                         let cursor_is_placed_after_comment_marker =
-    //                                             index_of_first_non_whitespace + delimiter.len()
-    //                                                 <= start_point.column as usize;
-    //                                         line_starts_with_comment
-    //                                             && cursor_is_placed_after_comment_marker
-    //                                     })
-    //                                     .then(|| delimiter.clone())
-    //                             } else {
-    //                                 None
-    //                             };
-    //                             (comment_delimiter, insert_extra_newline)
-    //                         } else {
-    //                             (None, false)
-    //                         };
+            if had_active_copilot_suggestion {
+                this.refresh_copilot_suggestions(true, cx);
+                if !this.has_active_copilot_suggestion(cx) {
+                    this.trigger_completion_on_input(&text, cx);
+                }
+            } else {
+                this.trigger_completion_on_input(&text, cx);
+                this.refresh_copilot_suggestions(true, cx);
+            }
+        });
+    }
 
-    //                         let capacity_for_delimiter = comment_delimiter
-    //                             .as_deref()
-    //                             .map(str::len)
-    //                             .unwrap_or_default();
-    //                         let mut new_text =
-    //                             String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
-    //                         new_text.push_str("\n");
-    //                         new_text.extend(indent.chars());
-    //                         if let Some(delimiter) = &comment_delimiter {
-    //                             new_text.push_str(&delimiter);
-    //                         }
-    //                         if insert_extra_newline {
-    //                             new_text = new_text.repeat(2);
-    //                         }
+    pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
+        self.transact(cx, |this, cx| {
+            let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
+                let selections = this.selections.all::<usize>(cx);
+                let multi_buffer = this.buffer.read(cx);
+                let buffer = multi_buffer.snapshot(cx);
+                selections
+                    .iter()
+                    .map(|selection| {
+                        let start_point = selection.start.to_point(&buffer);
+                        let mut indent = buffer.indent_size_for_line(start_point.row);
+                        indent.len = cmp::min(indent.len, start_point.column);
+                        let start = selection.start;
+                        let end = selection.end;
+                        let is_cursor = start == end;
+                        let language_scope = buffer.language_scope_at(start);
+                        let (comment_delimiter, insert_extra_newline) = if let Some(language) =
+                            &language_scope
+                        {
+                            let leading_whitespace_len = buffer
+                                .reversed_chars_at(start)
+                                .take_while(|c| c.is_whitespace() && *c != '\n')
+                                .map(|c| c.len_utf8())
+                                .sum::<usize>();
+
+                            let trailing_whitespace_len = buffer
+                                .chars_at(end)
+                                .take_while(|c| c.is_whitespace() && *c != '\n')
+                                .map(|c| c.len_utf8())
+                                .sum::<usize>();
+
+                            let insert_extra_newline =
+                                language.brackets().any(|(pair, enabled)| {
+                                    let pair_start = pair.start.trim_end();
+                                    let pair_end = pair.end.trim_start();
+
+                                    enabled
+                                        && pair.newline
+                                        && buffer.contains_str_at(
+                                            end + trailing_whitespace_len,
+                                            pair_end,
+                                        )
+                                        && buffer.contains_str_at(
+                                            (start - leading_whitespace_len)
+                                                .saturating_sub(pair_start.len()),
+                                            pair_start,
+                                        )
+                                });
+                            // Comment extension on newline is allowed only for cursor selections
+                            let comment_delimiter = language.line_comment_prefix().filter(|_| {
+                                let is_comment_extension_enabled =
+                                    multi_buffer.settings_at(0, cx).extend_comment_on_newline;
+                                is_cursor && is_comment_extension_enabled
+                            });
+                            let comment_delimiter = if let Some(delimiter) = comment_delimiter {
+                                buffer
+                                    .buffer_line_for_row(start_point.row)
+                                    .is_some_and(|(snapshot, range)| {
+                                        let mut index_of_first_non_whitespace = 0;
+                                        let line_starts_with_comment = snapshot
+                                            .chars_for_range(range)
+                                            .skip_while(|c| {
+                                                let should_skip = c.is_whitespace();
+                                                if should_skip {
+                                                    index_of_first_non_whitespace += 1;
+                                                }
+                                                should_skip
+                                            })
+                                            .take(delimiter.len())
+                                            .eq(delimiter.chars());
+                                        let cursor_is_placed_after_comment_marker =
+                                            index_of_first_non_whitespace + delimiter.len()
+                                                <= start_point.column as usize;
+                                        line_starts_with_comment
+                                            && cursor_is_placed_after_comment_marker
+                                    })
+                                    .then(|| delimiter.clone())
+                            } else {
+                                None
+                            };
+                            (comment_delimiter, insert_extra_newline)
+                        } else {
+                            (None, false)
+                        };
+
+                        let capacity_for_delimiter = comment_delimiter
+                            .as_deref()
+                            .map(str::len)
+                            .unwrap_or_default();
+                        let mut new_text =
+                            String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
+                        new_text.push_str("\n");
+                        new_text.extend(indent.chars());
+                        if let Some(delimiter) = &comment_delimiter {
+                            new_text.push_str(&delimiter);
+                        }
+                        if insert_extra_newline {
+                            new_text = new_text.repeat(2);
+                        }
 
-    //                         let anchor = buffer.anchor_after(end);
-    //                         let new_selection = selection.map(|_| anchor);
-    //                         (
-    //                             (start..end, new_text),
-    //                             (insert_extra_newline, new_selection),
-    //                         )
-    //                     })
-    //                     .unzip()
-    //             };
+                        let anchor = buffer.anchor_after(end);
+                        let new_selection = selection.map(|_| anchor);
+                        (
+                            (start..end, new_text),
+                            (insert_extra_newline, new_selection),
+                        )
+                    })
+                    .unzip()
+            };
 
-    //             this.edit_with_autoindent(edits, cx);
-    //             let buffer = this.buffer.read(cx).snapshot(cx);
-    //             let new_selections = selection_fixup_info
-    //                 .into_iter()
-    //                 .map(|(extra_newline_inserted, new_selection)| {
-    //                     let mut cursor = new_selection.end.to_point(&buffer);
-    //                     if extra_newline_inserted {
-    //                         cursor.row -= 1;
-    //                         cursor.column = buffer.line_len(cursor.row);
-    //                     }
-    //                     new_selection.map(|_| cursor)
-    //                 })
-    //                 .collect();
+            this.edit_with_autoindent(edits, cx);
+            let buffer = this.buffer.read(cx).snapshot(cx);
+            let new_selections = selection_fixup_info
+                .into_iter()
+                .map(|(extra_newline_inserted, new_selection)| {
+                    let mut cursor = new_selection.end.to_point(&buffer);
+                    if extra_newline_inserted {
+                        cursor.row -= 1;
+                        cursor.column = buffer.line_len(cursor.row);
+                    }
+                    new_selection.map(|_| cursor)
+                })
+                .collect();
 
-    //             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
-    //             this.refresh_copilot_suggestions(true, cx);
-    //         });
-    //     }
+            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
+            this.refresh_copilot_suggestions(true, cx);
+        });
+    }
 
-    //     pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
-    //         let buffer = self.buffer.read(cx);
-    //         let snapshot = buffer.snapshot(cx);
+    pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(cx);
+        let snapshot = buffer.snapshot(cx);
 
-    //         let mut edits = Vec::new();
-    //         let mut rows = Vec::new();
-    //         let mut rows_inserted = 0;
+        let mut edits = Vec::new();
+        let mut rows = Vec::new();
+        let mut rows_inserted = 0;
 
-    //         for selection in self.selections.all_adjusted(cx) {
-    //             let cursor = selection.head();
-    //             let row = cursor.row;
+        for selection in self.selections.all_adjusted(cx) {
+            let cursor = selection.head();
+            let row = cursor.row;
 
-    //             let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
+            let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
 
-    //             let newline = "\n".to_string();
-    //             edits.push((start_of_line..start_of_line, newline));
+            let newline = "\n".to_string();
+            edits.push((start_of_line..start_of_line, newline));
 
-    //             rows.push(row + rows_inserted);
-    //             rows_inserted += 1;
-    //         }
+            rows.push(row + rows_inserted);
+            rows_inserted += 1;
+        }
 
-    //         self.transact(cx, |editor, cx| {
-    //             editor.edit(edits, cx);
+        self.transact(cx, |editor, cx| {
+            editor.edit(edits, cx);
 
-    //             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-    //                 let mut index = 0;
-    //                 s.move_cursors_with(|map, _, _| {
-    //                     let row = rows[index];
-    //                     index += 1;
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                let mut index = 0;
+                s.move_cursors_with(|map, _, _| {
+                    let row = rows[index];
+                    index += 1;
 
-    //                     let point = Point::new(row, 0);
-    //                     let boundary = map.next_line_boundary(point).1;
-    //                     let clipped = map.clip_point(boundary, Bias::Left);
+                    let point = Point::new(row, 0);
+                    let boundary = map.next_line_boundary(point).1;
+                    let clipped = map.clip_point(boundary, Bias::Left);
 
-    //                     (clipped, SelectionGoal::None)
-    //                 });
-    //             });
+                    (clipped, SelectionGoal::None)
+                });
+            });
 
-    //             let mut indent_edits = Vec::new();
-    //             let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-    //             for row in rows {
-    //                 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-    //                 for (row, indent) in indents {
-    //                     if indent.len == 0 {
-    //                         continue;
-    //                     }
+            let mut indent_edits = Vec::new();
+            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+            for row in rows {
+                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+                for (row, indent) in indents {
+                    if indent.len == 0 {
+                        continue;
+                    }
 
-    //                     let text = match indent.kind {
-    //                         IndentKind::Space => " ".repeat(indent.len as usize),
-    //                         IndentKind::Tab => "\t".repeat(indent.len as usize),
-    //                     };
-    //                     let point = Point::new(row, 0);
-    //                     indent_edits.push((point..point, text));
-    //                 }
-    //             }
-    //             editor.edit(indent_edits, cx);
-    //         });
-    //     }
+                    let text = match indent.kind {
+                        IndentKind::Space => " ".repeat(indent.len as usize),
+                        IndentKind::Tab => "\t".repeat(indent.len as usize),
+                    };
+                    let point = Point::new(row, 0);
+                    indent_edits.push((point..point, text));
+                }
+            }
+            editor.edit(indent_edits, cx);
+        });
+    }
 
-    //     pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
-    //         let buffer = self.buffer.read(cx);
-    //         let snapshot = buffer.snapshot(cx);
+    pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(cx);
+        let snapshot = buffer.snapshot(cx);
 
-    //         let mut edits = Vec::new();
-    //         let mut rows = Vec::new();
-    //         let mut rows_inserted = 0;
+        let mut edits = Vec::new();
+        let mut rows = Vec::new();
+        let mut rows_inserted = 0;
 
-    //         for selection in self.selections.all_adjusted(cx) {
-    //             let cursor = selection.head();
-    //             let row = cursor.row;
+        for selection in self.selections.all_adjusted(cx) {
+            let cursor = selection.head();
+            let row = cursor.row;
 
-    //             let point = Point::new(row + 1, 0);
-    //             let start_of_line = snapshot.clip_point(point, Bias::Left);
+            let point = Point::new(row + 1, 0);
+            let start_of_line = snapshot.clip_point(point, Bias::Left);
 
-    //             let newline = "\n".to_string();
-    //             edits.push((start_of_line..start_of_line, newline));
+            let newline = "\n".to_string();
+            edits.push((start_of_line..start_of_line, newline));
 
-    //             rows_inserted += 1;
-    //             rows.push(row + rows_inserted);
-    //         }
+            rows_inserted += 1;
+            rows.push(row + rows_inserted);
+        }
 
-    //         self.transact(cx, |editor, cx| {
-    //             editor.edit(edits, cx);
+        self.transact(cx, |editor, cx| {
+            editor.edit(edits, cx);
 
-    //             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-    //                 let mut index = 0;
-    //                 s.move_cursors_with(|map, _, _| {
-    //                     let row = rows[index];
-    //                     index += 1;
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                let mut index = 0;
+                s.move_cursors_with(|map, _, _| {
+                    let row = rows[index];
+                    index += 1;
 
-    //                     let point = Point::new(row, 0);
-    //                     let boundary = map.next_line_boundary(point).1;
-    //                     let clipped = map.clip_point(boundary, Bias::Left);
+                    let point = Point::new(row, 0);
+                    let boundary = map.next_line_boundary(point).1;
+                    let clipped = map.clip_point(boundary, Bias::Left);
 
-    //                     (clipped, SelectionGoal::None)
-    //                 });
-    //             });
+                    (clipped, SelectionGoal::None)
+                });
+            });
 
-    //             let mut indent_edits = Vec::new();
-    //             let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-    //             for row in rows {
-    //                 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-    //                 for (row, indent) in indents {
-    //                     if indent.len == 0 {
-    //                         continue;
-    //                     }
+            let mut indent_edits = Vec::new();
+            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+            for row in rows {
+                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+                for (row, indent) in indents {
+                    if indent.len == 0 {
+                        continue;
+                    }
 
-    //                     let text = match indent.kind {
-    //                         IndentKind::Space => " ".repeat(indent.len as usize),
-    //                         IndentKind::Tab => "\t".repeat(indent.len as usize),
-    //                     };
-    //                     let point = Point::new(row, 0);
-    //                     indent_edits.push((point..point, text));
-    //                 }
-    //             }
-    //             editor.edit(indent_edits, cx);
-    //         });
-    //     }
+                    let text = match indent.kind {
+                        IndentKind::Space => " ".repeat(indent.len as usize),
+                        IndentKind::Tab => "\t".repeat(indent.len as usize),
+                    };
+                    let point = Point::new(row, 0);
+                    indent_edits.push((point..point, text));
+                }
+            }
+            editor.edit(indent_edits, cx);
+        });
+    }
 
-    //     pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-    //         self.insert_with_autoindent_mode(
-    //             text,
-    //             Some(AutoindentMode::Block {
-    //                 original_indent_columns: Vec::new(),
-    //             }),
-    //             cx,
-    //         );
-    //     }
+    pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
+        self.insert_with_autoindent_mode(
+            text,
+            Some(AutoindentMode::Block {
+                original_indent_columns: Vec::new(),
+            }),
+            cx,
+        );
+    }
 
     fn insert_with_autoindent_mode(
         &mut self,

crates/editor2/src/element.rs 🔗

@@ -2,17 +2,25 @@ use crate::{
     display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint},
     editor_settings::ShowScrollbar,
     git::{diff_hunk_to_display, DisplayDiffHunk},
+    hover_popover::hover_at,
+    link_go_to_definition::{
+        go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
+        update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+    },
+    scroll::scroll_amount::ScrollAmount,
     CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
-    MoveDown, Point, Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
+    HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, PageDown, PageUp, Point, SelectPhase,
+    Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
 };
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
     black, hsla, point, px, relative, size, transparent_black, Action, AnyElement,
     BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
-    Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels,
-    ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle,
-    TextSystem, ViewContext, WindowContext,
+    Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch,
+    Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+    ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext,
+    WindowContext,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -31,6 +39,7 @@ use std::{
 };
 use sum_tree::Bias;
 use theme::{ActiveTheme, PlayerColor};
+use util::ResultExt;
 use workspace::item::Item;
 
 enum FoldMarkers {}
@@ -104,194 +113,54 @@ impl EditorElement {
         Self { style }
     }
 
-    // fn attach_mouse_handlers(
-    //     position_map: &Arc<PositionMap>,
-    //     has_popovers: bool,
-    //     visible_bounds: Bounds<Pixels>,
-    //     text_bounds: Bounds<Pixels>,
-    //     gutter_bounds: Bounds<Pixels>,
-    //     bounds: Bounds<Pixels>,
-    //     cx: &mut ViewContext<Editor>,
-    // ) {
-    //     enum EditorElementMouseHandlers {}
-    //     let view_id = cx.view_id();
-    //     cx.scene().push_mouse_region(
-    //         MouseRegion::new::<EditorElementMouseHandlers>(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_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()
-    //                     }
-    //                 }
-    //             })
-    //             .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()
-    //                     }
-    //                 }
-    //             }),
-    //     );
-
-    //     enum GutterHandlers {}
-    //     let view_id = cx.view_id();
-    //     let region_id = cx.view_id() + 1;
-    //     cx.scene().push_mouse_region(
-    //         MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
-    //             |hover, editor: &mut Editor, cx| {
-    //                 editor.gutter_hover(
-    //                     &GutterHover {
-    //                         hovered: hover.started,
-    //                     },
-    //                     cx,
-    //                 );
-    //             },
-    //         ),
-    //     )
-    // }
+    fn mouse_down(
+        editor: &mut Editor,
+        event: &MouseDownEvent,
+        position_map: &PositionMap,
+        text_bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        let mut click_count = event.click_count;
+        let modifiers = event.modifiers;
 
-    // fn mouse_down(
-    //     editor: &mut Editor,
-    //     MouseButtonEvent {
-    //         position,
-    //         modifiers:
-    //             Modifiers {
-    //                 shift,
-    //                 ctrl,
-    //                 alt,
-    //                 cmd,
-    //                 ..
-    //             },
-    //         mut click_count,
-    //         ..
-    //     }: MouseButtonEvent,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     gutter_bounds: Bounds<Pixels>,
-    //     cx: &mut EventContext<Editor>,
-    // ) -> bool {
-    //     if gutter_bounds.contains_point(position) {
-    //         click_count = 3; // Simulate triple-click when clicking the gutter to select lines
-    //     } else if !text_bounds.contains_point(position) {
-    //         return false;
-    //     }
+        if gutter_bounds.contains_point(&event.position) {
+            click_count = 3; // Simulate triple-click when clicking the gutter to select lines
+        } else if !text_bounds.contains_point(&event.position) {
+            return false;
+        }
 
-    //     let point_for_position = position_map.point_for_position(text_bounds, position);
-    //     let position = point_for_position.previous_valid;
-    //     if shift && alt {
-    //         editor.select(
-    //             SelectPhase::BeginColumnar {
-    //                 position,
-    //                 goal_column: point_for_position.exact_unclipped.column(),
-    //             },
-    //             cx,
-    //         );
-    //     } else if shift && !ctrl && !alt && !cmd {
-    //         editor.select(
-    //             SelectPhase::Extend {
-    //                 position,
-    //                 click_count,
-    //             },
-    //             cx,
-    //         );
-    //     } else {
-    //         editor.select(
-    //             SelectPhase::Begin {
-    //                 position,
-    //                 add: alt,
-    //                 click_count,
-    //             },
-    //             cx,
-    //         );
-    //     }
+        let point_for_position = position_map.point_for_position(text_bounds, event.position);
+        let position = point_for_position.previous_valid;
+        if modifiers.shift && modifiers.alt {
+            editor.select(
+                SelectPhase::BeginColumnar {
+                    position,
+                    goal_column: point_for_position.exact_unclipped.column(),
+                },
+                cx,
+            );
+        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
+            editor.select(
+                SelectPhase::Extend {
+                    position,
+                    click_count,
+                },
+                cx,
+            );
+        } else {
+            editor.select(
+                SelectPhase::Begin {
+                    position,
+                    add: modifiers.alt,
+                    click_count,
+                },
+                cx,
+            );
+        }
 
-    //     true
-    // }
+        true
+    }
 
     // fn mouse_right_down(
     //     editor: &mut Editor,
@@ -313,157 +182,120 @@ impl EditorElement {
     //     true
     // }
 
-    // fn mouse_up(
-    //     editor: &mut Editor,
-    //     position: gpui::Point<Pixels>,
-    //     cmd: bool,
-    //     shift: bool,
-    //     alt: bool,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     cx: &mut EventContext<Editor>,
-    // ) -> bool {
-    //     let end_selection = editor.has_pending_selection();
-    //     let pending_nonempty_selections = editor.has_pending_nonempty_selection();
-
-    //     if end_selection {
-    //         editor.select(SelectPhase::End, cx);
-    //     }
-
-    //     if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
-    //         let point = position_map.point_for_position(text_bounds, position);
-    //         let could_be_inlay = point.as_valid().is_none();
-    //         if shift || could_be_inlay {
-    //             go_to_fetched_type_definition(editor, point, alt, cx);
-    //         } else {
-    //             go_to_fetched_definition(editor, point, alt, cx);
-    //         }
-
-    //         return true;
-    //     }
-
-    //     end_selection
-    // }
+    fn mouse_up(
+        editor: &mut Editor,
+        event: &MouseUpEvent,
+        position_map: &PositionMap,
+        text_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        let end_selection = editor.has_pending_selection();
+        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 
-    // fn mouse_dragged(
-    //     editor: &mut Editor,
-    //     MouseMovedEvent {
-    //         modifiers: Modifiers { cmd, shift, .. },
-    //         position,
-    //         ..
-    //     }: MouseMovedEvent,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     cx: &mut EventContext<Editor>,
-    // ) -> bool {
-    //     // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
-    //     // Don't trigger hover popover if mouse is hovering over context menu
-    //     let point = if text_bounds.contains_point(position) {
-    //         position_map
-    //             .point_for_position(text_bounds, position)
-    //             .as_valid()
-    //     } else {
-    //         None
-    //     };
+        if end_selection {
+            editor.select(SelectPhase::End, cx);
+        }
 
-    //     update_go_to_definition_link(
-    //         editor,
-    //         point.map(GoToDefinitionTrigger::Text),
-    //         cmd,
-    //         shift,
-    //         cx,
-    //     );
+        if !pending_nonempty_selections
+            && event.modifiers.command
+            && text_bounds.contains_point(&event.position)
+        {
+            let point = position_map.point_for_position(text_bounds, event.position);
+            let could_be_inlay = point.as_valid().is_none();
+            let split = event.modifiers.alt;
+            if event.modifiers.shift || could_be_inlay {
+                go_to_fetched_type_definition(editor, point, split, cx);
+            } else {
+                go_to_fetched_definition(editor, point, split, cx);
+            }
 
-    //     if editor.has_pending_selection() {
-    //         let mut scroll_delta = gpui::Point::<Pixels>::zero();
+            return true;
+        }
 
-    //         let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
-    //         let top = text_bounds.origin.y + vertical_margin;
-    //         let bottom = text_bounds.lower_left().y - vertical_margin;
-    //         if position.y < top {
-    //             scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y))
-    //         }
-    //         if position.y > bottom {
-    //             scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom))
-    //         }
+        end_selection
+    }
 
-    //         let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
-    //         let left = text_bounds.origin.x + horizontal_margin;
-    //         let right = text_bounds.upper_right().x - horizontal_margin;
-    //         if position.x < left {
-    //             scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
-    //                 left - position.x,
-    //             ))
-    //         }
-    //         if position.x > right {
-    //             scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
-    //                 position.x - right,
-    //             ))
-    //         }
+    fn mouse_moved(
+        editor: &mut Editor,
+        event: &MouseMoveEvent,
+        position_map: &PositionMap,
+        text_bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        let modifiers = event.modifiers;
+        if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
+            let point_for_position = position_map.point_for_position(text_bounds, event.position);
+            let mut scroll_delta = gpui::Point::<f32>::zero();
+            let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
+            let top = text_bounds.origin.y + vertical_margin;
+            let bottom = text_bounds.lower_left().y - vertical_margin;
+            if event.position.y < top {
+                scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
+            }
+            if event.position.y > bottom {
+                scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
+            }
 
-    //         let point_for_position = position_map.point_for_position(text_bounds, position);
+            let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
+            let left = text_bounds.origin.x + horizontal_margin;
+            let right = text_bounds.upper_right().x - horizontal_margin;
+            if event.position.x < left {
+                scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
+            }
+            if event.position.x > right {
+                scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
+            }
 
-    //         editor.select(
-    //             SelectPhase::Update {
-    //                 position: point_for_position.previous_valid,
-    //                 goal_column: point_for_position.exact_unclipped.column(),
-    //                 scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
-    //                     .clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max),
-    //             },
-    //             cx,
-    //         );
-    //         hover_at(editor, point, cx);
-    //         true
-    //     } else {
-    //         hover_at(editor, point, cx);
-    //         false
-    //     }
-    // }
+            editor.select(
+                SelectPhase::Update {
+                    position: point_for_position.previous_valid,
+                    goal_column: point_for_position.exact_unclipped.column(),
+                    scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
+                        .clamp(&gpui::Point::zero(), &position_map.scroll_max),
+                },
+                cx,
+            );
+        }
 
-    // fn mouse_moved(
-    //     editor: &mut Editor,
-    //     MouseMovedEvent {
-    //         modifiers: Modifiers { shift, cmd, .. },
-    //         position,
-    //         ..
-    //     }: MouseMovedEvent,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     cx: &mut ViewContext<Editor>,
-    // ) -> bool {
-    //     // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
-    //     // Don't trigger hover popover if mouse is hovering over context menu
-    //     if text_bounds.contains_point(position) {
-    //         let point_for_position = position_map.point_for_position(text_bounds, position);
-    //         match point_for_position.as_valid() {
-    //             Some(point) => {
-    //                 update_go_to_definition_link(
-    //                     editor,
-    //                     Some(GoToDefinitionTrigger::Text(point)),
-    //                     cmd,
-    //                     shift,
-    //                     cx,
-    //                 );
-    //                 hover_at(editor, Some(point), cx);
-    //             }
-    //             None => {
-    //                 update_inlay_link_and_hover_points(
-    //                     &position_map.snapshot,
-    //                     point_for_position,
-    //                     editor,
-    //                     cmd,
-    //                     shift,
-    //                     cx,
-    //                 );
-    //             }
-    //         }
-    //     } else {
-    //         update_go_to_definition_link(editor, None, cmd, shift, cx);
-    //         hover_at(editor, None, cx);
-    //     }
+        let text_hovered = text_bounds.contains_point(&event.position);
+        let gutter_hovered = gutter_bounds.contains_point(&event.position);
+        editor.set_gutter_hovered(gutter_hovered, cx);
+
+        // Don't trigger hover popover if mouse is hovering over context menu
+        if text_hovered {
+            let point_for_position = position_map.point_for_position(text_bounds, event.position);
+
+            match point_for_position.as_valid() {
+                Some(point) => {
+                    update_go_to_definition_link(
+                        editor,
+                        Some(GoToDefinitionTrigger::Text(point)),
+                        modifiers.command,
+                        modifiers.shift,
+                        cx,
+                    );
+                    hover_at(editor, Some(point), cx);
+                }
+                None => {
+                    update_inlay_link_and_hover_points(
+                        &position_map.snapshot,
+                        point_for_position,
+                        editor,
+                        modifiers.command,
+                        modifiers.shift,
+                        cx,
+                    );
+                }
+            }
 
-    //     true
-    // }
+            true
+        } else {
+            update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
+            hover_at(editor, None, cx);
+            gutter_hovered
+        }
+    }
 
     fn scroll(
         editor: &mut Editor,
@@ -2337,6 +2169,79 @@ impl EditorElement {
     //         blocks,
     //     )
     // }
+
+    fn paint_mouse_listeners(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        text_bounds: Bounds<Pixels>,
+        position_map: &Arc<PositionMap>,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        cx.on_mouse_event({
+            let position_map = position_map.clone();
+            move |editor, event: &ScrollWheelEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if Self::scroll(editor, event, &position_map, bounds, cx) {
+                    cx.stop_propagation();
+                }
+            }
+        });
+        cx.on_mouse_event({
+            let position_map = position_map.clone();
+            move |editor, event: &MouseDownEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
+                    cx.stop_propagation()
+                }
+            }
+        });
+        cx.on_mouse_event({
+            let position_map = position_map.clone();
+            move |editor, event: &MouseUpEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if Self::mouse_up(editor, event, &position_map, text_bounds, cx) {
+                    cx.stop_propagation()
+                }
+            }
+        });
+        // todo!()
+        // 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();
+        //         }
+        //     }
+        // });
+        cx.on_mouse_event({
+            let position_map = position_map.clone();
+            move |editor, event: &MouseMoveEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
+                    cx.stop_propagation()
+                }
+            }
+        });
+    }
 }
 
 #[derive(Debug)]
@@ -2556,30 +2461,9 @@ impl Element<Editor> for EditorElement {
         let dispatch_context = editor.dispatch_context(cx);
         cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
             cx.with_key_dispatch_context(dispatch_context, |cx| {
-                cx.with_key_listeners(
-                    [
-                        build_action_listener(Editor::move_left),
-                        build_action_listener(Editor::move_right),
-                        build_action_listener(Editor::move_down),
-                        build_action_listener(Editor::move_up),
-                        build_key_listener(
-                            move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
-                                if phase == DispatchPhase::Bubble {
-                                    if let KeyMatch::Some(action) = cx.match_keystroke(
-                                        &global_id,
-                                        &key_down.keystroke,
-                                        dispatch_context,
-                                    ) {
-                                        return Some(action);
-                                    }
-                                }
-
-                                None
-                            },
-                        ),
-                    ],
-                    |cx| cx.with_focus(editor.focus_handle.clone(), |_| {}),
-                );
+                cx.with_key_listeners(build_key_listeners(global_id), |cx| {
+                    cx.with_focus(editor.focus_handle.clone(), |_| {})
+                });
             })
         });
     }
@@ -2609,30 +2493,27 @@ impl Element<Editor> for EditorElement {
         cx: &mut gpui::ViewContext<Editor>,
     ) {
         let layout = self.compute_layout(editor, cx, bounds);
+        let gutter_bounds = Bounds {
+            origin: bounds.origin,
+            size: layout.gutter_size,
+        };
+        let text_bounds = Bounds {
+            origin: gutter_bounds.upper_right(),
+            size: layout.text_size,
+        };
 
-        cx.on_mouse_event({
-            let position_map = layout.position_map.clone();
-            move |editor, event: &ScrollWheelEvent, phase, cx| {
-                if phase != DispatchPhase::Bubble {
-                    return;
-                }
-
-                if Self::scroll(editor, event, &position_map, bounds, cx) {
-                    cx.stop_propagation();
-                }
-            }
-        });
+        if editor.focus_handle.is_focused(cx) {
+            cx.handle_text_input();
+        }
 
         cx.with_content_mask(ContentMask { bounds }, |cx| {
-            let gutter_bounds = Bounds {
-                origin: bounds.origin,
-                size: layout.gutter_size,
-            };
-            let text_bounds = Bounds {
-                origin: gutter_bounds.upper_right(),
-                size: layout.text_size,
-            };
-
+            self.paint_mouse_listeners(
+                bounds,
+                gutter_bounds,
+                text_bounds,
+                &layout.position_map,
+                cx,
+            );
             self.paint_background(gutter_bounds, text_bounds, &layout, cx);
             if layout.gutter_size.width > Pixels::ZERO {
                 self.paint_gutter(gutter_bounds, &layout, editor, cx);
@@ -3339,7 +3220,7 @@ impl PositionMap {
         let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
         let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
 
-        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into();
+        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
         *exact_unclipped.column_mut() += column_overshoot_after_line_end;
         PointForPosition {
             previous_valid,
@@ -3661,12 +3542,12 @@ impl HighlightedRange {
 //     bounds.into_iter()
 // }
 
-pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
-    delta.powf(1.5) / 100.0
+pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
+    (delta.pow(1.5) / 100.0).into()
 }
 
-fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
-    delta.powf(1.2) / 300.0
+fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
+    (delta.pow(1.2) / 300.0).into()
 }
 
 // #[cfg(test)]
@@ -4112,6 +3993,178 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
 //     }
 // }
 
+fn build_key_listeners(
+    global_element_id: GlobalElementId,
+) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
+    [
+        build_action_listener(Editor::move_left),
+        build_action_listener(Editor::move_right),
+        build_action_listener(Editor::move_down),
+        build_action_listener(Editor::move_up),
+        // build_action_listener(Editor::new_file), todo!()
+        // build_action_listener(Editor::new_file_in_direction), todo!()
+        build_action_listener(Editor::cancel),
+        build_action_listener(Editor::newline),
+        build_action_listener(Editor::newline_above),
+        build_action_listener(Editor::newline_below),
+        build_action_listener(Editor::backspace),
+        build_action_listener(Editor::delete),
+        build_action_listener(Editor::tab),
+        build_action_listener(Editor::tab_prev),
+        build_action_listener(Editor::indent),
+        build_action_listener(Editor::outdent),
+        build_action_listener(Editor::delete_line),
+        build_action_listener(Editor::join_lines),
+        build_action_listener(Editor::sort_lines_case_sensitive),
+        build_action_listener(Editor::sort_lines_case_insensitive),
+        build_action_listener(Editor::reverse_lines),
+        build_action_listener(Editor::shuffle_lines),
+        build_action_listener(Editor::convert_to_upper_case),
+        build_action_listener(Editor::convert_to_lower_case),
+        build_action_listener(Editor::convert_to_title_case),
+        build_action_listener(Editor::convert_to_snake_case),
+        build_action_listener(Editor::convert_to_kebab_case),
+        build_action_listener(Editor::convert_to_upper_camel_case),
+        build_action_listener(Editor::convert_to_lower_camel_case),
+        build_action_listener(Editor::delete_to_previous_word_start),
+        build_action_listener(Editor::delete_to_previous_subword_start),
+        build_action_listener(Editor::delete_to_next_word_end),
+        build_action_listener(Editor::delete_to_next_subword_end),
+        build_action_listener(Editor::delete_to_beginning_of_line),
+        build_action_listener(Editor::delete_to_end_of_line),
+        build_action_listener(Editor::cut_to_end_of_line),
+        build_action_listener(Editor::duplicate_line),
+        build_action_listener(Editor::move_line_up),
+        build_action_listener(Editor::move_line_down),
+        build_action_listener(Editor::transpose),
+        build_action_listener(Editor::cut),
+        build_action_listener(Editor::copy),
+        build_action_listener(Editor::paste),
+        build_action_listener(Editor::undo),
+        build_action_listener(Editor::redo),
+        build_action_listener(Editor::move_page_up),
+        build_action_listener(Editor::move_page_down),
+        build_action_listener(Editor::next_screen),
+        build_action_listener(Editor::scroll_cursor_top),
+        build_action_listener(Editor::scroll_cursor_center),
+        build_action_listener(Editor::scroll_cursor_bottom),
+        build_action_listener(|editor, _: &LineDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+        }),
+        build_action_listener(|editor, _: &LineUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+        }),
+        build_action_listener(|editor, _: &HalfPageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+        }),
+        build_action_listener(|editor, _: &HalfPageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+        }),
+        build_action_listener(|editor, _: &PageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+        }),
+        build_action_listener(|editor, _: &PageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+        }),
+        build_action_listener(Editor::move_to_previous_word_start),
+        build_action_listener(Editor::move_to_previous_subword_start),
+        build_action_listener(Editor::move_to_next_word_end),
+        build_action_listener(Editor::move_to_next_subword_end),
+        build_action_listener(Editor::move_to_beginning_of_line),
+        build_action_listener(Editor::move_to_end_of_line),
+        build_action_listener(Editor::move_to_start_of_paragraph),
+        build_action_listener(Editor::move_to_end_of_paragraph),
+        build_action_listener(Editor::move_to_beginning),
+        build_action_listener(Editor::move_to_end),
+        build_action_listener(Editor::select_up),
+        build_action_listener(Editor::select_down),
+        build_action_listener(Editor::select_left),
+        build_action_listener(Editor::select_right),
+        build_action_listener(Editor::select_to_previous_word_start),
+        build_action_listener(Editor::select_to_previous_subword_start),
+        build_action_listener(Editor::select_to_next_word_end),
+        build_action_listener(Editor::select_to_next_subword_end),
+        build_action_listener(Editor::select_to_beginning_of_line),
+        build_action_listener(Editor::select_to_end_of_line),
+        build_action_listener(Editor::select_to_start_of_paragraph),
+        build_action_listener(Editor::select_to_end_of_paragraph),
+        build_action_listener(Editor::select_to_beginning),
+        build_action_listener(Editor::select_to_end),
+        build_action_listener(Editor::select_all),
+        build_action_listener(|editor, action, cx| {
+            editor.select_all_matches(action, cx).log_err();
+        }),
+        build_action_listener(Editor::select_line),
+        build_action_listener(Editor::split_selection_into_lines),
+        build_action_listener(Editor::add_selection_above),
+        build_action_listener(Editor::add_selection_below),
+        build_action_listener(|editor, action, cx| {
+            editor.select_next(action, cx).log_err();
+        }),
+        build_action_listener(|editor, action, cx| {
+            editor.select_previous(action, cx).log_err();
+        }),
+        build_action_listener(Editor::toggle_comments),
+        build_action_listener(Editor::select_larger_syntax_node),
+        build_action_listener(Editor::select_smaller_syntax_node),
+        build_action_listener(Editor::move_to_enclosing_bracket),
+        build_action_listener(Editor::undo_selection),
+        build_action_listener(Editor::redo_selection),
+        build_action_listener(Editor::go_to_diagnostic),
+        build_action_listener(Editor::go_to_prev_diagnostic),
+        build_action_listener(Editor::go_to_hunk),
+        build_action_listener(Editor::go_to_prev_hunk),
+        build_action_listener(Editor::go_to_definition),
+        build_action_listener(Editor::go_to_definition_split),
+        build_action_listener(Editor::go_to_type_definition),
+        build_action_listener(Editor::go_to_type_definition_split),
+        build_action_listener(Editor::fold),
+        build_action_listener(Editor::fold_at),
+        build_action_listener(Editor::unfold_lines),
+        build_action_listener(Editor::unfold_at),
+        build_action_listener(Editor::fold_selected_ranges),
+        build_action_listener(Editor::show_completions),
+        // build_action_listener(Editor::toggle_code_actions), todo!()
+        // build_action_listener(Editor::open_excerpts), todo!()
+        build_action_listener(Editor::toggle_soft_wrap),
+        build_action_listener(Editor::toggle_inlay_hints),
+        build_action_listener(Editor::reveal_in_finder),
+        build_action_listener(Editor::copy_path),
+        build_action_listener(Editor::copy_relative_path),
+        build_action_listener(Editor::copy_highlight_json),
+        build_action_listener(|editor, action, cx| {
+            editor
+                .format(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        }),
+        build_action_listener(Editor::restart_language_server),
+        build_action_listener(Editor::show_character_palette),
+        // build_action_listener(Editor::confirm_completion), todo!()
+        // build_action_listener(Editor::confirm_code_action), todo!()
+        // build_action_listener(Editor::rename), todo!()
+        // build_action_listener(Editor::confirm_rename), todo!()
+        // build_action_listener(Editor::find_all_references), todo!()
+        build_action_listener(Editor::next_copilot_suggestion),
+        build_action_listener(Editor::previous_copilot_suggestion),
+        build_action_listener(Editor::copilot_suggest),
+        build_key_listener(
+            move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    if let KeyMatch::Some(action) = cx.match_keystroke(
+                        &global_element_id,
+                        &key_down.keystroke,
+                        dispatch_context,
+                    ) {
+                        return Some(action);
+                    }
+                }
+
+                None
+            },
+        ),
+    ]
+}
+
 fn build_key_listener<T: 'static>(
     listener: impl Fn(
             &mut Editor,

crates/editor2/src/hover_popover.rs 🔗

@@ -144,8 +144,7 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
     editor.hover_state.info_task = None;
     editor.hover_state.triggered_from = None;
 
-    // todo!()
-    // editor.clear_background_highlights::<HoverState>(cx);
+    editor.clear_background_highlights::<HoverState>(cx);
 
     if did_hide {
         cx.notify();
@@ -325,23 +324,22 @@ fn show_hover(
             };
 
             this.update(&mut cx, |this, cx| {
-                todo!();
-                // if let Some(symbol_range) = hover_popover
-                //     .as_ref()
-                //     .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
-                // {
-                //     // Highlight the selected symbol using a background highlight
-                //     this.highlight_background::<HoverState>(
-                //         vec![symbol_range],
-                //         |theme| theme.editor.hover_popover.highlight,
-                //         cx,
-                //     );
-                // } else {
-                //     this.clear_background_highlights::<HoverState>(cx);
-                // }
-                //
-                // this.hover_state.info_popover = hover_popover;
-                // cx.notify();
+                if let Some(symbol_range) = hover_popover
+                    .as_ref()
+                    .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
+                {
+                    // Highlight the selected symbol using a background highlight
+                    this.highlight_background::<HoverState>(
+                        vec![symbol_range],
+                        |theme| theme.element_hover, // todo! update theme
+                        cx,
+                    );
+                } else {
+                    this.clear_background_highlights::<HoverState>(cx);
+                }
+
+                this.hover_state.info_popover = hover_popover;
+                cx.notify();
             })?;
 
             Ok::<_, anyhow::Error>(())
@@ -171,173 +171,170 @@ pub fn update_inlay_link_and_hover_points(
     shift_held: bool,
     cx: &mut ViewContext<'_, Editor>,
 ) {
-    todo!("old implementation below")
-}
-// ) {
-//     let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
-//         Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
-//     } else {
-//         None
-//     };
-//     let mut go_to_definition_updated = false;
-//     let mut hover_updated = false;
-//     if let Some(hovered_offset) = hovered_offset {
-//         let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
-//         let previous_valid_anchor = buffer_snapshot.anchor_at(
-//             point_for_position.previous_valid.to_point(snapshot),
-//             Bias::Left,
-//         );
-//         let next_valid_anchor = buffer_snapshot.anchor_at(
-//             point_for_position.next_valid.to_point(snapshot),
-//             Bias::Right,
-//         );
-//         if let Some(hovered_hint) = editor
-//             .visible_inlay_hints(cx)
-//             .into_iter()
-//             .skip_while(|hint| {
-//                 hint.position
-//                     .cmp(&previous_valid_anchor, &buffer_snapshot)
-//                     .is_lt()
-//             })
-//             .take_while(|hint| {
-//                 hint.position
-//                     .cmp(&next_valid_anchor, &buffer_snapshot)
-//                     .is_le()
-//             })
-//             .max_by_key(|hint| hint.id)
-//         {
-//             let inlay_hint_cache = editor.inlay_hint_cache();
-//             let excerpt_id = previous_valid_anchor.excerpt_id;
-//             if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
-//                 match cached_hint.resolve_state {
-//                     ResolveState::CanResolve(_, _) => {
-//                         if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-//                             inlay_hint_cache.spawn_hint_resolve(
-//                                 buffer_id,
-//                                 excerpt_id,
-//                                 hovered_hint.id,
-//                                 cx,
-//                             );
-//                         }
-//                     }
-//                     ResolveState::Resolved => {
-//                         let mut extra_shift_left = 0;
-//                         let mut extra_shift_right = 0;
-//                         if cached_hint.padding_left {
-//                             extra_shift_left += 1;
-//                             extra_shift_right += 1;
-//                         }
-//                         if cached_hint.padding_right {
-//                             extra_shift_right += 1;
-//                         }
-//                         match cached_hint.label {
-//                             project::InlayHintLabel::String(_) => {
-//                                 if let Some(tooltip) = cached_hint.tooltip {
-//                                     hover_popover::hover_at_inlay(
-//                                         editor,
-//                                         InlayHover {
-//                                             excerpt: excerpt_id,
-//                                             tooltip: match tooltip {
-//                                                 InlayHintTooltip::String(text) => HoverBlock {
-//                                                     text,
-//                                                     kind: HoverBlockKind::PlainText,
-//                                                 },
-//                                                 InlayHintTooltip::MarkupContent(content) => {
-//                                                     HoverBlock {
-//                                                         text: content.value,
-//                                                         kind: content.kind,
-//                                                     }
-//                                                 }
-//                                             },
-//                                             range: InlayHighlight {
-//                                                 inlay: hovered_hint.id,
-//                                                 inlay_position: hovered_hint.position,
-//                                                 range: extra_shift_left
-//                                                     ..hovered_hint.text.len() + extra_shift_right,
-//                                             },
-//                                         },
-//                                         cx,
-//                                     );
-//                                     hover_updated = true;
-//                                 }
-//                             }
-//                             project::InlayHintLabel::LabelParts(label_parts) => {
-//                                 let hint_start =
-//                                     snapshot.anchor_to_inlay_offset(hovered_hint.position);
-//                                 if let Some((hovered_hint_part, part_range)) =
-//                                     hover_popover::find_hovered_hint_part(
-//                                         label_parts,
-//                                         hint_start,
-//                                         hovered_offset,
-//                                     )
-//                                 {
-//                                     let highlight_start =
-//                                         (part_range.start - hint_start).0 + extra_shift_left;
-//                                     let highlight_end =
-//                                         (part_range.end - hint_start).0 + extra_shift_right;
-//                                     let highlight = InlayHighlight {
-//                                         inlay: hovered_hint.id,
-//                                         inlay_position: hovered_hint.position,
-//                                         range: highlight_start..highlight_end,
-//                                     };
-//                                     if let Some(tooltip) = hovered_hint_part.tooltip {
-//                                         hover_popover::hover_at_inlay(
-//                                             editor,
-//                                             InlayHover {
-//                                                 excerpt: excerpt_id,
-//                                                 tooltip: match tooltip {
-//                                                     InlayHintLabelPartTooltip::String(text) => {
-//                                                         HoverBlock {
-//                                                             text,
-//                                                             kind: HoverBlockKind::PlainText,
-//                                                         }
-//                                                     }
-//                                                     InlayHintLabelPartTooltip::MarkupContent(
-//                                                         content,
-//                                                     ) => HoverBlock {
-//                                                         text: content.value,
-//                                                         kind: content.kind,
-//                                                     },
-//                                                 },
-//                                                 range: highlight.clone(),
-//                                             },
-//                                             cx,
-//                                         );
-//                                         hover_updated = true;
-//                                     }
-//                                     if let Some((language_server_id, location)) =
-//                                         hovered_hint_part.location
-//                                     {
-//                                         go_to_definition_updated = true;
-//                                         update_go_to_definition_link(
-//                                             editor,
-//                                             Some(GoToDefinitionTrigger::InlayHint(
-//                                                 highlight,
-//                                                 location,
-//                                                 language_server_id,
-//                                             )),
-//                                             cmd_held,
-//                                             shift_held,
-//                                             cx,
-//                                         );
-//                                     }
-//                                 }
-//                             }
-//                         };
-//                     }
-//                     ResolveState::Resolving => {}
-//                 }
-//             }
-//         }
-//     }
+    let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
+        Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
+    } else {
+        None
+    };
+    let mut go_to_definition_updated = false;
+    let mut hover_updated = false;
+    if let Some(hovered_offset) = hovered_offset {
+        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
+        let previous_valid_anchor = buffer_snapshot.anchor_at(
+            point_for_position.previous_valid.to_point(snapshot),
+            Bias::Left,
+        );
+        let next_valid_anchor = buffer_snapshot.anchor_at(
+            point_for_position.next_valid.to_point(snapshot),
+            Bias::Right,
+        );
+        if let Some(hovered_hint) = editor
+            .visible_inlay_hints(cx)
+            .into_iter()
+            .skip_while(|hint| {
+                hint.position
+                    .cmp(&previous_valid_anchor, &buffer_snapshot)
+                    .is_lt()
+            })
+            .take_while(|hint| {
+                hint.position
+                    .cmp(&next_valid_anchor, &buffer_snapshot)
+                    .is_le()
+            })
+            .max_by_key(|hint| hint.id)
+        {
+            let inlay_hint_cache = editor.inlay_hint_cache();
+            let excerpt_id = previous_valid_anchor.excerpt_id;
+            if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
+                match cached_hint.resolve_state {
+                    ResolveState::CanResolve(_, _) => {
+                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+                            inlay_hint_cache.spawn_hint_resolve(
+                                buffer_id,
+                                excerpt_id,
+                                hovered_hint.id,
+                                cx,
+                            );
+                        }
+                    }
+                    ResolveState::Resolved => {
+                        let mut extra_shift_left = 0;
+                        let mut extra_shift_right = 0;
+                        if cached_hint.padding_left {
+                            extra_shift_left += 1;
+                            extra_shift_right += 1;
+                        }
+                        if cached_hint.padding_right {
+                            extra_shift_right += 1;
+                        }
+                        match cached_hint.label {
+                            project::InlayHintLabel::String(_) => {
+                                if let Some(tooltip) = cached_hint.tooltip {
+                                    hover_popover::hover_at_inlay(
+                                        editor,
+                                        InlayHover {
+                                            excerpt: excerpt_id,
+                                            tooltip: match tooltip {
+                                                InlayHintTooltip::String(text) => HoverBlock {
+                                                    text,
+                                                    kind: HoverBlockKind::PlainText,
+                                                },
+                                                InlayHintTooltip::MarkupContent(content) => {
+                                                    HoverBlock {
+                                                        text: content.value,
+                                                        kind: content.kind,
+                                                    }
+                                                }
+                                            },
+                                            range: InlayHighlight {
+                                                inlay: hovered_hint.id,
+                                                inlay_position: hovered_hint.position,
+                                                range: extra_shift_left
+                                                    ..hovered_hint.text.len() + extra_shift_right,
+                                            },
+                                        },
+                                        cx,
+                                    );
+                                    hover_updated = true;
+                                }
+                            }
+                            project::InlayHintLabel::LabelParts(label_parts) => {
+                                let hint_start =
+                                    snapshot.anchor_to_inlay_offset(hovered_hint.position);
+                                if let Some((hovered_hint_part, part_range)) =
+                                    hover_popover::find_hovered_hint_part(
+                                        label_parts,
+                                        hint_start,
+                                        hovered_offset,
+                                    )
+                                {
+                                    let highlight_start =
+                                        (part_range.start - hint_start).0 + extra_shift_left;
+                                    let highlight_end =
+                                        (part_range.end - hint_start).0 + extra_shift_right;
+                                    let highlight = InlayHighlight {
+                                        inlay: hovered_hint.id,
+                                        inlay_position: hovered_hint.position,
+                                        range: highlight_start..highlight_end,
+                                    };
+                                    if let Some(tooltip) = hovered_hint_part.tooltip {
+                                        hover_popover::hover_at_inlay(
+                                            editor,
+                                            InlayHover {
+                                                excerpt: excerpt_id,
+                                                tooltip: match tooltip {
+                                                    InlayHintLabelPartTooltip::String(text) => {
+                                                        HoverBlock {
+                                                            text,
+                                                            kind: HoverBlockKind::PlainText,
+                                                        }
+                                                    }
+                                                    InlayHintLabelPartTooltip::MarkupContent(
+                                                        content,
+                                                    ) => HoverBlock {
+                                                        text: content.value,
+                                                        kind: content.kind,
+                                                    },
+                                                },
+                                                range: highlight.clone(),
+                                            },
+                                            cx,
+                                        );
+                                        hover_updated = true;
+                                    }
+                                    if let Some((language_server_id, location)) =
+                                        hovered_hint_part.location
+                                    {
+                                        go_to_definition_updated = true;
+                                        update_go_to_definition_link(
+                                            editor,
+                                            Some(GoToDefinitionTrigger::InlayHint(
+                                                highlight,
+                                                location,
+                                                language_server_id,
+                                            )),
+                                            cmd_held,
+                                            shift_held,
+                                            cx,
+                                        );
+                                    }
+                                }
+                            }
+                        };
+                    }
+                    ResolveState::Resolving => {}
+                }
+            }
+        }
+    }
 
-//     if !go_to_definition_updated {
-//         update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
-//     }
-//     if !hover_updated {
-//         hover_popover::hover_at(editor, None, cx);
-//     }
-// }
+    if !go_to_definition_updated {
+        update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
+    }
+    if !hover_updated {
+        hover_popover::hover_at(editor, None, cx);
+    }
+}
 
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub enum LinkDefinitionKind {

crates/editor2/src/scroll.rs 🔗

@@ -288,16 +288,15 @@ impl ScrollManager {
     }
 }
 
-// todo!()
 impl Editor {
-    //     pub fn vertical_scroll_margin(&mut self) -> usize {
-    //         self.scroll_manager.vertical_scroll_margin as usize
-    //     }
+    pub fn vertical_scroll_margin(&mut self) -> usize {
+        self.scroll_manager.vertical_scroll_margin as usize
+    }
 
-    //     pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
-    //         self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
-    //         cx.notify();
-    //     }
+    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
+        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
+        cx.notify();
+    }
 
     pub fn visible_line_count(&self) -> Option<f32> {
         self.scroll_manager.visible_line_count
@@ -349,11 +348,9 @@ impl Editor {
         self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
     }
 
-    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
+    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        // todo!() Should `self.scroll_manager.anchor.scroll_position()` return `Pixels`?
-        // self.scroll_manager.anchor.scroll_position(&display_map)
-        todo!()
+        self.scroll_manager.anchor.scroll_position(&display_map)
     }
 
     pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
@@ -382,50 +379,50 @@ impl Editor {
             .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
     }
 
-    //     pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
-    //         if matches!(self.mode, EditorMode::SingleLine) {
-    //             cx.propagate_action();
-    //             return;
-    //         }
-
-    //         if self.take_rename(true, cx).is_some() {
-    //             return;
-    //         }
-
-    //         let cur_position = self.scroll_position(cx);
-    //         let new_pos = cur_position + point(0., amount.lines(self));
-    //         self.set_scroll_position(new_pos, cx);
-    //     }
-
-    //     /// Returns an ordering. The newest selection is:
-    //     ///     Ordering::Equal => on screen
-    //     ///     Ordering::Less => above the screen
-    //     ///     Ordering::Greater => below the screen
-    //     pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
-    //         let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-    //         let newest_head = self
-    //             .selections
-    //             .newest_anchor()
-    //             .head()
-    //             .to_display_point(&snapshot);
-    //         let screen_top = self
-    //             .scroll_manager
-    //             .anchor
-    //             .anchor
-    //             .to_display_point(&snapshot);
-
-    //         if screen_top > newest_head {
-    //             return Ordering::Less;
-    //         }
-
-    //         if let Some(visible_lines) = self.visible_line_count() {
-    //             if newest_head.row() < screen_top.row() + visible_lines as u32 {
-    //                 return Ordering::Equal;
-    //             }
-    //         }
-
-    //         Ordering::Greater
-    //     }
+    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
+        if matches!(self.mode, EditorMode::SingleLine) {
+            cx.propagate();
+            return;
+        }
+
+        if self.take_rename(true, cx).is_some() {
+            return;
+        }
+
+        let cur_position = self.scroll_position(cx);
+        let new_pos = cur_position + point(0., amount.lines(self));
+        self.set_scroll_position(new_pos, cx);
+    }
+
+    /// Returns an ordering. The newest selection is:
+    ///     Ordering::Equal => on screen
+    ///     Ordering::Less => above the screen
+    ///     Ordering::Greater => below the screen
+    pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
+        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let newest_head = self
+            .selections
+            .newest_anchor()
+            .head()
+            .to_display_point(&snapshot);
+        let screen_top = self
+            .scroll_manager
+            .anchor
+            .anchor
+            .to_display_point(&snapshot);
+
+        if screen_top > newest_head {
+            return Ordering::Less;
+        }
+
+        if let Some(visible_lines) = self.visible_line_count() {
+            if newest_head.row() < screen_top.row() + visible_lines as u32 {
+                return Ordering::Equal;
+            }
+        }
+
+        Ordering::Greater
+    }
 
     pub fn read_scroll_position_from_db(
         &mut self,

crates/editor2/src/scroll/actions.rs 🔗

@@ -1,66 +1,27 @@
 use super::Axis;
-use crate::Editor;
-use gpui::{AppContext, Point, ViewContext};
-
-// actions!(
-//     editor,
-//     [
-//         LineDown,
-//         LineUp,
-//         HalfPageDown,
-//         HalfPageUp,
-//         PageDown,
-//         PageUp,
-//         NextScreen,
-//         ScrollCursorTop,
-//         ScrollCursorCenter,
-//         ScrollCursorBottom,
-//     ]
-// );
-
-pub fn init(cx: &mut AppContext) {
-    // todo!()
-    // cx.add_action(Editor::next_screen);
-    // cx.add_action(Editor::scroll_cursor_top);
-    // cx.add_action(Editor::scroll_cursor_center);
-    // cx.add_action(Editor::scroll_cursor_bottom);
-    // cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
-    //     this.scroll_screen(&ScrollAmount::Line(1.), cx)
-    // });
-    // cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
-    //     this.scroll_screen(&ScrollAmount::Line(-1.), cx)
-    // });
-    // cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
-    //     this.scroll_screen(&ScrollAmount::Page(0.5), cx)
-    // });
-    // cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
-    //     this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
-    // });
-    // cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
-    //     this.scroll_screen(&ScrollAmount::Page(1.), cx)
-    // });
-    // cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
-    //     this.scroll_screen(&ScrollAmount::Page(-1.), cx)
-    // });
-}
+use crate::{
+    Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom,
+    ScrollCursorCenter, ScrollCursorTop,
+};
+use gpui::{actions, AppContext, Point, ViewContext};
 
 impl Editor {
-    //     pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) -> Option<()> {
-    //         if self.take_rename(true, cx).is_some() {
-    //             return None;
-    //         }
+    pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
+        if self.take_rename(true, cx).is_some() {
+            return;
+        }
 
-    //         if self.mouse_context_menu.read(cx).visible() {
-    //             return None;
-    //         }
+        // todo!()
+        // if self.mouse_context_menu.read(cx).visible() {
+        //     return None;
+        // }
 
-    //         if matches!(self.mode, EditorMode::SingleLine) {
-    //             cx.propagate_action();
-    //             return None;
-    //         }
-    //         self.request_autoscroll(Autoscroll::Next, cx);
-    //         Some(())
-    //     }
+        if matches!(self.mode, EditorMode::SingleLine) {
+            cx.propagate();
+            return;
+        }
+        self.request_autoscroll(Autoscroll::Next, cx);
+    }
 
     pub fn scroll(
         &mut self,
@@ -72,79 +33,71 @@ impl Editor {
         self.set_scroll_position(scroll_position, cx);
     }
 
-    //     fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
-    //         let snapshot = editor.snapshot(cx).display_snapshot;
-    //         let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
+    pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
 
-    //         let mut new_screen_top = editor.selections.newest_display(cx).head();
-    //         *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
-    //         *new_screen_top.column_mut() = 0;
-    //         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-    //         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+        let mut new_screen_top = self.selections.newest_display(cx).head();
+        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
+        *new_screen_top.column_mut() = 0;
+        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-    //         editor.set_scroll_anchor(
-    //             ScrollAnchor {
-    //                 anchor: new_anchor,
-    //                 offset: Default::default(),
-    //             },
-    //             cx,
-    //         )
-    //     }
+        self.set_scroll_anchor(
+            ScrollAnchor {
+                anchor: new_anchor,
+                offset: Default::default(),
+            },
+            cx,
+        )
+    }
 
-    //     fn scroll_cursor_center(
-    //         editor: &mut Editor,
-    //         _: &ScrollCursorCenter,
-    //         cx: &mut ViewContext<Editor>,
-    //     ) {
-    //         let snapshot = editor.snapshot(cx).display_snapshot;
-    //         let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
-    //             visible_rows as u32
-    //         } else {
-    //             return;
-    //         };
+    pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
+            visible_rows as u32
+        } else {
+            return;
+        };
 
-    //         let mut new_screen_top = editor.selections.newest_display(cx).head();
-    //         *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
-    //         *new_screen_top.column_mut() = 0;
-    //         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-    //         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+        let mut new_screen_top = self.selections.newest_display(cx).head();
+        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
+        *new_screen_top.column_mut() = 0;
+        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-    //         editor.set_scroll_anchor(
-    //             ScrollAnchor {
-    //                 anchor: new_anchor,
-    //                 offset: Default::default(),
-    //             },
-    //             cx,
-    //         )
-    //     }
+        self.set_scroll_anchor(
+            ScrollAnchor {
+                anchor: new_anchor,
+                offset: Default::default(),
+            },
+            cx,
+        )
+    }
 
-    //     fn scroll_cursor_bottom(
-    //         editor: &mut Editor,
-    //         _: &ScrollCursorBottom,
-    //         cx: &mut ViewContext<Editor>,
-    //     ) {
-    //         let snapshot = editor.snapshot(cx).display_snapshot;
-    //         let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
-    //         let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
-    //             visible_rows as u32
-    //         } else {
-    //             return;
-    //         };
+    pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
+        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
+            visible_rows as u32
+        } else {
+            return;
+        };
 
-    //         let mut new_screen_top = editor.selections.newest_display(cx).head();
-    //         *new_screen_top.row_mut() = new_screen_top
-    //             .row()
-    //             .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
-    //         *new_screen_top.column_mut() = 0;
-    //         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-    //         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+        let mut new_screen_top = self.selections.newest_display(cx).head();
+        *new_screen_top.row_mut() = new_screen_top
+            .row()
+            .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
+        *new_screen_top.column_mut() = 0;
+        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-    //         editor.set_scroll_anchor(
-    //             ScrollAnchor {
-    //                 anchor: new_anchor,
-    //                 offset: Default::default(),
-    //             },
-    //             cx,
-    //         )
-    //     }
+        self.set_scroll_anchor(
+            ScrollAnchor {
+                anchor: new_anchor,
+                offset: Default::default(),
+            },
+            cx,
+        )
+    }
 }

crates/editor2/src/selections_collection.rs 🔗

@@ -302,39 +302,39 @@ impl SelectionsCollection {
             .collect()
     }
 
-    // pub fn build_columnar_selection(
-    //     &mut self,
-    //     display_map: &DisplaySnapshot,
-    //     row: u32,
-    //     positions: &Range<Pixels>,
-    //     reversed: bool,
-    //     text_layout_details: &TextLayoutDetails,
-    // ) -> Option<Selection<Point>> {
-    //     let is_empty = positions.start == positions.end;
-    //     let line_len = display_map.line_len(row);
-
-    //     let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
-
-    //     let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
-    //     if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) {
-    //         let start = DisplayPoint::new(row, start_col);
-    //         let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
-    //         let end = DisplayPoint::new(row, end_col);
-
-    //         Some(Selection {
-    //             id: post_inc(&mut self.next_selection_id),
-    //             start: start.to_point(display_map),
-    //             end: end.to_point(display_map),
-    //             reversed,
-    //             goal: SelectionGoal::HorizontalRange {
-    //                 start: positions.start,
-    //                 end: positions.end,
-    //             },
-    //         })
-    //     } else {
-    //         None
-    //     }
-    // }
+    pub fn build_columnar_selection(
+        &mut self,
+        display_map: &DisplaySnapshot,
+        row: u32,
+        positions: &Range<Pixels>,
+        reversed: bool,
+        text_layout_details: &TextLayoutDetails,
+    ) -> Option<Selection<Point>> {
+        let is_empty = positions.start == positions.end;
+        let line_len = display_map.line_len(row);
+
+        let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
+
+        let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
+        if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
+            let start = DisplayPoint::new(row, start_col);
+            let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
+            let end = DisplayPoint::new(row, end_col);
+
+            Some(Selection {
+                id: post_inc(&mut self.next_selection_id),
+                start: start.to_point(display_map),
+                end: end.to_point(display_map),
+                reversed,
+                goal: SelectionGoal::HorizontalRange {
+                    start: positions.start.into(),
+                    end: positions.end.into(),
+                },
+            })
+        } else {
+            None
+        }
+    }
 
     pub(crate) fn change_with<R>(
         &mut self,

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -12,7 +12,6 @@ use workspace::ModalRegistry;
 actions!(Toggle, Cancel, Confirm);
 
 pub fn init(cx: &mut AppContext) {
-    cx.register_action_type::<Toggle>();
     cx.global_mut::<ModalRegistry>()
         .register_modal(Toggle, |workspace, cx| {
             let editor = workspace

crates/gpui2/src/action.rs 🔗

@@ -1,9 +1,54 @@
 use crate::SharedString;
 use anyhow::{anyhow, Context, Result};
 use collections::{HashMap, HashSet};
+use lazy_static::lazy_static;
+use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
 use serde::Deserialize;
 use std::any::{type_name, Any};
 
+/// Actions are used to implement keyboard-driven UI.
+/// When you declare an action, you can bind keys to the action in the keymap and
+/// listeners for that action in the element tree.
+///
+/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
+/// action for each listed action name.
+/// ```rust
+/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
+/// ```
+/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
+/// it will automatically
+/// ```
+/// #[action]
+/// pub struct SelectNext {
+///     pub replace_newest: bool,
+/// }
+///
+/// Any type A that satisfies the following bounds is automatically an action:
+///
+/// ```
+/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
+/// ```
+///
+/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
+/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
+/// generates the code needed to register your action before `main`. Then you'll need to implement all
+/// the traits manually.
+///
+/// ```
+/// #[gpui::register_action]
+/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
+/// pub struct Paste {
+///     pub content: SharedString,
+/// }
+///
+/// impl std::default::Default for Paste {
+///     fn default() -> Self {
+///         Self {
+///             content: SharedString::from("🍝"),
+///         }
+///     }
+/// }
+/// ```
 pub trait Action: std::fmt::Debug + 'static {
     fn qualified_name() -> SharedString
     where
@@ -17,32 +62,7 @@ pub trait Action: std::fmt::Debug + 'static {
     fn as_any(&self) -> &dyn Any;
 }
 
-// actions defines structs that can be used as actions.
-#[macro_export]
-macro_rules! actions {
-    () => {};
-
-    ( $name:ident ) => {
-        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
-        pub struct $name;
-    };
-
-    ( $name:ident { $($token:tt)* } ) => {
-        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
-        pub struct $name { $($token)* }
-    };
-
-    ( $name:ident, $($rest:tt)* ) => {
-        actions!($name);
-        actions!($($rest)*);
-    };
-
-    ( $name:ident { $($token:tt)* }, $($rest:tt)* ) => {
-        actions!($name { $($token)* });
-        actions!($($rest)*);
-    };
-}
-
+// Types become actions by satisfying a list of trait bounds.
 impl<A> Action for A
 where
     A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
@@ -80,6 +100,61 @@ where
     }
 }
 
+type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
+
+lazy_static! {
+    static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
+}
+
+#[derive(Default)]
+struct ActionRegistry {
+    builders_by_name: HashMap<SharedString, ActionBuilder>,
+    all_names: Vec<SharedString>, // So we can return a static slice.
+}
+
+/// Register an action type to allow it to be referenced in keymaps.
+pub fn register_action<A: Action>() {
+    let name = A::qualified_name();
+    let mut lock = ACTION_REGISTRY.write();
+    lock.builders_by_name.insert(name.clone(), A::build);
+    lock.all_names.push(name);
+}
+
+/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
+pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
+    let lock = ACTION_REGISTRY.read();
+    let build_action = lock
+        .builders_by_name
+        .get(name)
+        .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
+    (build_action)(params)
+}
+
+pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
+    let lock = ACTION_REGISTRY.read();
+    RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
+        registry.all_names.as_slice()
+    })
+}
+
+/// Defines unit structs that can be used as actions.
+/// To use more complex data types as actions, annotate your type with the #[action] macro.
+#[macro_export]
+macro_rules! actions {
+    () => {};
+
+    ( $name:ident ) => {
+        #[gpui::register_action]
+        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
+        pub struct $name;
+    };
+
+    ( $name:ident, $($rest:tt)* ) => {
+        actions!($name);
+        actions!($($rest)*);
+    };
+}
+
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
 pub struct DispatchContext {
     set: HashSet<SharedString>,
@@ -317,22 +392,23 @@ fn skip_whitespace(source: &str) -> &str {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate as gpui;
     use DispatchContextPredicate::*;
 
     #[test]
     fn test_actions_definition() {
         {
-            actions!(A, B { field: i32 }, C, D, E, F {}, G);
+            actions!(A, B, C, D, E, F, G);
         }
 
         {
             actions!(
                 A,
-                B { field: i32 },
+                B,
                 C,
                 D,
                 E,
-                F {},
+                F,
                 G, // Don't wrap, test the trailing comma
             );
         }

crates/gpui2/src/app.rs 🔗

@@ -17,9 +17,9 @@ use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
     AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
     Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
-    PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
-    SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
-    View, Window, WindowContext, WindowHandle, WindowId,
+    PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
+    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
+    WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -140,7 +140,6 @@ impl App {
     }
 }
 
-type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
 pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
 type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
 type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
@@ -154,7 +153,7 @@ type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
 // }
 
 pub struct AppContext {
-    this: Weak<AppCell>,
+    pub(crate) this: Weak<AppCell>,
     pub(crate) platform: Rc<dyn Platform>,
     app_metadata: AppMetadata,
     text_system: Arc<TextSystem>,
@@ -176,7 +175,6 @@ pub struct AppContext {
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
         HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
-    action_builders: HashMap<SharedString, ActionBuilder>,
     pending_effects: VecDeque<Effect>,
     pub(crate) pending_notifications: HashSet<EntityId>,
     pub(crate) pending_global_notifications: HashSet<TypeId>,
@@ -234,7 +232,6 @@ impl AppContext {
                 windows: SlotMap::with_key(),
                 keymap: Arc::new(Mutex::new(Keymap::default())),
                 global_action_listeners: HashMap::default(),
-                action_builders: HashMap::default(),
                 pending_effects: VecDeque::new(),
                 pending_notifications: HashSet::default(),
                 pending_global_notifications: HashSet::default(),
@@ -522,7 +519,7 @@ impl AppContext {
         window_handle
             .update(self, |_, cx| {
                 if cx.window.focus == focused {
-                    let mut listeners = mem::take(&mut cx.window.focus_listeners);
+                    let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners);
                     let focused = focused
                         .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
                     let blurred = cx
@@ -538,8 +535,8 @@ impl AppContext {
                         }
                     }
 
-                    listeners.extend(cx.window.focus_listeners.drain(..));
-                    cx.window.focus_listeners = listeners;
+                    listeners.extend(cx.window.current_frame.focus_listeners.drain(..));
+                    cx.window.current_frame.focus_listeners = listeners;
                 }
             })
             .ok();
@@ -695,10 +692,6 @@ impl AppContext {
         )
     }
 
-    pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
-        self.action_builders.keys().cloned()
-    }
-
     /// Move the global of the given type to the stack.
     pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
         GlobalLease::new(
@@ -761,24 +754,6 @@ impl AppContext {
             }));
     }
 
-    /// Register an action type to allow it to be referenced in keymaps.
-    pub fn register_action_type<A: Action>(&mut self) {
-        self.action_builders.insert(A::qualified_name(), A::build);
-    }
-
-    /// Construct an action based on its name and parameters.
-    pub fn build_action(
-        &mut self,
-        name: &str,
-        params: Option<serde_json::Value>,
-    ) -> Result<Box<dyn Action>> {
-        let build = self
-            .action_builders
-            .get(name)
-            .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
-        (build)(params)
-    }
-
     /// Event handlers propagate events by default. Call this method to stop dispatching to
     /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
     /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by

crates/gpui2/src/app/entity_map.rs 🔗

@@ -13,6 +13,7 @@ use std::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc, Weak,
     },
+    thread::panicking,
 };
 
 slotmap::new_key_type! { pub struct EntityId; }
@@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
 
 impl<'a, T> Drop for Lease<'a, T> {
     fn drop(&mut self) {
-        if self.entity.is_some() {
-            // We don't panic here, because other panics can cause us to drop the lease without ending it cleanly.
-            log::error!("Leases must be ended with EntityMap::end_lease")
+        if self.entity.is_some() && !panicking() {
+            panic!("Leases must be ended with EntityMap::end_lease")
         }
     }
 }

crates/gpui2/src/color.rs 🔗

@@ -203,6 +203,15 @@ pub fn red() -> Hsla {
     }
 }
 
+pub fn blue() -> Hsla {
+    Hsla {
+        h: 0.6,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
+    }
+}
+
 impl Hsla {
     /// Returns true if the HSLA color is fully transparent, false otherwise.
     pub fn is_transparent(&self) -> bool {

crates/gpui2/src/elements.rs 🔗

@@ -2,8 +2,10 @@ mod div;
 mod img;
 mod svg;
 mod text;
+mod uniform_list;
 
 pub use div::*;
 pub use img::*;
 pub use svg::*;
 pub use text::*;
+pub use uniform_list::*;

crates/gpui2/src/elements/div.rs 🔗

@@ -1,28 +1,28 @@
 use crate::{
     point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
-    ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
+    ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
     GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
-    Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
-    StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility,
+    Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
+    StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
 };
 use refineable::Refineable;
 use smallvec::SmallVec;
 
 pub struct Div<
     V: 'static,
-    I: ElementInteraction<V> = StatelessInteraction<V>,
+    I: ElementInteractivity<V> = StatelessInteractivity<V>,
     F: ElementFocus<V> = FocusDisabled,
 > {
-    interaction: I,
+    interactivity: I,
     focus: F,
     children: SmallVec<[AnyElement<V>; 2]>,
     group: Option<SharedString>,
     base_style: StyleRefinement,
 }
 
-pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
     Div {
-        interaction: StatelessInteraction::default(),
+        interactivity: StatelessInteractivity::default(),
         focus: FocusDisabled,
         children: SmallVec::new(),
         group: None,
@@ -30,14 +30,14 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
     }
 }
 
-impl<V, F> Div<V, StatelessInteraction<V>, F>
+impl<V, F> Div<V, StatelessInteractivity<V>, F>
 where
     V: 'static,
     F: ElementFocus<V>,
 {
-    pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> {
+    pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
         Div {
-            interaction: id.into().into(),
+            interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
             focus: self.focus,
             children: self.children,
             group: self.group,
@@ -48,7 +48,7 @@ where
 
 impl<V, I, F> Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     pub fn group(mut self, group: impl Into<SharedString>) -> Self {
@@ -98,16 +98,20 @@ where
         let mut computed_style = Style::default();
         computed_style.refine(&self.base_style);
         self.focus.refine_style(&mut computed_style, cx);
-        self.interaction
-            .refine_style(&mut computed_style, bounds, &element_state.interactive, cx);
+        self.interactivity.refine_style(
+            &mut computed_style,
+            bounds,
+            &element_state.interactive,
+            cx,
+        );
         computed_style
     }
 }
 
-impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
-    pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
+    pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
         Div {
-            interaction: self.interaction,
+            interactivity: self.interactivity,
             focus: FocusEnabled::new(),
             children: self.children,
             group: self.group,
@@ -118,9 +122,9 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
     pub fn track_focus(
         self,
         handle: &FocusHandle,
-    ) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+    ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
         Div {
-            interaction: self.interaction,
+            interactivity: self.interactivity,
             focus: FocusEnabled::tracked(handle),
             children: self.children,
             group: self.group,
@@ -145,13 +149,13 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
     }
 }
 
-impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
+impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
     pub fn track_focus(
         self,
         handle: &FocusHandle,
-    ) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+    ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
         Div {
-            interaction: self.interaction.into_stateful(handle),
+            interactivity: self.interactivity.into_stateful(handle),
             focus: handle.clone().into(),
             children: self.children,
             group: self.group,
@@ -163,7 +167,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
 impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
 where
     V: 'static,
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
 {
     fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
         &mut self.focus.focus_listeners
@@ -191,13 +195,13 @@ pub struct DivState {
 
 impl<V, I, F> Element<V> for Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     type ElementState = DivState;
 
     fn id(&self) -> Option<ElementId> {
-        self.interaction
+        self.interactivity
             .as_stateful()
             .map(|identified| identified.id.clone())
     }
@@ -209,7 +213,7 @@ where
         cx: &mut ViewContext<V>,
     ) -> Self::ElementState {
         let mut element_state = element_state.unwrap_or_default();
-        self.interaction.initialize(cx, |cx| {
+        self.interactivity.initialize(cx, |cx| {
             self.focus
                 .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
                     element_state.focus_handle = focus_handle;
@@ -281,11 +285,11 @@ where
                 (child_max - child_min).into()
             };
 
-            cx.stack(z_index, |cx| {
-                cx.stack(0, |cx| {
+            cx.with_z_index(z_index, |cx| {
+                cx.with_z_index(0, |cx| {
                     style.paint(bounds, cx);
                     this.focus.paint(bounds, cx);
-                    this.interaction.paint(
+                    this.interactivity.paint(
                         bounds,
                         content_size,
                         style.overflow,
@@ -293,7 +297,7 @@ where
                         cx,
                     );
                 });
-                cx.stack(1, |cx| {
+                cx.with_z_index(1, |cx| {
                     style.apply_text_style(cx, |cx| {
                         style.apply_overflow(bounds, cx, |cx| {
                             let scroll_offset = element_state.interactive.scroll_offset();
@@ -316,7 +320,7 @@ where
 
 impl<V, I, F> Component<V> for Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn render(self) -> AnyElement<V> {
@@ -326,7 +330,7 @@ where
 
 impl<V, I, F> ParentElement<V> for Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
@@ -336,7 +340,7 @@ where
 
 impl<V, I, F> Styled for Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn style(&mut self) -> &mut StyleRefinement {
@@ -346,19 +350,19 @@ where
 
 impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
-    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
-        self.interaction.as_stateless_mut()
+    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+        self.interactivity.as_stateless_mut()
     }
 }
 
-impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
 where
     F: ElementFocus<V>,
 {
-    fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
-        &mut self.interaction
+    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+        &mut self.interactivity
     }
 }

crates/gpui2/src/elements/img.rs 🔗

@@ -1,15 +1,15 @@
 use crate::{
     div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
-    ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
-    LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
-    StatelessInteractive, StyleRefinement, Styled, ViewContext,
+    ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
+    LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+    StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
 };
 use futures::FutureExt;
 use util::ResultExt;
 
 pub struct Img<
     V: 'static,
-    I: ElementInteraction<V> = StatelessInteraction<V>,
+    I: ElementInteractivity<V> = StatelessInteractivity<V>,
     F: ElementFocus<V> = FocusDisabled,
 > {
     base: Div<V, I, F>,
@@ -17,7 +17,7 @@ pub struct Img<
     grayscale: bool,
 }
 
-pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
     Img {
         base: div(),
         uri: None,
@@ -28,7 +28,7 @@ pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
 impl<V, I, F> Img<V, I, F>
 where
     V: 'static,
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
@@ -42,11 +42,11 @@ where
     }
 }
 
-impl<V, F> Img<V, StatelessInteraction<V>, F>
+impl<V, F> Img<V, StatelessInteractivity<V>, F>
 where
     F: ElementFocus<V>,
 {
-    pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> {
+    pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
         Img {
             base: self.base.id(id),
             uri: self.uri,
@@ -57,7 +57,7 @@ where
 
 impl<V, I, F> Component<V> for Img<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn render(self) -> AnyElement<V> {
@@ -67,7 +67,7 @@ where
 
 impl<V, I, F> Element<V> for Img<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     type ElementState = DivState;
@@ -101,7 +101,7 @@ where
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) {
-        cx.stack(0, |cx| {
+        cx.with_z_index(0, |cx| {
             self.base.paint(bounds, view, element_state, cx);
         });
 
@@ -118,7 +118,7 @@ where
                 .and_then(ResultExt::log_err)
             {
                 let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
-                cx.stack(1, |cx| {
+                cx.with_z_index(1, |cx| {
                     cx.paint_image(bounds, corner_radii, data, self.grayscale)
                         .log_err()
                 });
@@ -136,7 +136,7 @@ where
 
 impl<V, I, F> Styled for Img<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn style(&mut self) -> &mut StyleRefinement {
@@ -146,27 +146,27 @@ where
 
 impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
-    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
-        self.base.stateless_interaction()
+    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+        self.base.stateless_interactivity()
     }
 }
 
-impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
 where
     F: ElementFocus<V>,
 {
-    fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
-        self.base.stateful_interaction()
+    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+        self.base.stateful_interactivity()
     }
 }
 
 impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
 where
     V: 'static,
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
 {
     fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
         self.base.focus_listeners()

crates/gpui2/src/elements/svg.rs 🔗

@@ -1,21 +1,21 @@
 use crate::{
     div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
-    ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
-    SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
-    StatelessInteractive, StyleRefinement, Styled, ViewContext,
+    ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
+    SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
+    StatelessInteractivity, StyleRefinement, Styled, ViewContext,
 };
 use util::ResultExt;
 
 pub struct Svg<
     V: 'static,
-    I: ElementInteraction<V> = StatelessInteraction<V>,
+    I: ElementInteractivity<V> = StatelessInteractivity<V>,
     F: ElementFocus<V> = FocusDisabled,
 > {
     base: Div<V, I, F>,
     path: Option<SharedString>,
 }
 
-pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
     Svg {
         base: div(),
         path: None,
@@ -24,7 +24,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
 
 impl<V, I, F> Svg<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     pub fn path(mut self, path: impl Into<SharedString>) -> Self {
@@ -33,11 +33,11 @@ where
     }
 }
 
-impl<V, F> Svg<V, StatelessInteraction<V>, F>
+impl<V, F> Svg<V, StatelessInteractivity<V>, F>
 where
     F: ElementFocus<V>,
 {
-    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> {
+    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
         Svg {
             base: self.base.id(id),
             path: self.path,
@@ -47,7 +47,7 @@ where
 
 impl<V, I, F> Component<V> for Svg<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn render(self) -> AnyElement<V> {
@@ -57,7 +57,7 @@ where
 
 impl<V, I, F> Element<V> for Svg<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     type ElementState = DivState;
@@ -107,7 +107,7 @@ where
 
 impl<V, I, F> Styled for Svg<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
     fn style(&mut self) -> &mut StyleRefinement {
@@ -117,27 +117,27 @@ where
 
 impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
     F: ElementFocus<V>,
 {
-    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
-        self.base.stateless_interaction()
+    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+        self.base.stateless_interactivity()
     }
 }
 
-impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
 where
     V: 'static,
     F: ElementFocus<V>,
 {
-    fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
-        self.base.stateful_interaction()
+    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+        self.base.stateful_interactivity()
     }
 }
 
 impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
 where
-    I: ElementInteraction<V>,
+    I: ElementInteractivity<V>,
 {
     fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
         self.base.focus_listeners()

crates/gpui2/src/elements/text.rs 🔗

@@ -127,6 +127,7 @@ impl<V: 'static> Element<V> for Text<V> {
         let element_state = element_state
             .as_ref()
             .expect("measurement has not been performed");
+
         let line_height = element_state.line_height;
         let mut line_origin = bounds.origin;
         for line in &element_state.lines {

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -0,0 +1,244 @@
+use crate::{
+    point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
+    ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
+    StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
+    StyleRefinement, Styled, ViewContext,
+};
+use parking_lot::Mutex;
+use smallvec::SmallVec;
+use std::{cmp, ops::Range, sync::Arc};
+use taffy::style::Overflow;
+
+pub fn uniform_list<Id, V, C>(
+    id: Id,
+    item_count: usize,
+    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
+) -> UniformList<V>
+where
+    Id: Into<ElementId>,
+    V: 'static,
+    C: Component<V>,
+{
+    let id = id.into();
+    UniformList {
+        id: id.clone(),
+        style: Default::default(),
+        item_count,
+        render_items: Box::new(move |view, visible_range, cx| {
+            f(view, visible_range, cx)
+                .into_iter()
+                .map(|component| component.render())
+                .collect()
+        }),
+        interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
+        scroll_handle: None,
+    }
+}
+
+pub struct UniformList<V: 'static> {
+    id: ElementId,
+    style: StyleRefinement,
+    item_count: usize,
+    render_items: Box<
+        dyn for<'a> Fn(
+            &'a mut V,
+            Range<usize>,
+            &'a mut ViewContext<V>,
+        ) -> SmallVec<[AnyElement<V>; 64]>,
+    >,
+    interactivity: StatefulInteractivity<V>,
+    scroll_handle: Option<UniformListScrollHandle>,
+}
+
+#[derive(Clone)]
+pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
+
+#[derive(Clone, Debug)]
+struct ScrollHandleState {
+    item_height: Pixels,
+    list_height: Pixels,
+    scroll_offset: Arc<Mutex<Point<Pixels>>>,
+}
+
+impl UniformListScrollHandle {
+    pub fn new() -> Self {
+        Self(Arc::new(Mutex::new(None)))
+    }
+
+    pub fn scroll_to_item(&self, ix: usize) {
+        if let Some(state) = &*self.0.lock() {
+            let mut scroll_offset = state.scroll_offset.lock();
+            let item_top = state.item_height * ix;
+            let item_bottom = item_top + state.item_height;
+            let scroll_top = -scroll_offset.y;
+            if item_top < scroll_top {
+                scroll_offset.y = -item_top;
+            } else if item_bottom > scroll_top + state.list_height {
+                scroll_offset.y = -(item_bottom - state.list_height);
+            }
+        }
+    }
+}
+
+impl<V: 'static> Styled for UniformList<V> {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.style
+    }
+}
+
+impl<V: 'static> Element<V> for UniformList<V> {
+    type ElementState = InteractiveElementState;
+
+    fn id(&self) -> Option<crate::ElementId> {
+        Some(self.id.clone())
+    }
+
+    fn initialize(
+        &mut self,
+        _: &mut V,
+        element_state: Option<Self::ElementState>,
+        _: &mut ViewContext<V>,
+    ) -> Self::ElementState {
+        element_state.unwrap_or_default()
+    }
+
+    fn layout(
+        &mut self,
+        _view_state: &mut V,
+        _element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) -> LayoutId {
+        cx.request_layout(&self.computed_style(), None)
+    }
+
+    fn paint(
+        &mut self,
+        bounds: crate::Bounds<crate::Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        let style = self.computed_style();
+        style.paint(bounds, cx);
+
+        let border = style.border_widths.to_pixels(cx.rem_size());
+        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
+
+        let padded_bounds = Bounds::from_corners(
+            bounds.origin + point(border.left + padding.left, border.top + padding.top),
+            bounds.lower_right()
+                - point(border.right + padding.right, border.bottom + padding.bottom),
+        );
+
+        cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
+            let content_size;
+            if self.item_count > 0 {
+                let item_height = self.measure_item_height(view_state, padded_bounds, cx);
+                if let Some(scroll_handle) = self.scroll_handle.clone() {
+                    scroll_handle.0.lock().replace(ScrollHandleState {
+                        item_height,
+                        list_height: padded_bounds.size.height,
+                        scroll_offset: element_state.track_scroll_offset(),
+                    });
+                }
+                let visible_item_count = if item_height > px(0.) {
+                    (padded_bounds.size.height / item_height).ceil() as usize + 1
+                } else {
+                    0
+                };
+                let scroll_offset = element_state
+                    .scroll_offset()
+                    .map_or((0.0).into(), |offset| offset.y);
+                let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
+                let visible_range = first_visible_element_ix
+                    ..cmp::min(
+                        first_visible_element_ix + visible_item_count,
+                        self.item_count,
+                    );
+
+                let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
+
+                content_size = Size {
+                    width: padded_bounds.size.width,
+                    height: item_height * self.item_count,
+                };
+
+                cx.with_z_index(1, |cx| {
+                    for (item, ix) in items.iter_mut().zip(visible_range) {
+                        item.initialize(view_state, cx);
+
+                        let layout_id = item.layout(view_state, cx);
+                        cx.compute_layout(
+                            layout_id,
+                            Size {
+                                width: AvailableSpace::Definite(bounds.size.width),
+                                height: AvailableSpace::Definite(item_height),
+                            },
+                        );
+                        let offset =
+                            padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
+                        cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
+                    }
+                });
+            } else {
+                content_size = Size {
+                    width: bounds.size.width,
+                    height: px(0.),
+                };
+            }
+
+            let overflow = point(style.overflow.x, Overflow::Scroll);
+
+            cx.with_z_index(0, |cx| {
+                self.interactivity
+                    .paint(bounds, content_size, overflow, element_state, cx);
+            });
+        })
+    }
+}
+
+impl<V> UniformList<V> {
+    fn measure_item_height(
+        &self,
+        view_state: &mut V,
+        list_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<V>,
+    ) -> Pixels {
+        let mut items = (self.render_items)(view_state, 0..1, cx);
+        debug_assert!(items.len() == 1);
+        let mut item_to_measure = items.pop().unwrap();
+        item_to_measure.initialize(view_state, cx);
+        let layout_id = item_to_measure.layout(view_state, cx);
+        cx.compute_layout(
+            layout_id,
+            Size {
+                width: AvailableSpace::Definite(list_bounds.size.width),
+                height: AvailableSpace::MinContent,
+            },
+        );
+        cx.layout_bounds(layout_id).size.height
+    }
+
+    pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
+        self.scroll_handle = Some(handle);
+        self
+    }
+}
+
+impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
+    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+        self.interactivity.as_stateless_mut()
+    }
+}
+
+impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
+    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+        &mut self.interactivity
+    }
+}
+
+impl<V: 'static> Component<V> for UniformList<V> {
+    fn render(self) -> AnyElement<V> {
+        AnyElement::new(self)
+    }
+}

crates/gpui2/src/executor.rs 🔗

@@ -68,7 +68,8 @@ impl<T> Future for Task<T> {
         }
     }
 }
-
+type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
+type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
 impl BackgroundExecutor {
     pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
         Self { dispatcher }
@@ -81,10 +82,16 @@ impl BackgroundExecutor {
         R: Send + 'static,
     {
         let dispatcher = self.dispatcher.clone();
-        let (runnable, task) =
-            async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
-        runnable.schedule();
-        Task::Spawned(task)
+        fn inner<R: Send + 'static>(
+            dispatcher: Arc<dyn PlatformDispatcher>,
+            future: AnyFuture<R>,
+        ) -> Task<R> {
+            let (runnable, task) =
+                async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
+            runnable.schedule();
+            Task::Spawned(task)
+        }
+        inner::<R>(dispatcher, Box::pin(future))
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -243,11 +250,17 @@ impl ForegroundExecutor {
         R: 'static,
     {
         let dispatcher = self.dispatcher.clone();
-        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
-            dispatcher.dispatch_on_main_thread(runnable)
-        });
-        runnable.schedule();
-        Task::Spawned(task)
+        fn inner<R: 'static>(
+            dispatcher: Arc<dyn PlatformDispatcher>,
+            future: AnyLocalFuture<R>,
+        ) -> Task<R> {
+            let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+                dispatcher.dispatch_on_main_thread(runnable)
+            });
+            runnable.schedule();
+            Task::Spawned(task)
+        }
+        inner::<R>(dispatcher, Box::pin(future))
     }
 }
 

crates/gpui2/src/geometry.rs 🔗

@@ -267,6 +267,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
     }
 }
 
+impl From<Size<Pixels>> for Size<DefiniteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
+impl From<Size<Pixels>> for Size<AbsoluteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
 impl Size<Length> {
     pub fn full() -> Self {
         Self {
@@ -558,6 +576,15 @@ impl Edges<DefiniteLength> {
             left: px(0.).into(),
         }
     }
+
+    pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
+        Edges {
+            top: self.top.to_pixels(parent_size.height, rem_size),
+            right: self.right.to_pixels(parent_size.width, rem_size),
+            bottom: self.bottom.to_pixels(parent_size.height, rem_size),
+            left: self.left.to_pixels(parent_size.width, rem_size),
+        }
+    }
 }
 
 impl Edges<AbsoluteLength> {
@@ -689,16 +716,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
 pub struct Pixels(pub(crate) f32);
 
 impl std::ops::Div for Pixels {
-    type Output = Self;
+    type Output = f32;
 
     fn div(self, rhs: Self) -> Self::Output {
-        Self(self.0 / rhs.0)
+        self.0 / rhs.0
     }
 }
 
 impl std::ops::DivAssign for Pixels {
     fn div_assign(&mut self, rhs: Self) {
-        self.0 /= rhs.0;
+        *self = Self(self.0 / rhs.0);
     }
 }
 
@@ -750,14 +777,6 @@ impl Pixels {
     pub const ZERO: Pixels = Pixels(0.0);
     pub const MAX: Pixels = Pixels(f32::MAX);
 
-    pub fn as_usize(&self) -> usize {
-        self.0 as usize
-    }
-
-    pub fn as_isize(&self) -> isize {
-        self.0 as isize
-    }
-
     pub fn floor(&self) -> Self {
         Self(self.0.floor())
     }

crates/gpui2/src/gpui2.rs 🔗

@@ -24,6 +24,7 @@ mod text_system;
 mod util;
 mod view;
 mod window;
+mod window_input_handler;
 
 mod private {
     /// A mechanism for restricting implementations of a trait to only those in GPUI.
@@ -36,6 +37,7 @@ pub use anyhow::Result;
 pub use app::*;
 pub use assets::*;
 pub use color::*;
+pub use ctor::ctor;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;
@@ -64,6 +66,7 @@ pub use text_system::*;
 pub use util::arc_cow::ArcCow;
 pub use view::*;
 pub use window::*;
+pub use window_input_handler::*;
 
 use derive_more::{Deref, DerefMut};
 use std::{

crates/gpui2/src/interactive.rs 🔗

@@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
 const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
 
 pub trait StatelessInteractive<V: 'static>: Element<V> {
-    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
+    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
 
     fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
     {
-        self.stateless_interaction().hover_style = f(StyleRefinement::default());
+        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
         self
     }
 
@@ -43,7 +43,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction().group_hover_style = Some(GroupStyle {
+        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
             group: group_name.into(),
             style: f(StyleRefinement::default()),
         });
@@ -58,7 +58,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .mouse_down_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
@@ -79,7 +79,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .mouse_up_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
@@ -100,7 +100,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .mouse_down_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
@@ -121,7 +121,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .mouse_up_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
@@ -141,7 +141,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .mouse_move_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -158,7 +158,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .scroll_wheel_listeners
             .push(Box::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -174,23 +174,48 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
         C: TryInto<DispatchContext>,
         C::Error: Debug,
     {
-        self.stateless_interaction().dispatch_context =
+        self.stateless_interactivity().dispatch_context =
             context.try_into().expect("invalid dispatch context");
         self
     }
 
+    /// Capture the given action, fires during the capture phase
+    fn capture_action<A: 'static>(
+        mut self,
+        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interactivity().key_listeners.push((
+            TypeId::of::<A>(),
+            Box::new(move |view, action, _dipatch_context, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Capture {
+                    listener(view, action, cx)
+                }
+                None
+            }),
+        ));
+        self
+    }
+
+    /// Add a listener for the given action, fires during the bubble event phase
     fn on_action<A: 'static>(
         mut self,
-        listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
     {
-        self.stateless_interaction().key_listeners.push((
+        self.stateless_interactivity().key_listeners.push((
             TypeId::of::<A>(),
-            Box::new(move |view, event, _, phase, cx| {
-                let event = event.downcast_ref().unwrap();
-                listener(view, event, phase, cx);
+            Box::new(move |view, action, _dispatch_context, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Bubble {
+                    listener(view, action, cx)
+                }
+
                 None
             }),
         ));
@@ -204,7 +229,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction().key_listeners.push((
+        self.stateless_interactivity().key_listeners.push((
             TypeId::of::<KeyDownEvent>(),
             Box::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
@@ -222,7 +247,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction().key_listeners.push((
+        self.stateless_interactivity().key_listeners.push((
             TypeId::of::<KeyUpEvent>(),
             Box::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
@@ -237,7 +262,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction()
+        self.stateless_interactivity()
             .drag_over_styles
             .push((TypeId::of::<S>(), f(StyleRefinement::default())));
         self
@@ -251,7 +276,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction().group_drag_over_styles.push((
+        self.stateless_interactivity().group_drag_over_styles.push((
             TypeId::of::<S>(),
             GroupStyle {
                 group: group_name.into(),
@@ -268,7 +293,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
     where
         Self: Sized,
     {
-        self.stateless_interaction().drop_listeners.push((
+        self.stateless_interactivity().drop_listeners.push((
             TypeId::of::<W>(),
             Box::new(move |view, dragged_view, cx| {
                 listener(view, dragged_view.downcast().unwrap(), cx);
@@ -279,13 +304,13 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
 }
 
 pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
-    fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>;
+    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
 
     fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
     {
-        self.stateful_interaction().active_style = f(StyleRefinement::default());
+        self.stateful_interactivity().active_style = f(StyleRefinement::default());
         self
     }
 
@@ -297,7 +322,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
     where
         Self: Sized,
     {
-        self.stateful_interaction().group_active_style = Some(GroupStyle {
+        self.stateful_interactivity().group_active_style = Some(GroupStyle {
             group: group_name.into(),
             style: f(StyleRefinement::default()),
         });
@@ -311,7 +336,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
     where
         Self: Sized,
     {
-        self.stateful_interaction()
+        self.stateful_interactivity()
             .click_listeners
             .push(Box::new(move |view, event, cx| listener(view, event, cx)));
         self
@@ -326,10 +351,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
         W: 'static + Render,
     {
         debug_assert!(
-            self.stateful_interaction().drag_listener.is_none(),
+            self.stateful_interactivity().drag_listener.is_none(),
             "calling on_drag more than once on the same element is not supported"
         );
-        self.stateful_interaction().drag_listener =
+        self.stateful_interactivity().drag_listener =
             Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
                 view: listener(view_state, cx).into(),
                 cursor_offset,
@@ -342,10 +367,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
         Self: Sized,
     {
         debug_assert!(
-            self.stateful_interaction().hover_listener.is_none(),
+            self.stateful_interactivity().hover_listener.is_none(),
             "calling on_hover more than once on the same element is not supported"
         );
-        self.stateful_interaction().hover_listener = Some(Box::new(listener));
+        self.stateful_interactivity().hover_listener = Some(Box::new(listener));
         self
     }
 
@@ -358,10 +383,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
         W: 'static + Render,
     {
         debug_assert!(
-            self.stateful_interaction().tooltip_builder.is_none(),
+            self.stateful_interactivity().tooltip_builder.is_none(),
             "calling tooltip more than once on the same element is not supported"
         );
-        self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| {
+        self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
             build_tooltip(view_state, cx).into()
         }));
 
@@ -369,11 +394,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
     }
 }
 
-pub trait ElementInteraction<V: 'static>: 'static {
-    fn as_stateless(&self) -> &StatelessInteraction<V>;
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
-    fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>;
+pub trait ElementInteractivity<V: 'static>: 'static {
+    fn as_stateless(&self) -> &StatelessInteractivity<V>;
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
 
     fn initialize<R>(
         &mut self,
@@ -382,6 +407,8 @@ pub trait ElementInteraction<V: 'static>: 'static {
     ) -> R {
         if let Some(stateful) = self.as_stateful_mut() {
             cx.with_element_id(stateful.id.clone(), |global_id, cx| {
+                // In addition to any key down/up listeners registered directly on the element,
+                // we also add a key listener to match actions from the keymap.
                 stateful.key_listeners.push((
                     TypeId::of::<KeyDownEvent>(),
                     Box::new(move |_, key_down, context, phase, cx| {
@@ -736,11 +763,11 @@ pub trait ElementInteraction<V: 'static>: 'static {
 }
 
 #[derive(Deref, DerefMut)]
-pub struct StatefulInteraction<V> {
+pub struct StatefulInteractivity<V> {
     pub id: ElementId,
     #[deref]
     #[deref_mut]
-    stateless: StatelessInteraction<V>,
+    stateless: StatelessInteractivity<V>,
     click_listeners: SmallVec<[ClickListener<V>; 2]>,
     active_style: StyleRefinement,
     group_active_style: Option<GroupStyle>,
@@ -749,42 +776,42 @@ pub struct StatefulInteraction<V> {
     tooltip_builder: Option<TooltipBuilder<V>>,
 }
 
-impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
-    fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
+impl<V: 'static> StatefulInteractivity<V> {
+    pub fn new(id: ElementId, stateless: StatelessInteractivity<V>) -> Self {
+        Self {
+            id,
+            stateless,
+            click_listeners: SmallVec::new(),
+            active_style: StyleRefinement::default(),
+            group_active_style: None,
+            drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
+        }
+    }
+}
+
+impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
         Some(self)
     }
 
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
         Some(self)
     }
 
-    fn as_stateless(&self) -> &StatelessInteraction<V> {
+    fn as_stateless(&self) -> &StatelessInteractivity<V> {
         &self.stateless
     }
 
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
         &mut self.stateless
     }
 }
 
-impl<V> From<ElementId> for StatefulInteraction<V> {
-    fn from(id: ElementId) -> Self {
-        Self {
-            id,
-            stateless: StatelessInteraction::default(),
-            click_listeners: SmallVec::new(),
-            drag_listener: None,
-            hover_listener: None,
-            tooltip_builder: None,
-            active_style: StyleRefinement::default(),
-            group_active_style: None,
-        }
-    }
-}
-
 type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
 
-pub struct StatelessInteraction<V> {
+pub struct StatelessInteractivity<V> {
     pub dispatch_context: DispatchContext,
     pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
     pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
@@ -798,9 +825,9 @@ pub struct StatelessInteraction<V> {
     drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
 }
 
-impl<V> StatelessInteraction<V> {
-    pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> {
-        StatefulInteraction {
+impl<V> StatelessInteractivity<V> {
+    pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
+        StatefulInteractivity {
             id: id.into(),
             stateless: self,
             click_listeners: SmallVec::new(),
@@ -876,9 +903,15 @@ impl InteractiveElementState {
             .as_ref()
             .map(|offset| offset.lock().clone())
     }
+
+    pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
+        self.scroll_offset
+            .get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
+            .clone()
+    }
 }
 
-impl<V> Default for StatelessInteraction<V> {
+impl<V> Default for StatelessInteractivity<V> {
     fn default() -> Self {
         Self {
             dispatch_context: DispatchContext::default(),
@@ -896,20 +929,20 @@ impl<V> Default for StatelessInteraction<V> {
     }
 }
 
-impl<V: 'static> ElementInteraction<V> for StatelessInteraction<V> {
-    fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
+impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
         None
     }
 
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
         None
     }
 
-    fn as_stateless(&self) -> &StatelessInteraction<V> {
+    fn as_stateless(&self) -> &StatelessInteractivity<V> {
         self
     }
 
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
         self
     }
 }
@@ -1236,7 +1269,7 @@ pub type KeyListener<V> = Box<
 mod test {
     use crate::{
         self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
-        StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext,
+        StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
     };
 
     struct TestView {
@@ -1248,19 +1281,13 @@ mod test {
     actions!(TestAction);
 
     impl Render for TestView {
-        type Element = Div<Self, StatefulInteraction<Self>>;
+        type Element = Div<Self, StatefulInteractivity<Self>>;
 
         fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
             div().id("testview").child(
                 div()
-                    .on_key_down(|this: &mut TestView, _, _, _| {
-                        dbg!("ola!");
-                        this.saw_key_down = true
-                    })
-                    .on_action(|this: &mut TestView, _: &TestAction, _, _| {
-                        dbg!("ola!");
-                        this.saw_action = true
-                    })
+                    .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
+                    .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
                     .track_focus(&self.focus_handle),
             )
         }

crates/gpui2/src/platform/mac/metal_renderer.rs 🔗

@@ -87,7 +87,7 @@ impl MetalRenderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
-        let paths_rasterization_pipeline_state = build_pipeline_state(
+        let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
             &device,
             &library,
             "paths_rasterization",
@@ -823,7 +823,40 @@ fn build_pipeline_state(
     color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
     color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
     color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-    descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
+
+    device
+        .new_render_pipeline_state(&descriptor)
+        .expect("could not create render pipeline state")
+}
+
+fn build_path_rasterization_pipeline_state(
+    device: &metal::DeviceRef,
+    library: &metal::LibraryRef,
+    label: &str,
+    vertex_fn_name: &str,
+    fragment_fn_name: &str,
+    pixel_format: metal::MTLPixelFormat,
+) -> metal::RenderPipelineState {
+    let vertex_fn = library
+        .get_function(vertex_fn_name, None)
+        .expect("error locating vertex function");
+    let fragment_fn = library
+        .get_function(fragment_fn_name, None)
+        .expect("error locating fragment function");
+
+    let descriptor = metal::RenderPipelineDescriptor::new();
+    descriptor.set_label(label);
+    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
+    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
+    color_attachment.set_pixel_format(pixel_format);
+    color_attachment.set_blending_enabled(true);
+    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
 
     device
         .new_render_pipeline_state(&descriptor)

crates/gpui2/src/platform/mac/shaders.metal 🔗

@@ -5,10 +5,11 @@ using namespace metal;
 
 float4 hsla_to_rgba(Hsla hsla);
 float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
-                          Bounds_ScaledPixels clip_bounds,
                           constant Size_DevicePixels *viewport_size);
 float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
                         constant Size_DevicePixels *atlas_size);
+float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
+                               Bounds_ScaledPixels clip_bounds);
 float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
                Corners_ScaledPixels corner_radii);
 float gaussian(float x, float sigma);
@@ -21,6 +22,14 @@ struct QuadVertexOutput {
   float4 background_color [[flat]];
   float4 border_color [[flat]];
   uint quad_id [[flat]];
+  float clip_distance [[clip_distance]][4];
+};
+
+struct QuadFragmentInput {
+  float4 position [[position]];
+  float4 background_color [[flat]];
+  float4 border_color [[flat]];
+  uint quad_id [[flat]];
 };
 
 vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
@@ -33,15 +42,21 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
                                     [[buffer(QuadInputIndex_ViewportSize)]]) {
   float2 unit_vertex = unit_vertices[unit_vertex_id];
   Quad quad = quads[quad_id];
-  float4 device_position = to_device_position(
-      unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
+  float4 device_position =
+      to_device_position(unit_vertex, quad.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
+                                                 quad.content_mask.bounds);
   float4 background_color = hsla_to_rgba(quad.background);
   float4 border_color = hsla_to_rgba(quad.border_color);
-  return QuadVertexOutput{device_position, background_color, border_color,
-                          quad_id};
+  return QuadVertexOutput{
+      device_position,
+      background_color,
+      border_color,
+      quad_id,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
 }
 
-fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
+fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
                               constant Quad *quads
                               [[buffer(QuadInputIndex_Quads)]]) {
   Quad quad = quads[input.quad_id];
@@ -117,6 +132,13 @@ struct ShadowVertexOutput {
   float4 position [[position]];
   float4 color [[flat]];
   uint shadow_id [[flat]];
+  float clip_distance [[clip_distance]][4];
+};
+
+struct ShadowFragmentInput {
+  float4 position [[position]];
+  float4 color [[flat]];
+  uint shadow_id [[flat]];
 };
 
 vertex ShadowVertexOutput shadow_vertex(
@@ -137,18 +159,20 @@ vertex ShadowVertexOutput shadow_vertex(
   bounds.size.width += 2. * margin;
   bounds.size.height += 2. * margin;
 
-  float4 device_position = to_device_position(
-      unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
+  float4 device_position =
+      to_device_position(unit_vertex, bounds, viewport_size);
+  float4 clip_distance =
+      distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
   float4 color = hsla_to_rgba(shadow.color);
 
   return ShadowVertexOutput{
       device_position,
       color,
       shadow_id,
-  };
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
 }
 
-fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
+fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
                                 constant Shadow *shadows
                                 [[buffer(ShadowInputIndex_Shadows)]]) {
   Shadow shadow = shadows[input.shadow_id];
@@ -197,6 +221,13 @@ struct UnderlineVertexOutput {
   float4 position [[position]];
   float4 color [[flat]];
   uint underline_id [[flat]];
+  float clip_distance [[clip_distance]][4];
+};
+
+struct UnderlineFragmentInput {
+  float4 position [[position]];
+  float4 color [[flat]];
+  uint underline_id [[flat]];
 };
 
 vertex UnderlineVertexOutput underline_vertex(
@@ -208,13 +239,18 @@ vertex UnderlineVertexOutput underline_vertex(
   float2 unit_vertex = unit_vertices[unit_vertex_id];
   Underline underline = underlines[underline_id];
   float4 device_position =
-      to_device_position(unit_vertex, underline.bounds,
-                         underline.content_mask.bounds, viewport_size);
+      to_device_position(unit_vertex, underline.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
+                                                 underline.content_mask.bounds);
   float4 color = hsla_to_rgba(underline.color);
-  return UnderlineVertexOutput{device_position, color, underline_id};
+  return UnderlineVertexOutput{
+      device_position,
+      color,
+      underline_id,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
 }
 
-fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
+fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
                                    constant Underline *underlines
                                    [[buffer(UnderlineInputIndex_Underlines)]]) {
   Underline underline = underlines[input.underline_id];
@@ -244,7 +280,13 @@ struct MonochromeSpriteVertexOutput {
   float4 position [[position]];
   float2 tile_position;
   float4 color [[flat]];
-  uint sprite_id [[flat]];
+  float clip_distance [[clip_distance]][4];
+};
+
+struct MonochromeSpriteFragmentInput {
+  float4 position [[position]];
+  float2 tile_position;
+  float4 color [[flat]];
 };
 
 vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
@@ -255,32 +297,31 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
     [[buffer(SpriteInputIndex_ViewportSize)]],
     constant Size_DevicePixels *atlas_size
     [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
-
   float2 unit_vertex = unit_vertices[unit_vertex_id];
   MonochromeSprite sprite = sprites[sprite_id];
-  // Don't apply content mask at the vertex level because we don't have time
-  // to make sampling from the texture match the mask.
-  float4 device_position = to_device_position(unit_vertex, sprite.bounds,
-                                              sprite.bounds, viewport_size);
+  float4 device_position =
+      to_device_position(unit_vertex, sprite.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
+                                                 sprite.content_mask.bounds);
   float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
   float4 color = hsla_to_rgba(sprite.color);
-  return MonochromeSpriteVertexOutput{device_position, tile_position, color,
-                                      sprite_id};
+  return MonochromeSpriteVertexOutput{
+      device_position,
+      tile_position,
+      color,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
 }
 
 fragment float4 monochrome_sprite_fragment(
-    MonochromeSpriteVertexOutput input [[stage_in]],
+    MonochromeSpriteFragmentInput input [[stage_in]],
     constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
     texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
-  MonochromeSprite sprite = sprites[input.sprite_id];
   constexpr sampler atlas_texture_sampler(mag_filter::linear,
                                           min_filter::linear);
   float4 sample =
       atlas_texture.sample(atlas_texture_sampler, input.tile_position);
-  float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
-                                 Corners_ScaledPixels{0., 0., 0., 0.});
   float4 color = input.color;
-  color.a *= sample.a * saturate(0.5 - clip_distance);
+  color.a *= sample.a;
   return color;
 }
 
@@ -288,6 +329,13 @@ struct PolychromeSpriteVertexOutput {
   float4 position [[position]];
   float2 tile_position;
   uint sprite_id [[flat]];
+  float clip_distance [[clip_distance]][4];
+};
+
+struct PolychromeSpriteFragmentInput {
+  float4 position [[position]];
+  float2 tile_position;
+  uint sprite_id [[flat]];
 };
 
 vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
@@ -301,17 +349,20 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
 
   float2 unit_vertex = unit_vertices[unit_vertex_id];
   PolychromeSprite sprite = sprites[sprite_id];
-  // Don't apply content mask at the vertex level because we don't have time
-  // to make sampling from the texture match the mask.
-  float4 device_position = to_device_position(unit_vertex, sprite.bounds,
-                                              sprite.bounds, viewport_size);
+  float4 device_position =
+      to_device_position(unit_vertex, sprite.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
+                                                 sprite.content_mask.bounds);
   float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
-  return PolychromeSpriteVertexOutput{device_position, tile_position,
-                                      sprite_id};
+  return PolychromeSpriteVertexOutput{
+      device_position,
+      tile_position,
+      sprite_id,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
 }
 
 fragment float4 polychrome_sprite_fragment(
-    PolychromeSpriteVertexOutput input [[stage_in]],
+    PolychromeSpriteFragmentInput input [[stage_in]],
     constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
     texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
   PolychromeSprite sprite = sprites[input.sprite_id];
@@ -319,11 +370,8 @@ fragment float4 polychrome_sprite_fragment(
                                           min_filter::linear);
   float4 sample =
       atlas_texture.sample(atlas_texture_sampler, input.tile_position);
-  float quad_distance =
+  float distance =
       quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
-  float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
-                                 Corners_ScaledPixels{0., 0., 0., 0.});
-  float distance = max(quad_distance, clip_distance);
 
   float4 color = sample;
   if (sprite.grayscale) {
@@ -385,7 +433,6 @@ struct PathSpriteVertexOutput {
   float4 position [[position]];
   float2 tile_position;
   float4 color [[flat]];
-  uint sprite_id [[flat]];
 };
 
 vertex PathSpriteVertexOutput path_sprite_vertex(
@@ -401,19 +448,17 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
   PathSprite sprite = sprites[sprite_id];
   // Don't apply content mask because it was already accounted for when
   // rasterizing the path.
-  float4 device_position = to_device_position(unit_vertex, sprite.bounds,
-                                              sprite.bounds, viewport_size);
+  float4 device_position =
+      to_device_position(unit_vertex, sprite.bounds, viewport_size);
   float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
   float4 color = hsla_to_rgba(sprite.color);
-  return PathSpriteVertexOutput{device_position, tile_position, color,
-                                sprite_id};
+  return PathSpriteVertexOutput{device_position, tile_position, color};
 }
 
 fragment float4 path_sprite_fragment(
     PathSpriteVertexOutput input [[stage_in]],
     constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
     texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
-  PathSprite sprite = sprites[input.sprite_id];
   constexpr sampler atlas_texture_sampler(mag_filter::linear,
                                           min_filter::linear);
   float4 sample =
@@ -473,16 +518,10 @@ float4 hsla_to_rgba(Hsla hsla) {
 }
 
 float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
-                          Bounds_ScaledPixels clip_bounds,
                           constant Size_DevicePixels *input_viewport_size) {
   float2 position =
       unit_vertex * float2(bounds.size.width, bounds.size.height) +
       float2(bounds.origin.x, bounds.origin.y);
-  position.x = max(clip_bounds.origin.x, position.x);
-  position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
-  position.y = max(clip_bounds.origin.y, position.y);
-  position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
-
   float2 viewport_size = float2((float)input_viewport_size->width,
                                 (float)input_viewport_size->height);
   float2 device_position =
@@ -551,3 +590,14 @@ float blur_along_x(float x, float y, float sigma, float corner,
       0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
   return integral.y - integral.x;
 }
+
+float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
+                               Bounds_ScaledPixels clip_bounds) {
+  float2 position =
+      unit_vertex * float2(bounds.size.width, bounds.size.height) +
+      float2(bounds.origin.x, bounds.origin.y);
+  return float4(position.x - clip_bounds.origin.x,
+                clip_bounds.origin.x + clip_bounds.size.width - position.x,
+                position.y - clip_bounds.origin.y,
+                clip_bounds.origin.y + clip_bounds.size.height - position.y);
+}

crates/gpui2/src/scene.rs 🔗

@@ -27,8 +27,8 @@ pub(crate) struct SceneBuilder {
     polychrome_sprites: Vec<PolychromeSprite>,
 }
 
-impl SceneBuilder {
-    pub fn new() -> SceneBuilder {
+impl Default for SceneBuilder {
+    fn default() -> Self {
         SceneBuilder {
             layers_by_order: BTreeMap::new(),
             splitter: BspSplitter::new(),
@@ -40,7 +40,9 @@ impl SceneBuilder {
             polychrome_sprites: Vec::new(),
         }
     }
+}
 
+impl SceneBuilder {
     pub fn build(&mut self) -> Scene {
         // Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
         let mut layer_z_values = vec![0.; self.layers_by_order.len()];

crates/gpui2/src/style.rs 🔗

@@ -281,7 +281,7 @@ impl Style {
     pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
         let rem_size = cx.rem_size();
 
-        cx.stack(0, |cx| {
+        cx.with_z_index(0, |cx| {
             cx.paint_shadows(
                 bounds,
                 self.corner_radii.to_pixels(bounds.size, rem_size),
@@ -291,7 +291,7 @@ impl Style {
 
         let background_color = self.background.as_ref().and_then(Fill::color);
         if background_color.is_some() || self.is_border_visible() {
-            cx.stack(1, |cx| {
+            cx.with_z_index(1, |cx| {
                 cx.paint_quad(
                     bounds,
                     self.corner_radii.to_pixels(bounds.size, rem_size),

crates/gpui2/src/styled.rs 🔗

@@ -1,14 +1,19 @@
 use crate::{
     self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
     DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
-    SharedString, StyleRefinement, Visibility,
+    SharedString, Style, StyleRefinement, Visibility,
 };
 use crate::{BoxShadow, TextStyleRefinement};
+use refineable::Refineable;
 use smallvec::{smallvec, SmallVec};
 
 pub trait Styled {
     fn style(&mut self) -> &mut StyleRefinement;
 
+    fn computed_style(&mut self) -> Style {
+        Style::default().refined(self.style().clone())
+    }
+
     gpui2_macros::style_helpers!();
 
     /// Sets the size of the element to the full width and height.

crates/gpui2/src/text_system/line.rs 🔗

@@ -74,7 +74,6 @@ impl Line {
                     glyph_origin.y += line_height;
                 }
                 prev_glyph_position = glyph.position;
-                let glyph_origin = glyph_origin + baseline_offset;
 
                 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
                 if glyph.index >= run_end {
@@ -125,14 +124,14 @@ impl Line {
                 if max_glyph_bounds.intersects(&content_mask.bounds) {
                     if glyph.is_emoji {
                         cx.paint_emoji(
-                            glyph_origin,
+                            glyph_origin + baseline_offset,
                             run.font_id,
                             glyph.id,
                             self.layout.layout.font_size,
                         )?;
                     } else {
                         cx.paint_glyph(
-                            glyph_origin,
+                            glyph_origin + baseline_offset,
                             run.font_id,
                             glyph.id,
                             self.layout.layout.font_size,

crates/gpui2/src/window.rs 🔗

@@ -2,13 +2,14 @@ use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
     Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
     Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
-    GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
-    KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
-    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
-    PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
-    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener,
+    KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
+    PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel,
+    Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
+    SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
+    TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
+    WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Result};
 use collections::HashMap;
@@ -131,7 +132,12 @@ impl FocusHandle {
             if self.id == ancestor_id {
                 return true;
             } else {
-                ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied();
+                ancestor = cx
+                    .window
+                    .current_frame
+                    .focus_parents_by_child
+                    .get(&ancestor_id)
+                    .copied();
             }
         }
         false
@@ -174,34 +180,39 @@ pub struct Window {
     pub(crate) layout_engine: TaffyLayoutEngine,
     pub(crate) root_view: Option<AnyView>,
     pub(crate) element_id_stack: GlobalElementId,
-    prev_frame_element_states: HashMap<GlobalElementId, AnyBox>,
-    element_states: HashMap<GlobalElementId, AnyBox>,
-    prev_frame_key_matchers: HashMap<GlobalElementId, KeyMatcher>,
-    key_matchers: HashMap<GlobalElementId, KeyMatcher>,
-    z_index_stack: StackingOrder,
-    content_mask_stack: Vec<ContentMask<Pixels>>,
-    element_offset_stack: Vec<Point<Pixels>>,
-    mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
-    key_dispatch_stack: Vec<KeyDispatchStackFrame>,
-    freeze_key_dispatch_stack: bool,
-    focus_stack: Vec<FocusId>,
-    focus_parents_by_child: HashMap<FocusId, FocusId>,
-    pub(crate) focus_listeners: Vec<AnyFocusListener>,
+    pub(crate) previous_frame: Frame,
+    pub(crate) current_frame: Frame,
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
     requested_cursor_style: Option<CursorStyle>,
+    requested_input_handler: Option<Box<dyn PlatformInputHandler>>,
     scale_factor: f32,
     bounds: WindowBounds,
     bounds_observers: SubscriberSet<(), AnyObserver>,
     active: bool,
     activation_observers: SubscriberSet<(), AnyObserver>,
-    pub(crate) scene_builder: SceneBuilder,
     pub(crate) dirty: bool,
     pub(crate) last_blur: Option<Option<FocusId>>,
     pub(crate) focus: Option<FocusId>,
 }
 
+#[derive(Default)]
+pub(crate) struct Frame {
+    element_states: HashMap<GlobalElementId, AnyBox>,
+    key_matchers: HashMap<GlobalElementId, KeyMatcher>,
+    mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
+    pub(crate) focus_listeners: Vec<AnyFocusListener>,
+    key_dispatch_stack: Vec<KeyDispatchStackFrame>,
+    freeze_key_dispatch_stack: bool,
+    focus_parents_by_child: HashMap<FocusId, FocusId>,
+    pub(crate) scene_builder: SceneBuilder,
+    z_index_stack: StackingOrder,
+    content_mask_stack: Vec<ContentMask<Pixels>>,
+    element_offset_stack: Vec<Point<Pixels>>,
+    focus_stack: Vec<FocusId>,
+}
+
 impl Window {
     pub(crate) fn new(
         handle: AnyWindowHandle,
@@ -253,7 +264,7 @@ impl Window {
                 handle
                     .update(&mut cx, |_, cx| cx.dispatch_event(event))
                     .log_err()
-                    .unwrap_or(true)
+                    .unwrap_or(false)
             })
         });
 
@@ -268,29 +279,18 @@ impl Window {
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             element_id_stack: GlobalElementId::default(),
-            prev_frame_element_states: HashMap::default(),
-            element_states: HashMap::default(),
-            prev_frame_key_matchers: HashMap::default(),
-            key_matchers: HashMap::default(),
-            z_index_stack: StackingOrder(SmallVec::new()),
-            content_mask_stack: Vec::new(),
-            element_offset_stack: Vec::new(),
-            mouse_listeners: HashMap::default(),
-            key_dispatch_stack: Vec::new(),
-            freeze_key_dispatch_stack: false,
-            focus_stack: Vec::new(),
-            focus_parents_by_child: HashMap::default(),
-            focus_listeners: Vec::new(),
+            previous_frame: Frame::default(),
+            current_frame: Frame::default(),
             focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             default_prevented: true,
             mouse_position,
             requested_cursor_style: None,
+            requested_input_handler: None,
             scale_factor,
             bounds,
             bounds_observers: SubscriberSet::new(),
             active: false,
             activation_observers: SubscriberSet::new(),
-            scene_builder: SceneBuilder::new(),
             dirty: true,
             last_blur: None,
             focus: None,
@@ -560,6 +560,12 @@ impl<'a> WindowContext<'a> {
             .request_measured_layout(style, rem_size, measure)
     }
 
+    pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
+        self.window
+            .layout_engine
+            .compute_layout(layout_id, available_space)
+    }
+
     /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
     /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
     /// in order to pass your element its `Bounds` automatically.
@@ -605,6 +611,10 @@ impl<'a> WindowContext<'a> {
             .find(|display| display.id() == self.window.display_id)
     }
 
+    pub fn show_character_palette(&self) {
+        self.window.platform_window.show_character_palette();
+    }
+
     /// The scale factor of the display associated with the window. For example, it could
     /// return 2.0 for a "retina" display, indicating that each logical pixel should actually
     /// be rendered as two pixels on screen.
@@ -654,8 +664,9 @@ impl<'a> WindowContext<'a> {
         &mut self,
         handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
-        let order = self.window.z_index_stack.clone();
+        let order = self.window.current_frame.z_index_stack.clone();
         self.window
+            .current_frame
             .mouse_listeners
             .entry(TypeId::of::<Event>())
             .or_default()
@@ -679,9 +690,9 @@ impl<'a> WindowContext<'a> {
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
     pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.z_index_stack.push(z_index);
+        self.window.current_frame.z_index_stack.push(z_index);
         let result = f(self);
-        self.window.z_index_stack.pop();
+        self.window.current_frame.z_index_stack.pop();
         result
     }
 
@@ -699,8 +710,8 @@ impl<'a> WindowContext<'a> {
             let mut shadow_bounds = bounds;
             shadow_bounds.origin += shadow.offset;
             shadow_bounds.dilate(shadow.spread_radius);
-            window.scene_builder.insert(
-                &window.z_index_stack,
+            window.current_frame.scene_builder.insert(
+                &window.current_frame.z_index_stack,
                 Shadow {
                     order: 0,
                     bounds: shadow_bounds.scale(scale_factor),
@@ -727,8 +738,8 @@ impl<'a> WindowContext<'a> {
         let content_mask = self.content_mask();
 
         let window = &mut *self.window;
-        window.scene_builder.insert(
-            &window.z_index_stack,
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
             Quad {
                 order: 0,
                 bounds: bounds.scale(scale_factor),
@@ -748,9 +759,10 @@ impl<'a> WindowContext<'a> {
         path.content_mask = content_mask;
         path.color = color.into();
         let window = &mut *self.window;
-        window
-            .scene_builder
-            .insert(&window.z_index_stack, path.scale(scale_factor));
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
+            path.scale(scale_factor),
+        );
     }
 
     /// Paint an underline into the scene for the current frame at the current z-index.
@@ -772,8 +784,8 @@ impl<'a> WindowContext<'a> {
         };
         let content_mask = self.content_mask();
         let window = &mut *self.window;
-        window.scene_builder.insert(
-            &window.z_index_stack,
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
             Underline {
                 order: 0,
                 bounds: bounds.scale(scale_factor),
@@ -787,6 +799,7 @@ impl<'a> WindowContext<'a> {
     }
 
     /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
+    /// The y component of the origin is the baseline of the glyph.
     pub fn paint_glyph(
         &mut self,
         origin: Point<Pixels>,
@@ -825,8 +838,8 @@ impl<'a> WindowContext<'a> {
             };
             let content_mask = self.content_mask().scale(scale_factor);
             let window = &mut *self.window;
-            window.scene_builder.insert(
-                &window.z_index_stack,
+            window.current_frame.scene_builder.insert(
+                &window.current_frame.z_index_stack,
                 MonochromeSprite {
                     order: 0,
                     bounds,
@@ -840,6 +853,7 @@ impl<'a> WindowContext<'a> {
     }
 
     /// Paint an emoji glyph into the scene for the current frame at the current z-index.
+    /// The y component of the origin is the baseline of the glyph.
     pub fn paint_emoji(
         &mut self,
         origin: Point<Pixels>,
@@ -875,8 +889,8 @@ impl<'a> WindowContext<'a> {
             let content_mask = self.content_mask().scale(scale_factor);
             let window = &mut *self.window;
 
-            window.scene_builder.insert(
-                &window.z_index_stack,
+            window.current_frame.scene_builder.insert(
+                &window.current_frame.z_index_stack,
                 PolychromeSprite {
                     order: 0,
                     bounds,
@@ -917,8 +931,8 @@ impl<'a> WindowContext<'a> {
         let content_mask = self.content_mask().scale(scale_factor);
 
         let window = &mut *self.window;
-        window.scene_builder.insert(
-            &window.z_index_stack,
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
             MonochromeSprite {
                 order: 0,
                 bounds,
@@ -953,8 +967,8 @@ impl<'a> WindowContext<'a> {
         let corner_radii = corner_radii.scale(scale_factor);
 
         let window = &mut *self.window;
-        window.scene_builder.insert(
-            &window.z_index_stack,
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
             PolychromeSprite {
                 order: 0,
                 bounds,
@@ -999,7 +1013,7 @@ impl<'a> WindowContext<'a> {
         }
 
         self.window.root_view = Some(root_view);
-        let scene = self.window.scene_builder.build();
+        let scene = self.window.current_frame.scene_builder.build();
 
         self.window.platform_window.draw(scene);
         let cursor_style = self
@@ -1008,43 +1022,28 @@ impl<'a> WindowContext<'a> {
             .take()
             .unwrap_or(CursorStyle::Arrow);
         self.platform.set_cursor_style(cursor_style);
+        if let Some(handler) = self.window.requested_input_handler.take() {
+            self.window.platform_window.set_input_handler(handler);
+        }
 
         self.window.dirty = false;
     }
 
+    /// Rotate the current frame and the previous frame, then clear the current frame.
+    /// We repopulate all state in the current frame during each paint.
     fn start_frame(&mut self) {
         self.text_system().start_frame();
 
         let window = &mut *self.window;
-
-        // Move the current frame element states to the previous frame.
-        // The new empty element states map will be populated for any element states we
-        // reference during the upcoming frame.
-        mem::swap(
-            &mut window.element_states,
-            &mut window.prev_frame_element_states,
-        );
-        window.element_states.clear();
-
-        // Make the current key matchers the previous, and then clear the current.
-        // An empty key matcher map will be created for every identified element in the
-        // upcoming frame.
-        mem::swap(
-            &mut window.key_matchers,
-            &mut window.prev_frame_key_matchers,
-        );
-        window.key_matchers.clear();
-
-        // Clear mouse event listeners, because elements add new element listeners
-        // when the upcoming frame is painted.
-        window.mouse_listeners.values_mut().for_each(Vec::clear);
-
-        // Clear focus state, because we determine what is focused when the new elements
-        // in the upcoming frame are initialized.
-        window.focus_listeners.clear();
-        window.key_dispatch_stack.clear();
-        window.focus_parents_by_child.clear();
-        window.freeze_key_dispatch_stack = false;
+        mem::swap(&mut window.previous_frame, &mut window.current_frame);
+        let frame = &mut window.current_frame;
+        frame.element_states.clear();
+        frame.key_matchers.clear();
+        frame.mouse_listeners.values_mut().for_each(Vec::clear);
+        frame.focus_listeners.clear();
+        frame.key_dispatch_stack.clear();
+        frame.focus_parents_by_child.clear();
+        frame.freeze_key_dispatch_stack = false;
     }
 
     /// Dispatch a mouse or keyboard event on the window.
@@ -1108,6 +1107,7 @@ impl<'a> WindowContext<'a> {
         if let Some(any_mouse_event) = event.mouse_event() {
             if let Some(mut handlers) = self
                 .window
+                .current_frame
                 .mouse_listeners
                 .remove(&any_mouse_event.type_id())
             {
@@ -1142,17 +1142,19 @@ impl<'a> WindowContext<'a> {
                 // Just in case any handlers added new handlers, which is weird, but possible.
                 handlers.extend(
                     self.window
+                        .current_frame
                         .mouse_listeners
                         .get_mut(&any_mouse_event.type_id())
                         .into_iter()
                         .flat_map(|handlers| handlers.drain(..)),
                 );
                 self.window
+                    .current_frame
                     .mouse_listeners
                     .insert(any_mouse_event.type_id(), handlers);
             }
         } else if let Some(any_key_event) = event.keyboard_event() {
-            let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack);
+            let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
             let key_event_type = any_key_event.type_id();
             let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
 
@@ -1212,10 +1214,10 @@ impl<'a> WindowContext<'a> {
             }
 
             drop(context_stack);
-            self.window.key_dispatch_stack = key_dispatch_stack;
+            self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
         }
 
-        true
+        !self.app.propagate_event
     }
 
     /// Attempt to map a keystroke to an action based on the keymap.
@@ -1227,13 +1229,14 @@ impl<'a> WindowContext<'a> {
     ) -> KeyMatch {
         let key_match = self
             .window
+            .current_frame
             .key_matchers
             .get_mut(element_id)
             .unwrap()
             .match_keystroke(keystroke, context_stack);
 
         if key_match.is_some() {
-            for matcher in self.window.key_matchers.values_mut() {
+            for matcher in self.window.current_frame.key_matchers.values_mut() {
                 matcher.clear_pending();
             }
         }
@@ -1493,11 +1496,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         window.element_id_stack.push(id.into());
         let global_id = window.element_id_stack.clone();
 
-        if window.key_matchers.get(&global_id).is_none() {
-            window.key_matchers.insert(
+        if window.current_frame.key_matchers.get(&global_id).is_none() {
+            window.current_frame.key_matchers.insert(
                 global_id.clone(),
                 window
-                    .prev_frame_key_matchers
+                    .previous_frame
+                    .key_matchers
                     .remove(&global_id)
                     .unwrap_or_else(|| KeyMatcher::new(keymap)),
             );
@@ -1517,9 +1521,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
         let mask = mask.intersect(&self.content_mask());
-        self.window_mut().content_mask_stack.push(mask);
+        self.window_mut()
+            .current_frame
+            .content_mask_stack
+            .push(mask);
         let result = f(self);
-        self.window_mut().content_mask_stack.pop();
+        self.window_mut().current_frame.content_mask_stack.pop();
         result
     }
 
@@ -1535,15 +1542,19 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         };
 
         let offset = self.element_offset() + offset;
-        self.window_mut().element_offset_stack.push(offset);
+        self.window_mut()
+            .current_frame
+            .element_offset_stack
+            .push(offset);
         let result = f(self);
-        self.window_mut().element_offset_stack.pop();
+        self.window_mut().current_frame.element_offset_stack.pop();
         result
     }
 
     /// Obtain the current element offset.
     fn element_offset(&self) -> Point<Pixels> {
         self.window()
+            .current_frame
             .element_offset_stack
             .last()
             .copied()
@@ -1565,9 +1576,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         self.with_element_id(id, |global_id, cx| {
             if let Some(any) = cx
                 .window_mut()
+                .current_frame
                 .element_states
                 .remove(&global_id)
-                .or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id))
+                .or_else(|| {
+                    cx.window_mut()
+                        .previous_frame
+                        .element_states
+                        .remove(&global_id)
+                })
             {
                 // Using the extra inner option to avoid needing to reallocate a new box.
                 let mut state_box = any
@@ -1578,11 +1595,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
                     .expect("element state is already on the stack");
                 let (result, state) = f(Some(state), cx);
                 state_box.replace(state);
-                cx.window_mut().element_states.insert(global_id, state_box);
+                cx.window_mut()
+                    .current_frame
+                    .element_states
+                    .insert(global_id, state_box);
                 result
             } else {
                 let (result, state) = f(None, cx);
                 cx.window_mut()
+                    .current_frame
                     .element_states
                     .insert(global_id, Box::new(Some(state)));
                 result
@@ -1610,6 +1631,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
     /// Obtain the current content mask.
     fn content_mask(&self) -> ContentMask<Pixels> {
         self.window()
+            .current_frame
             .content_mask_stack
             .last()
             .cloned()
@@ -1693,10 +1715,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self.window_cx
     }
 
-    pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.z_index_stack.push(order);
+    pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.window.current_frame.z_index_stack.push(z_index);
         let result = f(self);
-        self.window.z_index_stack.pop();
+        self.window.current_frame.z_index_stack.pop();
         result
     }
 
@@ -1851,11 +1873,14 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
     ) {
         let handle = self.view().downgrade();
-        self.window.focus_listeners.push(Box::new(move |event, cx| {
-            handle
-                .update(cx, |view, cx| listener(view, event, cx))
-                .log_err();
-        }));
+        self.window
+            .current_frame
+            .focus_listeners
+            .push(Box::new(move |event, cx| {
+                handle
+                    .update(cx, |view, cx| listener(view, event, cx))
+                    .log_err();
+            }));
     }
 
     pub fn with_key_listeners<R>(
@@ -1863,8 +1888,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        let old_stack_len = self.window.key_dispatch_stack.len();
-        if !self.window.freeze_key_dispatch_stack {
+        let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
+        if !self.window.current_frame.freeze_key_dispatch_stack {
             for (event_type, listener) in key_listeners {
                 let handle = self.view().downgrade();
                 let listener = Box::new(
@@ -1880,19 +1905,22 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                             .flatten()
                     },
                 );
-                self.window
-                    .key_dispatch_stack
-                    .push(KeyDispatchStackFrame::Listener {
+                self.window.current_frame.key_dispatch_stack.push(
+                    KeyDispatchStackFrame::Listener {
                         event_type,
                         listener,
-                    });
+                    },
+                );
             }
         }
 
         let result = f(self);
 
-        if !self.window.freeze_key_dispatch_stack {
-            self.window.key_dispatch_stack.truncate(old_stack_len);
+        if !self.window.current_frame.freeze_key_dispatch_stack {
+            self.window
+                .current_frame
+                .key_dispatch_stack
+                .truncate(old_stack_len);
         }
 
         result
@@ -1907,16 +1935,17 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             return f(self);
         }
 
-        if !self.window.freeze_key_dispatch_stack {
+        if !self.window.current_frame.freeze_key_dispatch_stack {
             self.window
+                .current_frame
                 .key_dispatch_stack
                 .push(KeyDispatchStackFrame::Context(context));
         }
 
         let result = f(self);
 
-        if !self.window.freeze_key_dispatch_stack {
-            self.window.key_dispatch_stack.pop();
+        if !self.window.previous_frame.freeze_key_dispatch_stack {
+            self.window.previous_frame.key_dispatch_stack.pop();
         }
 
         result
@@ -1927,20 +1956,21 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         focus_handle: FocusHandle,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        if let Some(parent_focus_id) = self.window.focus_stack.last().copied() {
+        if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
             self.window
+                .current_frame
                 .focus_parents_by_child
                 .insert(focus_handle.id, parent_focus_id);
         }
-        self.window.focus_stack.push(focus_handle.id);
+        self.window.current_frame.focus_stack.push(focus_handle.id);
 
         if Some(focus_handle.id) == self.window.focus {
-            self.window.freeze_key_dispatch_stack = true;
+            self.window.current_frame.freeze_key_dispatch_stack = true;
         }
 
         let result = f(self);
 
-        self.window.focus_stack.pop();
+        self.window.current_frame.focus_stack.pop();
         result
     }
 
@@ -1995,6 +2025,19 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     }
 }
 
+impl<V> ViewContext<'_, V>
+where
+    V: InputHandler + 'static,
+{
+    pub fn handle_text_input(&mut self) {
+        self.window.requested_input_handler = Some(Box::new(WindowInputHandler {
+            cx: self.app.this.clone(),
+            window: self.window_handle(),
+            handler: self.view().downgrade(),
+        }));
+    }
+}
+
 impl<V> ViewContext<'_, V>
 where
     V: EventEmitter,

crates/gpui2/src/window_input_handler.rs 🔗

@@ -0,0 +1,89 @@
+use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView};
+use std::{ops::Range, rc::Weak};
+
+pub struct WindowInputHandler<V>
+where
+    V: InputHandler,
+{
+    pub cx: Weak<AppCell>,
+    pub window: AnyWindowHandle,
+    pub handler: WeakView<V>,
+}
+
+impl<V: InputHandler + 'static> PlatformInputHandler for WindowInputHandler<V> {
+    fn selected_text_range(&self) -> Option<std::ops::Range<usize>> {
+        self.update(|view, cx| view.selected_text_range(cx))
+            .flatten()
+    }
+
+    fn marked_text_range(&self) -> Option<std::ops::Range<usize>> {
+        self.update(|view, cx| view.marked_text_range(cx)).flatten()
+    }
+
+    fn text_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<String> {
+        self.update(|view, cx| view.text_for_range(range_utf16, cx))
+            .flatten()
+    }
+
+    fn replace_text_in_range(
+        &mut self,
+        replacement_range: Option<std::ops::Range<usize>>,
+        text: &str,
+    ) {
+        self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx));
+    }
+
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<std::ops::Range<usize>>,
+        new_text: &str,
+        new_selected_range: Option<std::ops::Range<usize>>,
+    ) {
+        self.update(|view, cx| {
+            view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
+        });
+    }
+
+    fn unmark_text(&mut self) {
+        self.update(|view, cx| view.unmark_text(cx));
+    }
+
+    fn bounds_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<crate::Bounds<f32>> {
+        self.update(|view, cx| view.bounds_for_range(range_utf16, cx))
+            .flatten()
+    }
+}
+
+impl<V: InputHandler + 'static> WindowInputHandler<V> {
+    fn update<T>(&self, f: impl FnOnce(&mut V, &mut ViewContext<V>) -> T) -> Option<T> {
+        let cx = self.cx.upgrade()?;
+        let mut cx = cx.borrow_mut();
+        cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok())
+            .ok()?
+    }
+}
+
+pub trait InputHandler: Sized {
+    fn text_for_range(&self, range: Range<usize>, cx: &mut ViewContext<Self>) -> Option<String>;
+    fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+    fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+    fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
+    fn replace_text_in_range(
+        &mut self,
+        range: Option<Range<usize>>,
+        text: &str,
+        cx: &mut ViewContext<Self>,
+    );
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range: Option<Range<usize>>,
+        new_text: &str,
+        new_selected_range: Option<Range<usize>>,
+        cx: &mut ViewContext<Self>,
+    );
+    fn bounds_for_range(
+        &self,
+        range_utf16: std::ops::Range<usize>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<crate::Bounds<f32>>;
+}

crates/gpui2_macros/src/action.rs 🔗

@@ -0,0 +1,55 @@
+// Input:
+//
+// #[action]
+// struct Foo {
+//   bar: String,
+// }
+
+// Output:
+//
+// #[gpui::register_action]
+// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+// struct Foo {
+//   bar: String,
+// }
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as DeriveInput);
+    let name = &input.ident;
+    let attrs = input
+        .attrs
+        .into_iter()
+        .filter(|attr| !attr.path.is_ident("action"))
+        .collect::<Vec<_>>();
+
+    let attributes = quote! {
+        #[gpui::register_action]
+        #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+        #(#attrs)*
+    };
+    let visibility = input.vis;
+
+    let output = match input.data {
+        syn::Data::Struct(ref struct_data) => {
+            let fields = &struct_data.fields;
+            quote! {
+                #attributes
+                #visibility struct #name #fields
+            }
+        }
+        syn::Data::Enum(ref enum_data) => {
+            let variants = &enum_data.variants;
+            quote! {
+                #attributes
+                #visibility enum #name { #variants }
+            }
+        }
+        _ => panic!("Expected a struct or an enum."),
+    };
+
+    TokenStream::from(output)
+}

crates/gpui2_macros/src/gpui2_macros.rs 🔗

@@ -1,14 +1,26 @@
-use proc_macro::TokenStream;
-
+mod action;
 mod derive_component;
+mod register_action;
 mod style_helpers;
 mod test;
 
+use proc_macro::TokenStream;
+
 #[proc_macro]
 pub fn style_helpers(args: TokenStream) -> TokenStream {
     style_helpers::style_helpers(args)
 }
 
+#[proc_macro_attribute]
+pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
+    action::action(attr, item)
+}
+
+#[proc_macro_attribute]
+pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
+    register_action::register_action(attr, item)
+}
+
 #[proc_macro_derive(Component, attributes(component))]
 pub fn derive_component(input: TokenStream) -> TokenStream {
     derive_component::derive_component(input)

crates/gpui2_macros/src/register_action.rs 🔗

@@ -0,0 +1,33 @@
+// Input:
+//
+// struct FooBar {}
+
+// Output:
+//
+// struct FooBar {}
+//
+// #[allow(non_snake_case)]
+// #[gpui2::ctor]
+// fn register_foobar_builder() {
+//     gpui2::register_action_builder::<Foo>()
+// }
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as DeriveInput);
+    let type_name = &input.ident;
+    let ctor_fn_name = format_ident!("register_{}_builder", type_name.to_string().to_lowercase());
+
+    let expanded = quote! {
+        #input
+        #[allow(non_snake_case)]
+        #[gpui::ctor]
+        fn #ctor_fn_name() {
+            gpui::register_action::<#type_name>()
+        }
+    };
+
+    TokenStream::from(expanded)
+}

crates/menu2/Cargo.toml 🔗

@@ -10,3 +10,4 @@ doctest = false
 
 [dependencies]
 gpui = { package = "gpui2", path = "../gpui2" }
+serde = { workspace = true }

crates/menu2/src/menu2.rs 🔗

@@ -1,25 +1,12 @@
-// todo!(use actions! macro)
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct Cancel;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct Confirm;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SecondaryConfirm;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectPrev;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectNext;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectFirst;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectLast;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct ShowContextMenu;
+use gpui::actions;
+
+actions!(
+    Cancel,
+    Confirm,
+    SecondaryConfirm,
+    SelectPrev,
+    SelectNext,
+    SelectFirst,
+    SelectLast,
+    ShowContextMenu
+);

crates/picker2/Cargo.toml 🔗

@@ -0,0 +1,28 @@
+[package]
+name = "picker2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/picker2.rs"
+doctest = false
+
+[dependencies]
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+settings = { package = "settings2", path = "../settings2" }
+util = { path = "../util" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+
+parking_lot.workspace = true
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+serde_json.workspace = true
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+ctor.workspace = true
+env_logger.workspace = true

crates/picker2/src/picker2.rs 🔗

@@ -0,0 +1,163 @@
+use editor::Editor;
+use gpui::{
+    div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
+    StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
+    WindowContext,
+};
+use std::cmp;
+
+pub struct Picker<D: PickerDelegate> {
+    pub delegate: D,
+    scroll_handle: UniformListScrollHandle,
+    editor: View<Editor>,
+    pending_update_matches: Option<Task<Option<()>>>,
+}
+
+pub trait PickerDelegate: Sized + 'static {
+    type ListItem: Component<Picker<Self>>;
+
+    fn match_count(&self) -> usize;
+    fn selected_index(&self) -> usize;
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+
+    // fn placeholder_text(&self) -> Arc<str>;
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+
+    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
+
+    fn render_match(
+        &self,
+        ix: usize,
+        selected: bool,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Self::ListItem;
+}
+
+impl<D: PickerDelegate> Picker<D> {
+    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
+        let editor = cx.build_view(|cx| Editor::single_line(cx));
+        cx.subscribe(&editor, Self::on_input_editor_event).detach();
+        Self {
+            delegate,
+            scroll_handle: UniformListScrollHandle::new(),
+            pending_update_matches: None,
+            editor,
+        }
+    }
+
+    pub fn focus(&self, cx: &mut WindowContext) {
+        self.editor.update(cx, |editor, cx| editor.focus(cx));
+    }
+
+    fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            let index = self.delegate.selected_index();
+            let ix = cmp::min(index + 1, count - 1);
+            self.delegate.set_selected_index(ix, cx);
+            self.scroll_handle.scroll_to_item(ix);
+        }
+    }
+
+    fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            let index = self.delegate.selected_index();
+            let ix = index.saturating_sub(1);
+            self.delegate.set_selected_index(ix, cx);
+            self.scroll_handle.scroll_to_item(ix);
+        }
+    }
+
+    fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            self.delegate.set_selected_index(0, cx);
+            self.scroll_handle.scroll_to_item(0);
+        }
+    }
+
+    fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            self.delegate.set_selected_index(count - 1, cx);
+            self.scroll_handle.scroll_to_item(count - 1);
+        }
+    }
+
+    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+        self.delegate.dismissed(cx);
+    }
+
+    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+        self.delegate.confirm(false, cx);
+    }
+
+    fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+        self.delegate.confirm(true, cx);
+    }
+
+    fn on_input_editor_event(
+        &mut self,
+        _: View<Editor>,
+        event: &editor::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let editor::Event::BufferEdited = event {
+            let query = self.editor.read(cx).text(cx);
+            self.update_matches(query, cx);
+        }
+    }
+
+    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
+        let update = self.delegate.update_matches(query, cx);
+        self.matches_updated(cx);
+        self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
+            update.await;
+            this.update(&mut cx, |this, cx| {
+                this.matches_updated(cx);
+            })
+            .ok()
+        }));
+    }
+
+    fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
+        let index = self.delegate.selected_index();
+        self.scroll_handle.scroll_to_item(index);
+        self.pending_update_matches = None;
+        cx.notify();
+    }
+}
+
+impl<D: PickerDelegate> Render for Picker<D> {
+    type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        div()
+            .context("picker")
+            .id("picker-container")
+            .focusable()
+            .size_full()
+            .on_action(Self::select_next)
+            .on_action(Self::select_prev)
+            .on_action(Self::select_first)
+            .on_action(Self::select_last)
+            .on_action(Self::cancel)
+            .on_action(Self::confirm)
+            .on_action(Self::secondary_confirm)
+            .child(self.editor.clone())
+            .child(
+                uniform_list("candidates", self.delegate.match_count(), {
+                    move |this: &mut Self, visible_range, cx| {
+                        let selected_ix = this.delegate.selected_index();
+                        visible_range
+                            .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
+                            .collect()
+                    }
+                })
+                .track_scroll(self.scroll_handle.clone())
+                .size_full(),
+            )
+    }
+}

crates/settings2/src/keymap_file.rs 🔗

@@ -73,9 +73,9 @@ impl KeymapFile {
                                     "Expected first item in array to be a string."
                                 )));
                             };
-                            cx.build_action(&name, Some(data))
+                            gpui::build_action(&name, Some(data))
                         }
-                        Value::String(name) => cx.build_action(&name, None),
+                        Value::String(name) => gpui::build_action(&name, None),
                         Value::Null => Ok(no_action()),
                         _ => {
                             return Some(Err(anyhow!("Expected two-element array, got {action:?}")))

crates/storybook2/Cargo.toml 🔗

@@ -13,9 +13,12 @@ anyhow.workspace = true
 # TODO: Remove after diagnosing stack overflow.
 backtrace-on-stack-overflow = "0.3.0"
 clap = { version = "4.4", features = ["derive", "string"] }
+editor = { package = "editor2", path = "../editor2" }
 chrono = "0.4"
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 itertools = "0.11.0"
+language = { package = "language2", path = "../language2" }
 log.workspace = true
 rust-embed.workspace = true
 serde.workspace = true
@@ -25,8 +28,10 @@ smallvec.workspace = true
 strum = { version = "0.25.0", features = ["derive"] }
 theme = { path = "../theme" }
 theme2 = { path = "../theme2" }
+menu = { package = "menu2", path = "../menu2" }
 ui = { package = "ui2", path = "../ui2", features = ["stories"] }
 util = { path = "../util" }
+picker = { package = "picker2", path = "../picker2" }
 
 [dev-dependencies]
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/storybook2/src/stories.rs 🔗

@@ -1,6 +1,7 @@
 mod colors;
 mod focus;
 mod kitchen_sink;
+mod picker;
 mod scroll;
 mod text;
 mod z_index;
@@ -8,6 +9,7 @@ mod z_index;
 pub use colors::*;
 pub use focus::*;
 pub use kitchen_sink::*;
+pub use picker::*;
 pub use scroll::*;
 pub use text::*;
 pub use z_index::*;

crates/storybook2/src/stories/focus.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{
     actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
-    StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext,
+    StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
 };
 use theme2::ActiveTheme;
 
@@ -15,24 +15,22 @@ impl FocusStory {
             KeyBinding::new("cmd-a", ActionB, Some("child-1")),
             KeyBinding::new("cmd-c", ActionC, None),
         ]);
-        cx.register_action_type::<ActionA>();
-        cx.register_action_type::<ActionB>();
 
         cx.build_view(move |cx| Self {})
     }
 }
 
 impl Render for FocusStory {
-    type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
+    type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
-        let color_1 = theme.styles.git.created;
-        let color_2 = theme.styles.git.modified;
-        let color_3 = theme.styles.git.deleted;
-        let color_4 = theme.styles.git.conflict;
-        let color_5 = theme.styles.git.ignored;
-        let color_6 = theme.styles.git.renamed;
+        let color_1 = theme.status().created;
+        let color_2 = theme.status().modified;
+        let color_3 = theme.status().deleted;
+        let color_4 = theme.status().conflict;
+        let color_5 = theme.status().ignored;
+        let color_6 = theme.status().renamed;
         let child_1 = cx.focus_handle();
         let child_2 = cx.focus_handle();
 
@@ -40,20 +38,18 @@ impl Render for FocusStory {
             .id("parent")
             .focusable()
             .context("parent")
-            .on_action(|_, action: &ActionA, phase, cx| {
-                println!("Action A dispatched on parent during {:?}", phase);
+            .on_action(|_, action: &ActionA, cx| {
+                println!("Action A dispatched on parent during");
             })
-            .on_action(|_, action: &ActionB, phase, cx| {
-                println!("Action B dispatched on parent during {:?}", phase);
+            .on_action(|_, action: &ActionB, cx| {
+                println!("Action B dispatched on parent during");
             })
             .on_focus(|_, _, _| println!("Parent focused"))
             .on_blur(|_, _, _| println!("Parent blurred"))
             .on_focus_in(|_, _, _| println!("Parent focus_in"))
             .on_focus_out(|_, _, _| println!("Parent focus_out"))
-            .on_key_down(|_, event, phase, _| {
-                println!("Key down on parent {:?} {:?}", phase, event)
-            })
-            .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
+            .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
+            .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
             .size_full()
             .bg(color_1)
             .focus(|style| style.bg(color_2))
@@ -62,8 +58,8 @@ impl Render for FocusStory {
                 div()
                     .track_focus(&child_1)
                     .context("child-1")
-                    .on_action(|_, action: &ActionB, phase, cx| {
-                        println!("Action B dispatched on child 1 during {:?}", phase);
+                    .on_action(|_, action: &ActionB, cx| {
+                        println!("Action B dispatched on child 1 during");
                     })
                     .w_full()
                     .h_6()
@@ -74,20 +70,16 @@ impl Render for FocusStory {
                     .on_blur(|_, _, _| println!("Child 1 blurred"))
                     .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
                     .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
-                    .on_key_down(|_, event, phase, _| {
-                        println!("Key down on child 1 {:?} {:?}", phase, event)
-                    })
-                    .on_key_up(|_, event, phase, _| {
-                        println!("Key up on child 1 {:?} {:?}", phase, event)
-                    })
+                    .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
+                    .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
                     .child("Child 1"),
             )
             .child(
                 div()
                     .track_focus(&child_2)
                     .context("child-2")
-                    .on_action(|_, action: &ActionC, phase, cx| {
-                        println!("Action C dispatched on child 2 during {:?}", phase);
+                    .on_action(|_, action: &ActionC, cx| {
+                        println!("Action C dispatched on child 2 during");
                     })
                     .w_full()
                     .h_6()
@@ -96,12 +88,8 @@ impl Render for FocusStory {
                     .on_blur(|_, _, _| println!("Child 2 blurred"))
                     .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
                     .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
-                    .on_key_down(|_, event, phase, _| {
-                        println!("Key down on child 2 {:?} {:?}", phase, event)
-                    })
-                    .on_key_up(|_, event, phase, _| {
-                        println!("Key up on child 2 {:?} {:?}", phase, event)
-                    })
+                    .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
+                    .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
                     .child("Child 2"),
             )
     }

crates/storybook2/src/stories/kitchen_sink.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{story::Story, story_selector::ComponentStory};
-use gpui::{Div, Render, StatefulInteraction, View, VisualContext};
+use gpui::{Div, Render, StatefulInteractivity, View, VisualContext};
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 
@@ -12,7 +12,7 @@ impl KitchenSinkStory {
 }
 
 impl Render for KitchenSinkStory {
-    type Element = Div<Self, StatefulInteraction<Self>>;
+    type Element = Div<Self, StatefulInteractivity<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let component_stories = ComponentStory::iter()

crates/storybook2/src/stories/picker.rs 🔗

@@ -0,0 +1,214 @@
+use std::sync::Arc;
+
+use fuzzy::StringMatchCandidate;
+use gpui::{
+    div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task,
+    View, VisualContext, WindowContext,
+};
+use picker::{Picker, PickerDelegate};
+use theme2::ActiveTheme;
+
+pub struct PickerStory {
+    picker: View<Picker<Delegate>>,
+}
+
+struct Delegate {
+    candidates: Arc<[StringMatchCandidate]>,
+    matches: Vec<usize>,
+    selected_ix: usize,
+}
+
+impl Delegate {
+    fn new(strings: &[&str]) -> Self {
+        Self {
+            candidates: strings
+                .iter()
+                .copied()
+                .enumerate()
+                .map(|(id, string)| StringMatchCandidate {
+                    id,
+                    char_bag: string.into(),
+                    string: string.into(),
+                })
+                .collect(),
+            matches: vec![],
+            selected_ix: 0,
+        }
+    }
+}
+
+impl PickerDelegate for Delegate {
+    type ListItem = Div<Picker<Self>>;
+
+    fn match_count(&self) -> usize {
+        self.candidates.len()
+    }
+
+    fn render_match(
+        &self,
+        ix: usize,
+        selected: bool,
+        cx: &mut gpui::ViewContext<Picker<Self>>,
+    ) -> Self::ListItem {
+        let colors = cx.theme().colors();
+        let Some(candidate_ix) = self.matches.get(ix) else {
+            return div();
+        };
+        let candidate = self.candidates[*candidate_ix].string.clone();
+
+        div()
+            .text_color(colors.text)
+            .when(selected, |s| {
+                s.border_l_10().border_color(colors.terminal_ansi_yellow)
+            })
+            .hover(|style| {
+                style
+                    .bg(colors.element_active)
+                    .text_color(colors.text_accent)
+            })
+            .child(candidate)
+    }
+
+    fn selected_index(&self) -> usize {
+        self.selected_ix
+    }
+
+    fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
+        self.selected_ix = ix;
+        cx.notify();
+    }
+
+    fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
+        let candidate_ix = self.matches[self.selected_ix];
+        let candidate = self.candidates[candidate_ix].string.clone();
+
+        if secondary {
+            eprintln!("Secondary confirmed {}", candidate)
+        } else {
+            eprintln!("Confirmed {}", candidate)
+        }
+    }
+
+    fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
+        cx.quit();
+    }
+
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut gpui::ViewContext<Picker<Self>>,
+    ) -> Task<()> {
+        let candidates = self.candidates.clone();
+        self.matches = cx
+            .background_executor()
+            .block(fuzzy::match_strings(
+                &candidates,
+                &query,
+                true,
+                100,
+                &Default::default(),
+                cx.background_executor().clone(),
+            ))
+            .into_iter()
+            .map(|r| r.candidate_id)
+            .collect();
+        self.selected_ix = 0;
+        Task::ready(())
+    }
+}
+
+impl PickerStory {
+    pub fn new(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| {
+            cx.bind_keys([
+                KeyBinding::new("up", menu::SelectPrev, Some("picker")),
+                KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
+                KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
+                KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
+                KeyBinding::new("down", menu::SelectNext, Some("picker")),
+                KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
+                KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
+                KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
+                KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
+                KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
+                KeyBinding::new("enter", menu::Confirm, Some("picker")),
+                KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")),
+                KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
+                KeyBinding::new("escape", menu::Cancel, Some("picker")),
+                KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
+            ]);
+
+            PickerStory {
+                picker: cx.build_view(|cx| {
+                    let mut delegate = Delegate::new(&[
+                        "Baguette (France)",
+                        "Baklava (Turkey)",
+                        "Beef Wellington (UK)",
+                        "Biryani (India)",
+                        "Borscht (Ukraine)",
+                        "Bratwurst (Germany)",
+                        "Bulgogi (Korea)",
+                        "Burrito (USA)",
+                        "Ceviche (Peru)",
+                        "Chicken Tikka Masala (India)",
+                        "Churrasco (Brazil)",
+                        "Couscous (North Africa)",
+                        "Croissant (France)",
+                        "Dim Sum (China)",
+                        "Empanada (Argentina)",
+                        "Fajitas (Mexico)",
+                        "Falafel (Middle East)",
+                        "Feijoada (Brazil)",
+                        "Fish and Chips (UK)",
+                        "Fondue (Switzerland)",
+                        "Goulash (Hungary)",
+                        "Haggis (Scotland)",
+                        "Kebab (Middle East)",
+                        "Kimchi (Korea)",
+                        "Lasagna (Italy)",
+                        "Maple Syrup Pancakes (Canada)",
+                        "Moussaka (Greece)",
+                        "Pad Thai (Thailand)",
+                        "Paella (Spain)",
+                        "Pancakes (USA)",
+                        "Pasta Carbonara (Italy)",
+                        "Pavlova (Australia)",
+                        "Peking Duck (China)",
+                        "Pho (Vietnam)",
+                        "Pierogi (Poland)",
+                        "Pizza (Italy)",
+                        "Poutine (Canada)",
+                        "Pretzel (Germany)",
+                        "Ramen (Japan)",
+                        "Rendang (Indonesia)",
+                        "Sashimi (Japan)",
+                        "Satay (Indonesia)",
+                        "Shepherd's Pie (Ireland)",
+                        "Sushi (Japan)",
+                        "Tacos (Mexico)",
+                        "Tandoori Chicken (India)",
+                        "Tortilla (Spain)",
+                        "Tzatziki (Greece)",
+                        "Wiener Schnitzel (Austria)",
+                    ]);
+                    delegate.update_matches("".into(), cx).detach();
+
+                    let picker = Picker::new(delegate, cx);
+                    picker.focus(cx);
+                    picker
+                }),
+            }
+        })
+    }
+}
+
+impl Render for PickerStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+        div()
+            .bg(cx.theme().styles.colors.background)
+            .size_full()
+            .child(self.picker.clone())
+    }
+}

crates/storybook2/src/stories/scroll.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{
-    div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled,
+    div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled,
     View, VisualContext, WindowContext,
 };
 use theme2::ActiveTheme;
@@ -13,12 +13,12 @@ impl ScrollStory {
 }
 
 impl Render for ScrollStory {
-    type Element = Div<Self, StatefulInteraction<Self>>;
+    type Element = Div<Self, StatefulInteractivity<Self>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
-        let color_1 = theme.styles.git.created;
-        let color_2 = theme.styles.git.modified;
+        let color_1 = theme.status().created;
+        let color_2 = theme.status().modified;
 
         div()
             .id("parent")

crates/storybook2/src/story_selector.rs 🔗

@@ -38,6 +38,7 @@ pub enum ComponentStory {
     Palette,
     Panel,
     ProjectPanel,
+    Players,
     RecentProjects,
     Scroll,
     Tab,
@@ -51,6 +52,7 @@ pub enum ComponentStory {
     TrafficLights,
     Workspace,
     ZIndex,
+    Picker,
 }
 
 impl ComponentStory {
@@ -79,6 +81,7 @@ impl ComponentStory {
             Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
             Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
             Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
+            Self::Players => cx.build_view(|_| theme2::PlayerStory).into(),
             Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
             Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
             Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),
@@ -94,6 +97,7 @@ impl ComponentStory {
             Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
             Self::Workspace => ui::WorkspaceStory::view(cx).into(),
             Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
+            Self::Picker => PickerStory::new(cx).into(),
         }
     }
 }

crates/storybook2/src/storybook2.rs 🔗

@@ -72,6 +72,8 @@ fn main() {
         ThemeSettings::override_global(theme_settings, cx);
 
         ui::settings::init(cx);
+        language::init(cx);
+        editor::init(cx);
 
         let window = cx.open_window(
             WindowOptions {

crates/terminal2/src/mappings/mouse.rs 🔗

@@ -186,9 +186,9 @@ pub fn mouse_side(
 }
 
 pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
-    let col = GridCol((pos.x / cur_size.cell_width).as_usize());
+    let col = GridCol((cur_size.cell_width / pos.x) as usize);
     let col = min(col, cur_size.last_column());
-    let line = (pos.y / cur_size.line_height).as_isize() as i32;
+    let line = (cur_size.line_height / pos.y) as i32;
     let line = min(line, cur_size.bottommost_line().0);
     AlacPoint::new(GridLine(line - display_offset as i32), col)
 }

crates/terminal2/src/terminal2.rs 🔗

@@ -1121,8 +1121,7 @@ impl Terminal {
                     None => return,
                 };
 
-                let scroll_lines =
-                    (scroll_delta / self.last_content.size.line_height).as_isize() as i32;
+                let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
 
                 self.events
                     .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
@@ -1280,11 +1279,11 @@ impl Terminal {
             }
             /* Calculate the appropriate scroll lines */
             TouchPhase::Moved => {
-                let old_offset = (self.scroll_px / line_height).as_isize() as i32;
+                let old_offset = (self.scroll_px / line_height) as i32;
 
                 self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
 
-                let new_offset = (self.scroll_px / line_height).as_isize() as i32;
+                let new_offset = (self.scroll_px / line_height) as i32;
 
                 // Whenever we hit the edges, reset our stored scroll to 0
                 // so we can respond to changes in direction quickly
@@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>(
 }
 
 fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
-    let col = (pos.x / size.cell_width()).round().as_usize();
+    let col = (pos.x / size.cell_width()).round() as usize;
     let clamped_col = min(col, size.columns() - 1);
-    let row = (pos.y / size.line_height()).round().as_usize();
+    let row = (pos.y / size.line_height()).round() as usize;
     let clamped_row = min(row, size.screen_lines() - 1);
     clamped_row * size.columns() + clamped_col
 }

crates/theme2/Cargo.toml 🔗

@@ -5,6 +5,8 @@ edition = "2021"
 publish = false
 
 [features]
+default = ["stories"]
+stories = ["dep:itertools"]
 test-support = [
     "gpui/test-support",
     "fs/test-support",
@@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" }
 toml.workspace = true
 uuid.workspace = true
 util = { path = "../util" }
+itertools = { version = "0.11.0", optional = true }
 
 [dev-dependencies]
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/theme2/src/colors.rs 🔗

@@ -3,7 +3,7 @@ use std::sync::Arc;
 use gpui::Hsla;
 use refineable::Refineable;
 
-use crate::SyntaxTheme;
+use crate::{PlayerColors, SyntaxTheme};
 
 #[derive(Clone)]
 pub struct SystemColors {
@@ -13,33 +13,6 @@ pub struct SystemColors {
     pub mac_os_traffic_light_green: Hsla,
 }
 
-#[derive(Debug, Clone, Copy)]
-pub struct PlayerColor {
-    pub cursor: Hsla,
-    pub background: Hsla,
-    pub selection: Hsla,
-}
-
-#[derive(Clone)]
-pub struct PlayerColors(pub Vec<PlayerColor>);
-
-impl PlayerColors {
-    pub fn local(&self) -> PlayerColor {
-        // todo!("use a valid color");
-        *self.0.first().unwrap()
-    }
-
-    pub fn absent(&self) -> PlayerColor {
-        // todo!("use a valid color");
-        *self.0.last().unwrap()
-    }
-
-    pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
-        let len = self.0.len() - 1;
-        self.0[(participant_index as usize % len) + 1]
-    }
-}
-
 #[derive(Refineable, Clone, Debug)]
 #[refineable(debug)]
 pub struct StatusColors {
@@ -56,17 +29,6 @@ pub struct StatusColors {
     pub warning: Hsla,
 }
 
-#[derive(Refineable, Clone, Debug)]
-#[refineable(debug)]
-pub struct GitStatusColors {
-    pub conflict: Hsla,
-    pub created: Hsla,
-    pub deleted: Hsla,
-    pub ignored: Hsla,
-    pub modified: Hsla,
-    pub renamed: Hsla,
-}
-
 #[derive(Refineable, Clone, Debug)]
 #[refineable(debug, deserialize)]
 pub struct ThemeColors {
@@ -287,7 +249,6 @@ pub struct ThemeStyles {
     #[refineable]
     pub colors: ThemeColors,
     pub status: StatusColors,
-    pub git: GitStatusColors,
     pub player: PlayerColors,
     pub syntax: Arc<SyntaxTheme>,
 }

crates/theme2/src/default_colors.rs 🔗

@@ -2,12 +2,104 @@ use std::num::ParseIntError;
 
 use gpui::{hsla, Hsla, Rgba};
 
-use crate::{
-    colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors},
-    scale::{ColorScaleSet, ColorScales},
-    syntax::SyntaxTheme,
-    ColorScale,
-};
+use crate::colors::{StatusColors, SystemColors, ThemeColors};
+use crate::scale::{ColorScaleSet, ColorScales};
+use crate::syntax::SyntaxTheme;
+use crate::{ColorScale, PlayerColor, PlayerColors};
+
+impl Default for PlayerColors {
+    fn default() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().dark().step_9(),
+                background: blue().dark().step_5(),
+                selection: blue().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().dark().step_9(),
+                background: orange().dark().step_5(),
+                selection: orange().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().dark().step_9(),
+                background: pink().dark().step_5(),
+                selection: pink().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().dark().step_9(),
+                background: lime().dark().step_5(),
+                selection: lime().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().dark().step_9(),
+                background: purple().dark().step_5(),
+                selection: purple().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().dark().step_9(),
+                background: amber().dark().step_5(),
+                selection: amber().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().dark().step_9(),
+                background: jade().dark().step_5(),
+                selection: jade().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: red().dark().step_9(),
+                background: red().dark().step_5(),
+                selection: red().dark().step_3(),
+            },
+        ])
+    }
+}
+
+impl PlayerColors {
+    pub fn default_light() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().light().step_9(),
+                background: blue().light().step_4(),
+                selection: blue().light().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().light().step_9(),
+                background: orange().light().step_4(),
+                selection: orange().light().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().light().step_9(),
+                background: pink().light().step_4(),
+                selection: pink().light().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().light().step_9(),
+                background: lime().light().step_4(),
+                selection: lime().light().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().light().step_9(),
+                background: purple().light().step_4(),
+                selection: purple().light().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().light().step_9(),
+                background: amber().light().step_4(),
+                selection: amber().light().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().light().step_9(),
+                background: jade().light().step_4(),
+                selection: jade().light().step_3(),
+            },
+            PlayerColor {
+                cursor: red().light().step_9(),
+                background: red().light().step_4(),
+                selection: red().light().step_3(),
+            },
+        ])
+    }
+}
 
 fn neutral() -> ColorScaleSet {
     slate()
@@ -27,61 +119,21 @@ impl Default for SystemColors {
 impl Default for StatusColors {
     fn default() -> Self {
         Self {
-            conflict: red().dark().step_11(),
-            created: grass().dark().step_11(),
-            deleted: red().dark().step_11(),
-            error: red().dark().step_11(),
-            hidden: neutral().dark().step_11(),
-            ignored: neutral().dark().step_11(),
-            info: blue().dark().step_11(),
-            modified: yellow().dark().step_11(),
-            renamed: blue().dark().step_11(),
-            success: grass().dark().step_11(),
-            warning: yellow().dark().step_11(),
+            conflict: red().dark().step_9(),
+            created: grass().dark().step_9(),
+            deleted: red().dark().step_9(),
+            error: red().dark().step_9(),
+            hidden: neutral().dark().step_9(),
+            ignored: neutral().dark().step_9(),
+            info: blue().dark().step_9(),
+            modified: yellow().dark().step_9(),
+            renamed: blue().dark().step_9(),
+            success: grass().dark().step_9(),
+            warning: yellow().dark().step_9(),
         }
     }
 }
 
-impl Default for GitStatusColors {
-    fn default() -> Self {
-        Self {
-            conflict: orange().dark().step_11(),
-            created: grass().dark().step_11(),
-            deleted: red().dark().step_11(),
-            ignored: neutral().dark().step_11(),
-            modified: yellow().dark().step_11(),
-            renamed: blue().dark().step_11(),
-        }
-    }
-}
-
-impl Default for PlayerColors {
-    fn default() -> Self {
-        Self(vec![
-            PlayerColor {
-                cursor: hsla(0.0, 0.0, 0.0, 1.0),
-                background: hsla(0.0, 0.0, 0.0, 1.0),
-                selection: hsla(0.0, 0.0, 0.0, 1.0),
-            },
-            PlayerColor {
-                cursor: hsla(0.0, 0.0, 0.0, 1.0),
-                background: hsla(0.0, 0.0, 0.0, 1.0),
-                selection: hsla(0.0, 0.0, 0.0, 1.0),
-            },
-            PlayerColor {
-                cursor: hsla(0.0, 0.0, 0.0, 1.0),
-                background: hsla(0.0, 0.0, 0.0, 1.0),
-                selection: hsla(0.0, 0.0, 0.0, 1.0),
-            },
-            PlayerColor {
-                cursor: hsla(0.0, 0.0, 0.0, 1.0),
-                background: hsla(0.0, 0.0, 0.0, 1.0),
-                selection: hsla(0.0, 0.0, 0.0, 1.0),
-            },
-        ])
-    }
-}
-
 impl SyntaxTheme {
     pub fn default_light() -> Self {
         Self {

crates/theme2/src/default_theme.rs 🔗

@@ -1,8 +1,8 @@
 use std::sync::Arc;
 
 use crate::{
-    colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles},
-    default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily,
+    colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
+    default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
 };
 
 fn zed_pro_daylight() -> Theme {
@@ -14,8 +14,7 @@ fn zed_pro_daylight() -> Theme {
             system: SystemColors::default(),
             colors: ThemeColors::default_light(),
             status: StatusColors::default(),
-            git: GitStatusColors::default(),
-            player: PlayerColors::default(),
+            player: PlayerColors::default_light(),
             syntax: Arc::new(SyntaxTheme::default_light()),
         },
     }
@@ -30,7 +29,6 @@ pub(crate) fn zed_pro_moonlight() -> Theme {
             system: SystemColors::default(),
             colors: ThemeColors::default_dark(),
             status: StatusColors::default(),
-            git: GitStatusColors::default(),
             player: PlayerColors::default(),
             syntax: Arc::new(SyntaxTheme::default_dark()),
         },

crates/theme2/src/players.rs 🔗

@@ -0,0 +1,170 @@
+use gpui::Hsla;
+
+#[derive(Debug, Clone, Copy)]
+pub struct PlayerColor {
+    pub cursor: Hsla,
+    pub background: Hsla,
+    pub selection: Hsla,
+}
+
+/// A collection of colors that are used to color players in the editor.
+///
+/// The first color is always the local player's color, usually a blue.
+///
+/// The rest of the default colors crisscross back and forth on the
+/// color wheel so that the colors are as distinct as possible.
+#[derive(Clone)]
+pub struct PlayerColors(pub Vec<PlayerColor>);
+
+impl PlayerColors {
+    pub fn local(&self) -> PlayerColor {
+        // todo!("use a valid color");
+        *self.0.first().unwrap()
+    }
+
+    pub fn absent(&self) -> PlayerColor {
+        // todo!("use a valid color");
+        *self.0.last().unwrap()
+    }
+
+    pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
+        let len = self.0.len() - 1;
+        self.0[(participant_index as usize % len) + 1]
+    }
+}
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use super::*;
+    use crate::{ActiveTheme, Story};
+    use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
+
+    pub struct PlayerStory;
+
+    impl Render for PlayerStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            Story::container(cx).child(
+                div()
+                    .flex()
+                    .flex_col()
+                    .gap_4()
+                    .child(Story::title_for::<_, PlayerColors>(cx))
+                    .child(Story::label(cx, "Player Colors"))
+                    .child(
+                        div()
+                            .flex()
+                            .flex_col()
+                            .gap_1()
+                            .child(
+                                div().flex().gap_1().children(
+                                    cx.theme().players().0.clone().iter_mut().map(|player| {
+                                        div().w_8().h_8().rounded_md().bg(player.cursor)
+                                    }),
+                                ),
+                            )
+                            .child(div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|player| {
+                                    div().w_8().h_8().rounded_md().bg(player.background)
+                                }),
+                            ))
+                            .child(div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|player| {
+                                    div().w_8().h_8().rounded_md().bg(player.selection)
+                                }),
+                            )),
+                    )
+                    .child(Story::label(cx, "Avatar Rings"))
+                    .child(div().flex().gap_1().children(
+                        cx.theme().players().0.clone().iter_mut().map(|player| {
+                            div()
+                                .my_1()
+                                .rounded_full()
+                                .border_2()
+                                .border_color(player.cursor)
+                                .child(
+                                    img()
+                                        .rounded_full()
+                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                        .size_6()
+                                        .bg(gpui::red()),
+                                )
+                        }),
+                    ))
+                    .child(Story::label(cx, "Player Backgrounds"))
+                    .child(div().flex().gap_1().children(
+                        cx.theme().players().0.clone().iter_mut().map(|player| {
+                            div()
+                                .my_1()
+                                .rounded_xl()
+                                .flex()
+                                .items_center()
+                                .h_8()
+                                .py_0p5()
+                                .px_1p5()
+                                .bg(player.background)
+                                .child(
+                                div().relative().neg_mx_1().rounded_full().z_index(3)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                    img()
+                                        .rounded_full()
+                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                        .size(px(24.))
+                                        .bg(gpui::red()),
+                                ),
+                            ).child(
+                            div().relative().neg_mx_1().rounded_full().z_index(2)
+                                .border_2()
+                                .border_color(player.background)
+                                .size(px(28.))
+                                .child(
+                                img()
+                                    .rounded_full()
+                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                    .size(px(24.))
+                                    .bg(gpui::red()),
+                            ),
+                        ).child(
+                        div().relative().neg_mx_1().rounded_full().z_index(1)
+                            .border_2()
+                            .border_color(player.background)
+                            .size(px(28.))
+                            .child(
+                            img()
+                                .rounded_full()
+                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                .size(px(24.))
+                                .bg(gpui::red()),
+                        ),
+                    )
+                        }),
+                    ))
+                    .child(Story::label(cx, "Player Selections"))
+                    .child(div().flex().flex_col().gap_px().children(
+                        cx.theme().players().0.clone().iter_mut().map(|player| {
+                            div()
+                                .flex()
+                                .child(
+                                    div()
+                                        .flex()
+                                        .flex_none()
+                                        .rounded_sm()
+                                        .px_0p5()
+                                        .text_color(cx.theme().colors().text)
+                                        .bg(player.selection)
+                                        .child("The brown fox jumped over the lazy dog."),
+                                )
+                                .child(div().flex_1())
+                        }),
+                    )),
+            )
+        }
+    }
+}

crates/theme2/src/registry.rs 🔗

@@ -6,8 +6,8 @@ use gpui::SharedString;
 use refineable::Refineable;
 
 use crate::{
-    zed_pro_family, Appearance, GitStatusColors, PlayerColors, StatusColors, SyntaxTheme,
-    SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
+    zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
+    ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
 };
 
 pub struct ThemeRegistry {
@@ -50,7 +50,6 @@ impl ThemeRegistry {
                     system: SystemColors::default(),
                     colors: theme_colors,
                     status: StatusColors::default(),
-                    git: GitStatusColors::default(),
                     player: PlayerColors::default(),
                     syntax: match user_theme.appearance {
                         Appearance::Light => Arc::new(SyntaxTheme::default_light()),

crates/theme2/src/story.rs 🔗

@@ -0,0 +1,38 @@
+use gpui::{div, Component, Div, ParentElement, Styled, ViewContext};
+
+use crate::ActiveTheme;
+
+pub struct Story {}
+
+impl Story {
+    pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
+        div()
+            .size_full()
+            .flex()
+            .flex_col()
+            .pt_2()
+            .px_4()
+            .font("Zed Mono")
+            .bg(cx.theme().colors().background)
+    }
+
+    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
+        div()
+            .text_xl()
+            .text_color(cx.theme().colors().text)
+            .child(title.to_owned())
+    }
+
+    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
+        Self::title(cx, std::any::type_name::<T>())
+    }
+
+    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+        div()
+            .mt_4()
+            .mb_2()
+            .text_xs()
+            .text_color(cx.theme().colors().text)
+            .child(label.to_owned())
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -1,6 +1,7 @@
 mod colors;
 mod default_colors;
 mod default_theme;
+mod players;
 mod registry;
 mod scale;
 mod settings;
@@ -14,6 +15,7 @@ use ::settings::Settings;
 pub use colors::*;
 pub use default_colors::*;
 pub use default_theme::*;
+pub use players::*;
 pub use registry::*;
 pub use scale::*;
 pub use settings::*;
@@ -93,19 +95,13 @@ impl Theme {
         &self.styles.status
     }
 
-    /// Returns the [`GitStatusColors`] for the theme.
-    #[inline(always)]
-    pub fn git(&self) -> &GitStatusColors {
-        &self.styles.git
-    }
-
     /// Returns the color for the syntax node with the given name.
     #[inline(always)]
     pub fn syntax_color(&self, name: &str) -> Hsla {
         self.syntax().color(name)
     }
 
-    /// Returns the [`StatusColors`] for the theme.
+    /// Returns the [`DiagnosticStyle`] for the theme.
     #[inline(always)]
     pub fn diagnostic_style(&self) -> DiagnosticStyle {
         DiagnosticStyle {
@@ -126,3 +122,8 @@ pub struct DiagnosticStyle {
     pub hint: Hsla,
     pub ignored: Hsla,
 }
+
+#[cfg(feature = "stories")]
+mod story;
+#[cfg(feature = "stories")]
+pub use story::*;

crates/theme_importer/src/theme_printer.rs 🔗

@@ -2,8 +2,8 @@ use std::fmt::{self, Debug};
 
 use gpui::{Hsla, Rgba};
 use theme::{
-    Appearance, GitStatusColors, PlayerColor, PlayerColors, StatusColors, SyntaxTheme,
-    SystemColors, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
+    Appearance, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
+    ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
 };
 
 struct RawSyntaxPrinter<'a>(&'a str);
@@ -270,21 +270,6 @@ impl<'a> Debug for StatusColorsPrinter<'a> {
     }
 }
 
-pub struct GitStatusColorsPrinter<'a>(&'a GitStatusColors);
-
-impl<'a> Debug for GitStatusColorsPrinter<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("GitStatusColors")
-            .field("conflict", &HslaPrinter(self.0.conflict))
-            .field("created", &HslaPrinter(self.0.created))
-            .field("deleted", &HslaPrinter(self.0.deleted))
-            .field("ignored", &HslaPrinter(self.0.ignored))
-            .field("modified", &HslaPrinter(self.0.modified))
-            .field("renamed", &HslaPrinter(self.0.renamed))
-            .finish()
-    }
-}
-
 pub struct PlayerColorsPrinter<'a>(&'a PlayerColors);
 
 impl<'a> Debug for PlayerColorsPrinter<'a> {

crates/ui2/src/components/checkbox.rs 🔗

@@ -128,7 +128,7 @@ impl<V: 'static> Checkbox<V> {
             // click area for the checkbox.
             .size_5()
             // Because we've enlarged the click area, we need to create a
-            // `group` to pass down interaction events to the checkbox.
+            // `group` to pass down interactivity events to the checkbox.
             .group(group_id.clone())
             .child(
                 div()
@@ -148,7 +148,7 @@ impl<V: 'static> Checkbox<V> {
                     .bg(bg_color)
                     .border()
                     .border_color(border_color)
-                    // We only want the interaction states to fire when we
+                    // We only want the interactivity states to fire when we
                     // are in a checkbox that isn't disabled.
                     .when(!self.disabled, |this| {
                         // Here instead of `hover()` we use `group_hover()`

crates/ui2/src/prelude.rs 🔗

@@ -46,12 +46,12 @@ pub enum GitStatus {
 impl GitStatus {
     pub fn hsla(&self, cx: &WindowContext) -> Hsla {
         match self {
-            Self::None => cx.theme().styles.system.transparent,
-            Self::Created => cx.theme().styles.git.created,
-            Self::Modified => cx.theme().styles.git.modified,
-            Self::Deleted => cx.theme().styles.git.deleted,
-            Self::Conflict => cx.theme().styles.git.conflict,
-            Self::Renamed => cx.theme().styles.git.renamed,
+            Self::None => cx.theme().system().transparent,
+            Self::Created => cx.theme().status().created,
+            Self::Modified => cx.theme().status().modified,
+            Self::Deleted => cx.theme().status().deleted,
+            Self::Conflict => cx.theme().status().conflict,
+            Self::Renamed => cx.theme().status().renamed,
         }
     }
 }

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,13 +1,11 @@
-use std::{any::TypeId, sync::Arc};
-
+use crate::Workspace;
 use gpui::{
-    div, px, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render,
-    StatelessInteractive, Styled, View, ViewContext,
+    div, px, AnyView, AppContext, Component, Div, ParentElement, Render, StatelessInteractive,
+    Styled, View, ViewContext,
 };
+use std::{any::TypeId, sync::Arc};
 use ui::v_stack;
 
-use crate::Workspace;
-
 pub struct ModalRegistry {
     registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
 }
@@ -43,14 +41,7 @@ impl ModalRegistry {
                 let build_view = build_view.clone();
 
                 div.on_action(
-                    move |workspace: &mut Workspace,
-                          event: &A,
-                          phase: DispatchPhase,
-                          cx: &mut ViewContext<Workspace>| {
-                        if phase == DispatchPhase::Capture {
-                            return;
-                        }
-
+                    move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
                         let Some(new_modal) = (build_view)(workspace, cx) else {
                             return;
                         };

crates/workspace2/src/workspace2.rs 🔗

@@ -2694,7 +2694,7 @@ impl Workspace {
                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
         if is_edited != self.window_edited {
             self.window_edited = is_edited;
-            todo!()
+            // todo!()
             // cx.set_window_edited(self.window_edited)
         }
     }

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.112.0"
+version = "0.113.0"
 publish = false
 
 [lib]

crates/zed2/src/languages/json.rs 🔗

@@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
         &self,
         cx: &mut AppContext,
     ) -> BoxFuture<'static, serde_json::Value> {
-        let action_names = cx.all_action_names().collect::<Vec<_>>();
+        let action_names = gpui::all_action_names();
         let staff_mode = cx.is_staff();
         let language_names = &self.languages.language_names();
         let settings_schema = cx.global::<SettingsStore>().json_schema(

script/get-preview-channel-changes 🔗

@@ -2,7 +2,7 @@
 
 const { execFileSync } = require("child_process");
 const { GITHUB_ACCESS_TOKEN } = process.env;
-const PR_REGEX = /#\d+/ // Ex: matches on #4241
+const PR_REGEX = /#\d+/; // Ex: matches on #4241
 const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
 
 main();
@@ -12,7 +12,7 @@ async function main() {
   const [newTag, oldTag] = execFileSync(
     "git",
     ["tag", "--sort", "-committerdate"],
-    { encoding: "utf8" }
+    { encoding: "utf8" },
   )
     .split("\n")
     .filter((t) => t.startsWith("v") && t.endsWith("-pre"));
@@ -22,13 +22,22 @@ async function main() {
 
   let hasProtocolChanges = false;
   try {
-    execFileSync("git", ["diff", oldTag, newTag, "--exit-code", "--", "crates/rpc"]).status != 0;
+    execFileSync("git", [
+      "diff",
+      oldTag,
+      newTag,
+      "--exit-code",
+      "--",
+      "crates/rpc",
+    ]).status != 0;
   } catch (error) {
     hasProtocolChanges = true;
   }
 
   if (hasProtocolChanges) {
-    console.warn("\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n");
+    console.warn(
+      "\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n",
+    );
   } else {
     console.log("No RPC protocol changes\n");
   }
@@ -37,10 +46,14 @@ async function main() {
   const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag);
 
   // Get the PRs that were cherry-picked between main and the old tag.
-  const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag));
+  const existingPullRequestNumbers = new Set(
+    getPullRequestNumbers("main", oldTag),
+  );
 
   // Filter out those existing PRs from the set of new PRs.
-  const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number));
+  const newPullRequestNumbers = pullRequestNumbers.filter(
+    (number) => !existingPullRequestNumbers.has(number),
+  );
 
   // Fetch the pull requests from the GitHub API.
   console.log("Merged Pull requests:");
@@ -56,6 +69,16 @@ async function main() {
 
     // Print the pull request title and URL.
     const pullRequest = await response.json();
+    const releaseNotesHeader = /^\s*(?:Release )?Notes\s*:(.+)/ims;
+
+    let releaseNotes = pullRequest.body || "";
+    const captures = releaseNotesHeader.exec(releaseNotes);
+    const notes = captures ? captures[1] : "MISSING";
+    const skippableNoteRegex = /^\s*-?\s*n\/?a\s*/ims;
+
+    if (skippableNoteRegex.exec(notes) != null) {
+      continue;
+    }
     console.log("*", pullRequest.title);
     console.log("  PR URL:    ", webURL);
 
@@ -66,38 +89,30 @@ async function main() {
       console.log("  Issue URL:    ", fixedIssueURL);
     }
 
-    let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1];
-
-    if (releaseNotes) {
-      releaseNotes = releaseNotes.trim().split("\n")
-      console.log("  Release Notes:");
+    releaseNotes = notes.trim().split("\n");
+    console.log("  Release Notes:");
 
-      for (const line of releaseNotes) {
-        console.log(`    ${line}`);
-      }
+    for (const line of releaseNotes) {
+      console.log(`    ${line}`);
     }
 
-    console.log()
+    console.log();
   }
 }
 
 function getPullRequestNumbers(oldTag, newTag) {
   const pullRequestNumbers = execFileSync(
     "git",
-    [
-      "log",
-      `${oldTag}..${newTag}`,
-      "--oneline"
-    ],
-    { encoding: "utf8" }
+    ["log", `${oldTag}..${newTag}`, "--oneline"],
+    { encoding: "utf8" },
   )
     .split("\n")
-    .filter(line => line.length > 0)
-    .map(line => {
+    .filter((line) => line.length > 0)
+    .map((line) => {
       const match = line.match(/#(\d+)/);
       return match ? match[1] : null;
     })
-    .filter(line => line);
+    .filter((line) => line);
 
   return pullRequestNumbers;
 }