Merge remote-tracking branch 'origin/main' into register-actions

Nathan Sobo created

Change summary

Cargo.lock                                 |    1 
crates/editor2/src/editor.rs               | 1117 ++++++++++++-----------
crates/editor2/src/element.rs              |   26 
crates/go_to_line2/src/go_to_line.rs       |    8 
crates/gpui2/src/app.rs                    |    2 
crates/gpui2/src/element.rs                |    5 
crates/gpui2/src/gpui2.rs                  |    2 
crates/gpui2/src/interactive.rs            |    7 
crates/gpui2/src/style.rs                  |    4 
crates/gpui2/src/window.rs                 |   39 
crates/gpui2/src/window_input_handler.rs   |   89 +
crates/settings2/src/keymap_file.rs        |    6 
crates/storybook2/src/story_selector.rs    |    2 
crates/theme2/Cargo.toml                   |    3 
crates/theme2/src/colors.rs                |  175 +++
crates/theme2/src/default_colors.rs        |  165 ++-
crates/theme2/src/default_theme.rs         |    6 
crates/theme2/src/players.rs               |  170 +++
crates/theme2/src/story.rs                 |   38 
crates/theme2/src/theme2.rs                |    7 
crates/theme_importer/src/theme_printer.rs |    3 
crates/ui2/src/components/tab.rs           |    2 
crates/workspace2/src/modal_layer.rs       |   14 
crates/workspace2/src/workspace2.rs        |    8 
24 files changed, 1,217 insertions(+), 682 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9005,6 +9005,7 @@ dependencies = [
  "fs2",
  "gpui2",
  "indexmap 1.9.3",
+ "itertools 0.11.0",
  "parking_lot 0.11.2",
  "refineable",
  "schemars",

crates/editor2/src/editor.rs 🔗

@@ -36,10 +36,10 @@ pub use element::{
 use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, div, px, AnyElement, AppContext, BackgroundExecutor, Context, DispatchContext, Div,
-    Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, Model, Pixels, Render,
-    Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView,
-    WindowContext,
+    actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context,
+    DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, 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};
@@ -56,6 +56,7 @@ use language::{
 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,
@@ -67,7 +68,7 @@ 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;
@@ -82,7 +83,7 @@ use std::{
 };
 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,
 };
@@ -592,7 +593,6 @@ pub struct EditorStyle {
     pub background: Hsla,
     pub local_player: PlayerColor,
     pub text: TextStyle,
-    pub line_height_scalar: f32,
     pub scrollbar_width: Pixels,
     pub syntax: Arc<SyntaxTheme>,
     pub diagnostic_style: DiagnosticStyle,
@@ -1794,14 +1794,11 @@ impl InlayHintRefreshReason {
 }
 
 impl Editor {
-    //     pub fn single_line(
-    //         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Self {
-    //         let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
-    //         let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
-    //         Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx)
-    //     }
+    pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+        let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+        Self::new(EditorMode::SingleLine, buffer, None, cx)
+    }
 
     //     pub fn multi_line(
     //         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
@@ -2772,197 +2769,197 @@ impl Editor {
     //         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 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 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,
+                    },
+                );
+            }
 
-    //             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));
+            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 !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);
-    //                 }
-    //             }
+            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);
+                }
+            }
 
-    //             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 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);
+            }
+        });
+    }
 
     //     pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
     //         self.transact(cx, |this, cx| {
@@ -3262,22 +3259,22 @@ impl Editor {
         });
     }
 
-    //     fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-    //         if !EditorSettings>(cx).show_completions_on_input {
-    //             return;
-    //         }
+    fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
+        if !EditorSettings::get_global(cx).show_completions_on_input {
+            return;
+        }
 
-    //         let selection = self.selections.newest_anchor();
-    //         if self
-    //             .buffer
-    //             .read(cx)
-    //             .is_completion_trigger(selection.head(), text, cx)
-    //         {
-    //             self.show_completions(&ShowCompletions, cx);
-    //         } else {
-    //             self.hide_context_menu(cx);
-    //         }
-    //     }
+        let selection = self.selections.newest_anchor();
+        if self
+            .buffer
+            .read(cx)
+            .is_completion_trigger(selection.head(), text, cx)
+        {
+            self.show_completions(&ShowCompletions, cx);
+        } else {
+            self.hide_context_menu(cx);
+        }
+    }
 
     //     /// If any empty selections is touching the start of its innermost containing autoclose
     //     /// region, expand it to select the brackets.
@@ -3308,37 +3305,37 @@ impl Editor {
     //         self.change_selections(None, cx, |selections| selections.select(new_selections));
     //     }
 
-    //     /// Iterate the given selections, and for each one, find the smallest surrounding
-    //     /// autoclose region. This uses the ordering of the selections and the autoclose
-    //     /// regions to avoid repeated comparisons.
-    //     fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
-    //         &'a self,
-    //         selections: impl IntoIterator<Item = Selection<D>>,
-    //         buffer: &'a MultiBufferSnapshot,
-    //     ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
-    //         let mut i = 0;
-    //         let mut regions = self.autoclose_regions.as_slice();
-    //         selections.into_iter().map(move |selection| {
-    //             let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
-
-    //             let mut enclosing = None;
-    //             while let Some(pair_state) = regions.get(i) {
-    //                 if pair_state.range.end.to_offset(buffer) < range.start {
-    //                     regions = &regions[i + 1..];
-    //                     i = 0;
-    //                 } else if pair_state.range.start.to_offset(buffer) > range.end {
-    //                     break;
-    //                 } else {
-    //                     if pair_state.selection_id == selection.id {
-    //                         enclosing = Some(pair_state);
-    //                     }
-    //                     i += 1;
-    //                 }
-    //             }
+    /// Iterate the given selections, and for each one, find the smallest surrounding
+    /// autoclose region. This uses the ordering of the selections and the autoclose
+    /// regions to avoid repeated comparisons.
+    fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
+        &'a self,
+        selections: impl IntoIterator<Item = Selection<D>>,
+        buffer: &'a MultiBufferSnapshot,
+    ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
+        let mut i = 0;
+        let mut regions = self.autoclose_regions.as_slice();
+        selections.into_iter().map(move |selection| {
+            let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
+
+            let mut enclosing = None;
+            while let Some(pair_state) = regions.get(i) {
+                if pair_state.range.end.to_offset(buffer) < range.start {
+                    regions = &regions[i + 1..];
+                    i = 0;
+                } else if pair_state.range.start.to_offset(buffer) > range.end {
+                    break;
+                } else {
+                    if pair_state.selection_id == selection.id {
+                        enclosing = Some(pair_state);
+                    }
+                    i += 1;
+                }
+            }
 
-    //             (selection.clone(), enclosing)
-    //         })
-    //     }
+            (selection.clone(), enclosing)
+        })
+    }
 
     /// Remove any autoclose regions that no longer contain their selection.
     fn invalidate_autoclose_regions(
@@ -3540,51 +3537,51 @@ impl Editor {
         cx.notify();
     }
 
-    //     fn trigger_on_type_formatting(
-    //         &self,
-    //         input: String,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         if input.len() != 1 {
-    //             return None;
-    //         }
+    fn trigger_on_type_formatting(
+        &self,
+        input: String,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Task<Result<()>>> {
+        if input.len() != 1 {
+            return None;
+        }
 
-    //         let project = self.project.as_ref()?;
-    //         let position = self.selections.newest_anchor().head();
-    //         let (buffer, buffer_position) = self
-    //             .buffer
-    //             .read(cx)
-    //             .text_anchor_for_position(position.clone(), cx)?;
-
-    //         // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
-    //         // hence we do LSP request & edit on host side only — add formats to host's history.
-    //         let push_to_lsp_host_history = true;
-    //         // If this is not the host, append its history with new edits.
-    //         let push_to_client_history = project.read(cx).is_remote();
-
-    //         let on_type_formatting = project.update(cx, |project, cx| {
-    //             project.on_type_format(
-    //                 buffer.clone(),
-    //                 buffer_position,
-    //                 input,
-    //                 push_to_lsp_host_history,
-    //                 cx,
-    //             )
-    //         });
-    //         Some(cx.spawn(|editor, mut cx| async move {
-    //             if let Some(transaction) = on_type_formatting.await? {
-    //                 if push_to_client_history {
-    //                     buffer.update(&mut cx, |buffer, _| {
-    //                         buffer.push_transaction(transaction, Instant::now());
-    //                     });
-    //                 }
-    //                 editor.update(&mut cx, |editor, cx| {
-    //                     editor.refresh_document_highlights(cx);
-    //                 })?;
-    //             }
-    //             Ok(())
-    //         }))
-    //     }
+        let project = self.project.as_ref()?;
+        let position = self.selections.newest_anchor().head();
+        let (buffer, buffer_position) = self
+            .buffer
+            .read(cx)
+            .text_anchor_for_position(position.clone(), cx)?;
+
+        // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
+        // hence we do LSP request & edit on host side only — add formats to host's history.
+        let push_to_lsp_host_history = true;
+        // If this is not the host, append its history with new edits.
+        let push_to_client_history = project.read(cx).is_remote();
+
+        let on_type_formatting = project.update(cx, |project, cx| {
+            project.on_type_format(
+                buffer.clone(),
+                buffer_position,
+                input,
+                push_to_lsp_host_history,
+                cx,
+            )
+        });
+        Some(cx.spawn(|editor, mut cx| async move {
+            if let Some(transaction) = on_type_formatting.await? {
+                if push_to_client_history {
+                    buffer.update(&mut cx, |buffer, _| {
+                        buffer.push_transaction(transaction, Instant::now());
+                    });
+                }
+                editor.update(&mut cx, |editor, cx| {
+                    editor.refresh_document_highlights(cx);
+                })?;
+            }
+            Ok(())
+        }))
+    }
 
     fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
         if self.pending_rename.is_some() {
@@ -8137,15 +8134,14 @@ impl Editor {
     }
 
     fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.end_selection(cx);
-        // if let Some(tx_id) = self
-        //     .buffer
-        //     .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
-        // {
-        //     self.selection_history
-        //         .insert_transaction(tx_id, self.selections.disjoint_anchors());
-        // }
+        self.end_selection(cx);
+        if let Some(tx_id) = self
+            .buffer
+            .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
+        {
+            self.selection_history
+                .insert_transaction(tx_id, self.selections.disjoint_anchors());
+        }
     }
 
     fn end_transaction_at(
@@ -8153,22 +8149,21 @@ impl Editor {
         now: Instant,
         cx: &mut ViewContext<Self>,
     ) -> Option<TransactionId> {
-        todo!()
-        // if let Some(tx_id) = self
-        //     .buffer
-        //     .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
-        // {
-        //     if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
-        //         *end_selections = Some(self.selections.disjoint_anchors());
-        //     } else {
-        //         error!("unexpectedly ended a transaction that wasn't started by this editor");
-        //     }
-
-        //     cx.emit(Event::Edited);
-        //     Some(tx_id)
-        // } else {
-        //     None
-        // }
+        if let Some(tx_id) = self
+            .buffer
+            .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
+        {
+            if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
+                *end_selections = Some(self.selections.disjoint_anchors());
+            } else {
+                log::error!("unexpectedly ended a transaction that wasn't started by this editor");
+            }
+
+            cx.emit(Event::Edited);
+            Some(tx_id)
+        } else {
+            None
+        }
     }
 
     //     pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
@@ -8366,9 +8361,9 @@ impl Editor {
     //             .max_point()
     //     }
 
-    //     pub fn text(&self, cx: &AppContext) -> String {
-    //         self.buffer.read(cx).read(cx).text()
-    //     }
+    pub fn text(&self, cx: &AppContext) -> String {
+        self.buffer.read(cx).read(cx).text()
+    }
 
     //     pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
     //         self.transact(cx, |this, cx| {
@@ -8691,17 +8686,17 @@ impl Editor {
     //         results
     //     }
 
-    //     pub fn highlight_text<T: 'static>(
-    //         &mut self,
-    //         ranges: Vec<Range<Anchor>>,
-    //         style: HighlightStyle,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.display_map.update(cx, |map, _| {
-    //             map.highlight_text(TypeId::of::<T>(), ranges, style)
-    //         });
-    //         cx.notify();
-    //     }
+    pub fn highlight_text<T: 'static>(
+        &mut self,
+        ranges: Vec<Range<Anchor>>,
+        style: HighlightStyle,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.display_map.update(cx, |map, _| {
+            map.highlight_text(TypeId::of::<T>(), ranges, style)
+        });
+        cx.notify();
+    }
 
     //     pub fn highlight_inlays<T: 'static>(
     //         &mut self,
@@ -8715,12 +8710,12 @@ impl Editor {
     //         cx.notify();
     //     }
 
-    //     pub fn text_highlights<'a, T: 'static>(
-    //         &'a self,
-    //         cx: &'a AppContext,
-    //     ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
-    //         self.display_map.read(cx).text_highlights(TypeId::of::<T>())
-    //     }
+    pub fn text_highlights<'a, T: 'static>(
+        &'a self,
+        cx: &'a AppContext,
+    ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
+        self.display_map.read(cx).text_highlights(TypeId::of::<T>())
+    }
 
     pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
         let cleared = self
@@ -8937,43 +8932,43 @@ impl Editor {
     //         .detach_and_log_err(cx);
     //     }
 
-    //     fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
-    //         let snapshot = self.buffer.read(cx).read(cx);
-    //         let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
-    //         Some(
-    //             ranges
-    //                 .iter()
-    //                 .map(move |range| {
-    //                     range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
-    //                 })
-    //                 .collect(),
-    //         )
-    //     }
+    fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
+        let snapshot = self.buffer.read(cx).read(cx);
+        let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
+        Some(
+            ranges
+                .iter()
+                .map(move |range| {
+                    range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
+                })
+                .collect(),
+        )
+    }
 
-    //     fn selection_replacement_ranges(
-    //         &self,
-    //         range: Range<OffsetUtf16>,
-    //         cx: &AppContext,
-    //     ) -> Vec<Range<OffsetUtf16>> {
-    //         let selections = self.selections.all::<OffsetUtf16>(cx);
-    //         let newest_selection = selections
-    //             .iter()
-    //             .max_by_key(|selection| selection.id)
-    //             .unwrap();
-    //         let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
-    //         let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
-    //         let snapshot = self.buffer.read(cx).read(cx);
-    //         selections
-    //             .into_iter()
-    //             .map(|mut selection| {
-    //                 selection.start.0 =
-    //                     (selection.start.0 as isize).saturating_add(start_delta) as usize;
-    //                 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
-    //                 snapshot.clip_offset_utf16(selection.start, Bias::Left)
-    //                     ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
-    //             })
-    //             .collect()
-    //     }
+    fn selection_replacement_ranges(
+        &self,
+        range: Range<OffsetUtf16>,
+        cx: &AppContext,
+    ) -> Vec<Range<OffsetUtf16>> {
+        let selections = self.selections.all::<OffsetUtf16>(cx);
+        let newest_selection = selections
+            .iter()
+            .max_by_key(|selection| selection.id)
+            .unwrap();
+        let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
+        let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
+        let snapshot = self.buffer.read(cx).read(cx);
+        selections
+            .into_iter()
+            .map(|mut selection| {
+                selection.start.0 =
+                    (selection.start.0 as isize).saturating_add(start_delta) as usize;
+                selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
+                snapshot.clip_offset_utf16(selection.start, Bias::Left)
+                    ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
+            })
+            .collect()
+    }
 
     fn report_copilot_event(
         &self,
@@ -9188,6 +9183,10 @@ impl Editor {
     //         });
     //         supports
     //     }
+
+    fn focus(&self, cx: &mut WindowContext) {
+        cx.focus(&self.focus_handle)
+    }
 }
 
 pub trait CollaborationHub {
@@ -9371,14 +9370,13 @@ impl Render for Editor {
             font_size: settings.buffer_font_size.into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,
-            line_height: Default::default(),
+            line_height: relative(settings.buffer_line_height.value()),
             underline: None,
         };
         EditorElement::new(EditorStyle {
             background: cx.theme().colors().editor_background,
             local_player: cx.theme().players().local(),
             text: text_style,
-            line_height_scalar: settings.buffer_line_height.value(),
             scrollbar_width: px(12.),
             syntax: cx.theme().syntax().clone(),
             diagnostic_style: cx.theme().diagnostic_style(),
@@ -9485,214 +9483,229 @@ impl Render for Editor {
 
 //         false
 //     }
-//
-//     fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
-//         Some(
-//             self.buffer
-//                 .read(cx)
-//                 .read(cx)
-//                 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
-//                 .collect(),
-//         )
-//     }
 
-//     fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
-//         // Prevent the IME menu from appearing when holding down an alphabetic key
-//         // while input is disabled.
-//         if !self.input_enabled {
-//             return None;
-//         }
+impl InputHandler for Editor {
+    fn text_for_range(
+        &self,
+        range_utf16: Range<usize>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<String> {
+        Some(
+            self.buffer
+                .read(cx)
+                .read(cx)
+                .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
+                .collect(),
+        )
+    }
+
+    fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
+        // Prevent the IME menu from appearing when holding down an alphabetic key
+        // while input is disabled.
+        if !self.input_enabled {
+            return None;
+        }
 
-//         let range = self.selections.newest::<OffsetUtf16>(cx).range();
-//         Some(range.start.0..range.end.0)
-//     }
+        let range = self.selections.newest::<OffsetUtf16>(cx).range();
+        Some(range.start.0..range.end.0)
+    }
 
-//     fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
-//         let snapshot = self.buffer.read(cx).read(cx);
-//         let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
-//         Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
-//     }
+    fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
+        let snapshot = self.buffer.read(cx).read(cx);
+        let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
+        Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
+    }
 
-//     fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
-//         self.clear_highlights::<InputComposition>(cx);
-//         self.ime_transaction.take();
-//     }
+    fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
+        self.clear_highlights::<InputComposition>(cx);
+        self.ime_transaction.take();
+    }
 
-//     fn replace_text_in_range(
-//         &mut self,
-//         range_utf16: Option<Range<usize>>,
-//         text: &str,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if !self.input_enabled {
-//             cx.emit(Event::InputIgnored { text: text.into() });
-//             return;
-//         }
+    fn replace_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        text: &str,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if !self.input_enabled {
+            cx.emit(Event::InputIgnored { text: text.into() });
+            return;
+        }
 
-//         self.transact(cx, |this, cx| {
-//             let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
-//                 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
-//                 Some(this.selection_replacement_ranges(range_utf16, cx))
-//             } else {
-//                 this.marked_text_ranges(cx)
-//             };
+        self.transact(cx, |this, cx| {
+            let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
+                let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
+                Some(this.selection_replacement_ranges(range_utf16, cx))
+            } else {
+                this.marked_text_ranges(cx)
+            };
 
-//             let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
-//                 let newest_selection_id = this.selections.newest_anchor().id;
-//                 this.selections
-//                     .all::<OffsetUtf16>(cx)
-//                     .iter()
-//                     .zip(ranges_to_replace.iter())
-//                     .find_map(|(selection, range)| {
-//                         if selection.id == newest_selection_id {
-//                             Some(
-//                                 (range.start.0 as isize - selection.head().0 as isize)
-//                                     ..(range.end.0 as isize - selection.head().0 as isize),
-//                             )
-//                         } else {
-//                             None
-//                         }
-//                     })
-//             });
+            let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
+                let newest_selection_id = this.selections.newest_anchor().id;
+                this.selections
+                    .all::<OffsetUtf16>(cx)
+                    .iter()
+                    .zip(ranges_to_replace.iter())
+                    .find_map(|(selection, range)| {
+                        if selection.id == newest_selection_id {
+                            Some(
+                                (range.start.0 as isize - selection.head().0 as isize)
+                                    ..(range.end.0 as isize - selection.head().0 as isize),
+                            )
+                        } else {
+                            None
+                        }
+                    })
+            });
 
-//             cx.emit(Event::InputHandled {
-//                 utf16_range_to_replace: range_to_replace,
-//                 text: text.into(),
-//             });
+            cx.emit(Event::InputHandled {
+                utf16_range_to_replace: range_to_replace,
+                text: text.into(),
+            });
 
-//             if let Some(new_selected_ranges) = new_selected_ranges {
-//                 this.change_selections(None, cx, |selections| {
-//                     selections.select_ranges(new_selected_ranges)
-//                 });
-//             }
+            if let Some(new_selected_ranges) = new_selected_ranges {
+                this.change_selections(None, cx, |selections| {
+                    selections.select_ranges(new_selected_ranges)
+                });
+            }
 
-//             this.handle_input(text, cx);
-//         });
+            this.handle_input(text, cx);
+        });
 
-//         if let Some(transaction) = self.ime_transaction {
-//             self.buffer.update(cx, |buffer, cx| {
-//                 buffer.group_until_transaction(transaction, cx);
-//             });
-//         }
+        if let Some(transaction) = self.ime_transaction {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.group_until_transaction(transaction, cx);
+            });
+        }
 
-//         self.unmark_text(cx);
-//     }
+        self.unmark_text(cx);
+    }
 
-//     fn replace_and_mark_text_in_range(
-//         &mut self,
-//         range_utf16: Option<Range<usize>>,
-//         text: &str,
-//         new_selected_range_utf16: Option<Range<usize>>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if !self.input_enabled {
-//             cx.emit(Event::InputIgnored { text: text.into() });
-//             return;
-//         }
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        text: &str,
+        new_selected_range_utf16: Option<Range<usize>>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if !self.input_enabled {
+            cx.emit(Event::InputIgnored { text: text.into() });
+            return;
+        }
 
-//         let transaction = self.transact(cx, |this, cx| {
-//             let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
-//                 let snapshot = this.buffer.read(cx).read(cx);
-//                 if let Some(relative_range_utf16) = range_utf16.as_ref() {
-//                     for marked_range in &mut marked_ranges {
-//                         marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
-//                         marked_range.start.0 += relative_range_utf16.start;
-//                         marked_range.start =
-//                             snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
-//                         marked_range.end =
-//                             snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
-//                     }
-//                 }
-//                 Some(marked_ranges)
-//             } else if let Some(range_utf16) = range_utf16 {
-//                 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
-//                 Some(this.selection_replacement_ranges(range_utf16, cx))
-//             } else {
-//                 None
-//             };
+        let transaction = self.transact(cx, |this, cx| {
+            let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
+                let snapshot = this.buffer.read(cx).read(cx);
+                if let Some(relative_range_utf16) = range_utf16.as_ref() {
+                    for marked_range in &mut marked_ranges {
+                        marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
+                        marked_range.start.0 += relative_range_utf16.start;
+                        marked_range.start =
+                            snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
+                        marked_range.end =
+                            snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
+                    }
+                }
+                Some(marked_ranges)
+            } else if let Some(range_utf16) = range_utf16 {
+                let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
+                Some(this.selection_replacement_ranges(range_utf16, cx))
+            } else {
+                None
+            };
 
-//             let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
-//                 let newest_selection_id = this.selections.newest_anchor().id;
-//                 this.selections
-//                     .all::<OffsetUtf16>(cx)
-//                     .iter()
-//                     .zip(ranges_to_replace.iter())
-//                     .find_map(|(selection, range)| {
-//                         if selection.id == newest_selection_id {
-//                             Some(
-//                                 (range.start.0 as isize - selection.head().0 as isize)
-//                                     ..(range.end.0 as isize - selection.head().0 as isize),
-//                             )
-//                         } else {
-//                             None
-//                         }
-//                     })
-//             });
+            let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
+                let newest_selection_id = this.selections.newest_anchor().id;
+                this.selections
+                    .all::<OffsetUtf16>(cx)
+                    .iter()
+                    .zip(ranges_to_replace.iter())
+                    .find_map(|(selection, range)| {
+                        if selection.id == newest_selection_id {
+                            Some(
+                                (range.start.0 as isize - selection.head().0 as isize)
+                                    ..(range.end.0 as isize - selection.head().0 as isize),
+                            )
+                        } else {
+                            None
+                        }
+                    })
+            });
 
-//             cx.emit(Event::InputHandled {
-//                 utf16_range_to_replace: range_to_replace,
-//                 text: text.into(),
-//             });
+            cx.emit(Event::InputHandled {
+                utf16_range_to_replace: range_to_replace,
+                text: text.into(),
+            });
 
-//             if let Some(ranges) = ranges_to_replace {
-//                 this.change_selections(None, cx, |s| s.select_ranges(ranges));
-//             }
+            if let Some(ranges) = ranges_to_replace {
+                this.change_selections(None, cx, |s| s.select_ranges(ranges));
+            }
 
-//             let marked_ranges = {
-//                 let snapshot = this.buffer.read(cx).read(cx);
-//                 this.selections
-//                     .disjoint_anchors()
-//                     .iter()
-//                     .map(|selection| {
-//                         selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
-//                     })
-//                     .collect::<Vec<_>>()
-//             };
+            let marked_ranges = {
+                let snapshot = this.buffer.read(cx).read(cx);
+                this.selections
+                    .disjoint_anchors()
+                    .iter()
+                    .map(|selection| {
+                        selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
+                    })
+                    .collect::<Vec<_>>()
+            };
 
-//             if text.is_empty() {
-//                 this.unmark_text(cx);
-//             } else {
-//                 this.highlight_text::<InputComposition>(
-//                     marked_ranges.clone(),
-//                     this.style(cx).composition_mark,
-//                     cx,
-//                 );
-//             }
+            if text.is_empty() {
+                this.unmark_text(cx);
+            } else {
+                this.highlight_text::<InputComposition>(
+                    marked_ranges.clone(),
+                    HighlightStyle::default(), // todo!() this.style(cx).composition_mark,
+                    cx,
+                );
+            }
 
-//             this.handle_input(text, cx);
-
-//             if let Some(new_selected_range) = new_selected_range_utf16 {
-//                 let snapshot = this.buffer.read(cx).read(cx);
-//                 let new_selected_ranges = marked_ranges
-//                     .into_iter()
-//                     .map(|marked_range| {
-//                         let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
-//                         let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
-//                         let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
-//                         snapshot.clip_offset_utf16(new_start, Bias::Left)
-//                             ..snapshot.clip_offset_utf16(new_end, Bias::Right)
-//                     })
-//                     .collect::<Vec<_>>();
-
-//                 drop(snapshot);
-//                 this.change_selections(None, cx, |selections| {
-//                     selections.select_ranges(new_selected_ranges)
-//                 });
-//             }
-//         });
+            this.handle_input(text, cx);
 
-//         self.ime_transaction = self.ime_transaction.or(transaction);
-//         if let Some(transaction) = self.ime_transaction {
-//             self.buffer.update(cx, |buffer, cx| {
-//                 buffer.group_until_transaction(transaction, cx);
-//             });
-//         }
+            if let Some(new_selected_range) = new_selected_range_utf16 {
+                let snapshot = this.buffer.read(cx).read(cx);
+                let new_selected_ranges = marked_ranges
+                    .into_iter()
+                    .map(|marked_range| {
+                        let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
+                        let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
+                        let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
+                        snapshot.clip_offset_utf16(new_start, Bias::Left)
+                            ..snapshot.clip_offset_utf16(new_end, Bias::Right)
+                    })
+                    .collect::<Vec<_>>();
 
-//         if self.text_highlights::<InputComposition>(cx).is_none() {
-//             self.ime_transaction.take();
-//         }
-//     }
-// }
+                drop(snapshot);
+                this.change_selections(None, cx, |selections| {
+                    selections.select_ranges(new_selected_ranges)
+                });
+            }
+        });
+
+        self.ime_transaction = self.ime_transaction.or(transaction);
+        if let Some(transaction) = self.ime_transaction {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.group_until_transaction(transaction, cx);
+            });
+        }
+
+        if self.text_highlights::<InputComposition>(cx).is_none() {
+            self.ime_transaction.take();
+        }
+    }
+
+    fn bounds_for_range(
+        &self,
+        range_utf16: Range<usize>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<gpui::Bounds<f32>> {
+        // todo!()
+        // See how we did it before: `rect_for_range`
+        None
+    }
+}
 
 // fn build_style(
 //     settings: &ThemeSettings,

crates/editor2/src/element.rs 🔗

@@ -8,10 +8,11 @@ use crate::{
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
-    black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, 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,
+    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,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -1594,7 +1595,7 @@ impl EditorElement {
         &mut self,
         editor: &mut Editor,
         cx: &mut ViewContext<'_, Editor>,
-        bounds: Bounds<Pixels>,
+        mut bounds: Bounds<Pixels>,
     ) -> LayoutState {
         // let mut size = constraint.max;
         // if size.x.is_infinite() {
@@ -1605,7 +1606,7 @@ impl EditorElement {
         let style = self.style.clone();
         let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
         let font_size = style.text.font_size.to_pixels(cx.rem_size());
-        let line_height = (font_size * style.line_height_scalar).round();
+        let line_height = style.text.line_height_in_pixels(cx.rem_size());
         let em_width = cx
             .text_system()
             .typographic_bounds(font_id, font_size, 'm')
@@ -1672,8 +1673,7 @@ impl EditorElement {
             //             .min(line_height * max_lines as f32),
             //     )
         } else if let EditorMode::SingleLine = editor_mode {
-            todo!()
-            //     size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
+            bounds.size.height = line_height.min(bounds.size.height);
         }
         // todo!()
         // else if size.y.is_infinite() {
@@ -2593,7 +2593,11 @@ impl Element<Editor> for EditorElement {
         let rem_size = cx.rem_size();
         let mut style = Style::default();
         style.size.width = relative(1.).into();
-        style.size.height = relative(1.).into();
+        style.size.height = match editor.mode {
+            EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(),
+            EditorMode::AutoHeight { .. } => todo!(),
+            EditorMode::Full => relative(1.).into(),
+        };
         cx.request_layout(&style, None)
     }
 
@@ -2619,6 +2623,10 @@ impl Element<Editor> for EditorElement {
             }
         });
 
+        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,

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,12 +1,10 @@
-use gpui::{div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext};
-use serde::Deserialize;
+use gpui::{actions, div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext};
 use workspace::ModalRegistry;
 
-// actions!(go_to_line, [Toggle]);
-#[derive(Clone, Default, PartialEq, Deserialize)]
-struct Toggle;
+actions!(Toggle);
 
 pub fn init(cx: &mut AppContext) {
+    cx.register_action_type::<Toggle>();
     cx.global_mut::<ModalRegistry>()
         .register_modal(Toggle, |_, cx| {
             // if let Some(editor) = workspace

crates/gpui2/src/app.rs 🔗

@@ -153,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>,

crates/gpui2/src/element.rs 🔗

@@ -134,7 +134,10 @@ where
                         .layout(state, frame_state.as_mut().unwrap(), cx);
                 }
             }
-            _ => panic!("must call initialize before layout"),
+            ElementRenderPhase::Start => panic!("must call initialize before layout"),
+            ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => {
+                panic!("element rendered twice")
+            }
         };
 
         self.phase = ElementRenderPhase::LayoutRequested {

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.
@@ -65,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 🔗

@@ -397,9 +397,10 @@ pub trait ElementInteraction<V: 'static>: 'static {
                         None
                     }),
                 ));
-                let result = stateful.stateless.initialize(cx, f);
-                stateful.key_listeners.pop();
-                result
+
+                cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
+                    cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
+                })
             })
         } else {
             let stateless = self.as_stateless_mut();

crates/gpui2/src/style.rs 🔗

@@ -189,6 +189,10 @@ impl TextStyle {
         }
     }
 
+    pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels {
+        self.line_height.to_pixels(self.font_size, rem_size)
+    }
+
     pub fn to_run(&self, len: usize) -> TextRun {
         TextRun {
             len,

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;
@@ -191,6 +192,7 @@ pub struct Window {
     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>,
@@ -253,7 +255,7 @@ impl Window {
                 handle
                     .update(&mut cx, |_, cx| cx.dispatch_event(event))
                     .log_err()
-                    .unwrap_or(true)
+                    .unwrap_or(false)
             })
         });
 
@@ -285,6 +287,7 @@ impl Window {
             default_prevented: true,
             mouse_position,
             requested_cursor_style: None,
+            requested_input_handler: None,
             scale_factor,
             bounds,
             bounds_observers: SubscriberSet::new(),
@@ -1008,6 +1011,9 @@ 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;
     }
@@ -1152,6 +1158,7 @@ impl<'a> WindowContext<'a> {
                     .insert(any_mouse_event.type_id(), handlers);
             }
         } else if let Some(any_key_event) = event.keyboard_event() {
+            let mut did_handle_action = false;
             let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack);
             let key_event_type = any_key_event.type_id();
             let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
@@ -1172,6 +1179,7 @@ impl<'a> WindowContext<'a> {
                                 self.dispatch_action(action, &key_dispatch_stack[..ix]);
                             }
                             if !self.app.propagate_event {
+                                did_handle_action = true;
                                 break;
                             }
                         }
@@ -1200,6 +1208,7 @@ impl<'a> WindowContext<'a> {
                                 }
 
                                 if !self.app.propagate_event {
+                                    did_handle_action = true;
                                     break;
                                 }
                             }
@@ -1213,6 +1222,7 @@ impl<'a> WindowContext<'a> {
 
             drop(context_stack);
             self.window.key_dispatch_stack = key_dispatch_stack;
+            return did_handle_action;
         }
 
         true
@@ -1995,6 +2005,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/settings2/src/keymap_file.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{settings_store::parse_json_with_comments, SettingsAssets};
 use anyhow::{anyhow, Context, Result};
 use collections::BTreeMap;
-use gpui::{AppContext, KeyBinding, SharedString};
+use gpui::{actions, Action, AppContext, KeyBinding, SharedString};
 use schemars::{
     gen::{SchemaGenerator, SchemaSettings},
     schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
@@ -137,8 +137,10 @@ impl KeymapFile {
     }
 }
 
+actions!(NoAction);
+
 fn no_action() -> Box<dyn gpui::Action> {
-    todo!()
+    NoAction.boxed_clone()
 }
 
 #[cfg(test)]

crates/storybook2/src/story_selector.rs 🔗

@@ -38,6 +38,7 @@ pub enum ComponentStory {
     Palette,
     Panel,
     ProjectPanel,
+    Players,
     RecentProjects,
     Scroll,
     Tab,
@@ -79,6 +80,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(),

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 {
@@ -71,71 +44,213 @@ pub struct GitStatusColors {
 #[refineable(debug, deserialize)]
 pub struct ThemeColors {
     pub border: Hsla,
+    /// Border color. Used for deemphasized borders, like a visual divider between two sections
     pub border_variant: Hsla,
+    /// Border color. Used for focused elements, like keyboard focused list item.
     pub border_focused: Hsla,
+    /// Border color. Used for selected elements, like an active search filter or selected checkbox.
     pub border_selected: Hsla,
+    /// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change.
     pub border_transparent: Hsla,
+    /// Border color. Used for disabled elements, like a disabled input or button.
     pub border_disabled: Hsla,
+    /// Border color. Used for elevated surfaces, like a context menu, popup, or dialog.
     pub elevated_surface_background: Hsla,
+    /// Background Color. Used for grounded surfaces like a panel or tab.
     pub surface_background: Hsla,
+    /// Background Color. Used for the app background and blank panels or windows.
     pub background: Hsla,
+    /// Background Color. Used for the background of an element that should have a different background than the surface it's on.
+    ///
+    /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
+    ///
+    /// For an element that should have the same background as the surface it's on, use `ghost_element_background`.
     pub element_background: Hsla,
+    /// Background Color. Used for the hover state of an element that should have a different background than the surface it's on.
+    ///
+    /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
     pub element_hover: Hsla,
+    /// Background Color. Used for the active state of an element that should have a different background than the surface it's on.
+    ///
+    /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd.
     pub element_active: Hsla,
+    /// Background Color. Used for the selected state of an element that should have a different background than the surface it's on.
+    ///
+    /// Selected states are triggered by the element being selected (or "activated") by the user.
+    ///
+    /// This could include a selected checkbox, a toggleable button that is toggled on, etc.
     pub element_selected: Hsla,
+    /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on.
+    ///
+    /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
     pub element_disabled: Hsla,
-    pub element_placeholder: Hsla,
-    pub element_drop_target: Hsla,
+    /// Background Color. Used for the area that shows where a dragged element will be dropped.
+    pub drop_target_background: Hsla,
+    /// Border Color. Used to show the area that shows where a dragged element will be dropped.
+    // pub drop_target_border: Hsla,
+    /// Used for the background of a ghost element that should have the same background as the surface it's on.
+    ///
+    /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
+    ///
+    /// For an element that should have a different background than the surface it's on, use `element_background`.
     pub ghost_element_background: Hsla,
+    /// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on.
+    ///
+    /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
     pub ghost_element_hover: Hsla,
+    /// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on.
+    ///
+    /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd.
     pub ghost_element_active: Hsla,
+    /// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on.
+    ///
+    /// Selected states are triggered by the element being selected (or "activated") by the user.
+    ///
+    /// This could include a selected checkbox, a toggleable button that is toggled on, etc.
     pub ghost_element_selected: Hsla,
+    /// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on.
+    ///
+    /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
     pub ghost_element_disabled: Hsla,
+    /// Text Color. Default text color used for most text.
     pub text: Hsla,
+    /// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color.
     pub text_muted: Hsla,
+    /// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data.
     pub text_placeholder: Hsla,
+    /// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state.
     pub text_disabled: Hsla,
+    /// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search.
     pub text_accent: Hsla,
+    /// Fill Color. Used for the default fill color of an icon.
     pub icon: Hsla,
+    /// Fill Color. Used for the muted or deemphasized fill color of an icon.
+    ///
+    /// This might be used to show an icon in an inactive pane, or to demphasize a series of icons to give them less visual weight.
     pub icon_muted: Hsla,
+    /// Fill Color. Used for the disabled fill color of an icon.
+    ///
+    /// Disabled states are shown when a user cannot interact with an element, like a icon button.
     pub icon_disabled: Hsla,
+    /// Fill Color. Used for the placeholder fill color of an icon.
+    ///
+    /// This might be used to show an icon in an input that disappears when the user enters text.
     pub icon_placeholder: Hsla,
+    /// Fill Color. Used for the accent fill color of an icon.
+    ///
+    /// This might be used to show when a toggleable icon button is selected.
     pub icon_accent: Hsla,
+
+    // ===
+    // UI Elements
+    // ===
     pub status_bar_background: Hsla,
     pub title_bar_background: Hsla,
     pub toolbar_background: Hsla,
     pub tab_bar_background: Hsla,
     pub tab_inactive_background: Hsla,
     pub tab_active_background: Hsla,
+    // pub panel_background: Hsla,
+    // pub pane_focused_border: Hsla,
+    // /// The color of the scrollbar thumb.
+    // pub scrollbar_thumb_background: Hsla,
+    // /// The color of the scrollbar thumb when hovered over.
+    // pub scrollbar_thumb_hover_background: Hsla,
+    // /// The border color of the scrollbar thumb.
+    // pub scrollbar_thumb_border: Hsla,
+    // /// The background color of the scrollbar track.
+    // pub scrollbar_track_background: Hsla,
+    // /// The border color of the scrollbar track.
+    // pub scrollbar_track_border: Hsla,
+    // /// The opacity of the scrollbar status marks, like diagnostic states and git status..
+    // pub scrollbar_status_opacity: Hsla,
+
+    // ===
+    // Editor
+    // ===
     pub editor_background: Hsla,
+    // pub editor_inactive_background: Hsla,
     pub editor_gutter_background: Hsla,
     pub editor_subheader_background: Hsla,
     pub editor_active_line_background: Hsla,
     pub editor_highlighted_line_background: Hsla,
+    /// Text Color. Used for the text of the line number in the editor gutter.
     pub editor_line_number: Hsla,
+    /// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted.
     pub editor_active_line_number: Hsla,
+    /// Text Color. Used to mark invisible characters in the editor.
+    ///
+    /// Example: spaces, tabs, carriage returns, etc.
     pub editor_invisible: Hsla,
     pub editor_wrap_guide: Hsla,
     pub editor_active_wrap_guide: Hsla,
     pub editor_document_highlight_read_background: Hsla,
     pub editor_document_highlight_write_background: Hsla,
+
+    // ===
+    // Terminal
+    // ===
+    /// Terminal Background Color
     pub terminal_background: Hsla,
+    /// Bright Black Color for ANSI Terminal
     pub terminal_ansi_bright_black: Hsla,
+    /// Bright Red Color for ANSI Terminal
     pub terminal_ansi_bright_red: Hsla,
+    /// Bright Green Color for ANSI Terminal
     pub terminal_ansi_bright_green: Hsla,
+    /// Bright Yellow Color for ANSI Terminal
     pub terminal_ansi_bright_yellow: Hsla,
+    /// Bright Blue Color for ANSI Terminal
     pub terminal_ansi_bright_blue: Hsla,
+    /// Bright Magenta Color for ANSI Terminal
     pub terminal_ansi_bright_magenta: Hsla,
+    /// Bright Cyan Color for ANSI Terminal
     pub terminal_ansi_bright_cyan: Hsla,
+    /// Bright White Color for ANSI Terminal
     pub terminal_ansi_bright_white: Hsla,
+    /// Black Color for ANSI Terminal
     pub terminal_ansi_black: Hsla,
+    /// Red Color for ANSI Terminal
     pub terminal_ansi_red: Hsla,
+    /// Green Color for ANSI Terminal
     pub terminal_ansi_green: Hsla,
+    /// Yellow Color for ANSI Terminal
     pub terminal_ansi_yellow: Hsla,
+    /// Blue Color for ANSI Terminal
     pub terminal_ansi_blue: Hsla,
+    /// Magenta Color for ANSI Terminal
     pub terminal_ansi_magenta: Hsla,
+    /// Cyan Color for ANSI Terminal
     pub terminal_ansi_cyan: Hsla,
+    /// White Color for ANSI Terminal
     pub terminal_ansi_white: Hsla,
+    // new colors
+
+    // ===
+    // Elevation
+    // ===
+    // elevation_0_shadow
+    // elevation_0_shadow_color
+    // elevation_1_shadow
+    // elevation_1_shadow_color
+    // elevation_2_shadow
+    // elevation_2_shadow_color
+    // elevation_3_shadow
+    // elevation_3_shadow_color
+    // elevation_4_shadow
+    // elevation_4_shadow_color
+    // elevation_5_shadow
+    // elevation_5_shadow_color
+
+    // ===
+    // UI Text
+    // ===
+    // pub headline: Hsla,
+    // pub paragraph: Hsla,
+    // pub link: Hsla,
+    // pub link_hover: Hsla,
+    // pub code_block_background: Hsla,
+    // pub code_block_border: Hsla,
 }
 
 #[derive(Refineable, Clone)]

crates/theme2/src/default_colors.rs 🔗

@@ -3,12 +3,106 @@ use std::num::ParseIntError;
 use gpui::{hsla, Hsla, Rgba};
 
 use crate::{
-    colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors},
+    colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors},
     scale::{ColorScaleSet, ColorScales},
     syntax::SyntaxTheme,
-    ColorScale,
+    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,17 +121,17 @@ 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(),
         }
     }
 }
@@ -45,43 +139,16 @@ impl Default for StatusColors {
 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(),
+            conflict: orange().dark().step_9(),
+            created: grass().dark().step_9(),
+            deleted: red().dark().step_9(),
+            ignored: neutral().dark().step_9(),
+            modified: yellow().dark().step_9(),
+            renamed: blue().dark().step_9(),
         }
     }
 }
 
-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 {
@@ -220,8 +287,7 @@ impl ThemeColors {
             element_active: neutral().light().step_5(),
             element_selected: neutral().light().step_5(),
             element_disabled: neutral().light_alpha().step_3(),
-            element_placeholder: neutral().light().step_11(),
-            element_drop_target: blue().light_alpha().step_2(),
+            drop_target_background: blue().light_alpha().step_2(),
             ghost_element_background: system.transparent,
             ghost_element_hover: neutral().light().step_4(),
             ghost_element_active: neutral().light().step_5(),
@@ -293,8 +359,7 @@ impl ThemeColors {
             element_active: neutral().dark().step_5(),
             element_selected: neutral().dark().step_5(),
             element_disabled: neutral().dark_alpha().step_3(),
-            element_placeholder: neutral().dark().step_11(),
-            element_drop_target: blue().dark_alpha().step_2(),
+            drop_target_background: blue().dark_alpha().step_2(),
             ghost_element_background: system.transparent,
             ghost_element_hover: neutral().dark().step_4(),
             ghost_element_active: neutral().dark().step_5(),

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::{GitStatusColors, StatusColors, SystemColors, ThemeColors, ThemeStyles},
+    default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
 };
 
 fn zed_pro_daylight() -> Theme {
@@ -15,7 +15,7 @@ fn zed_pro_daylight() -> Theme {
             colors: ThemeColors::default_light(),
             status: StatusColors::default(),
             git: GitStatusColors::default(),
-            player: PlayerColors::default(),
+            player: PlayerColors::default_light(),
             syntax: Arc::new(SyntaxTheme::default_light()),
         },
     }

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/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::*;
@@ -120,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 🔗

@@ -140,8 +140,7 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> {
             ("element_active", self.0.element_active),
             ("element_selected", self.0.element_selected),
             ("element_disabled", self.0.element_disabled),
-            ("element_placeholder", self.0.element_placeholder),
-            ("element_drop_target", self.0.element_drop_target),
+            ("drop_target_background", self.0.drop_target_background),
             ("ghost_element_background", self.0.ghost_element_background),
             ("ghost_element_hover", self.0.ghost_element_hover),
             ("ghost_element_active", self.0.ghost_element_active),

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

@@ -127,7 +127,7 @@ impl Tab {
         div()
             .id(self.id.clone())
             .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
-            .drag_over::<TabDragState>(|d| d.bg(cx.theme().colors().element_drop_target))
+            .drag_over::<TabDragState>(|d| d.bg(cx.theme().colors().drop_target_background))
             .on_drop(|_view, state: View<TabDragState>, cx| {
                 eprintln!("{:?}", state.read(cx));
             })

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,8 +1,8 @@
 use std::{any::TypeId, sync::Arc};
 
 use gpui::{
-    div, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render,
-    StatelessInteractive, View, ViewContext,
+    div, AnyView, AppContext, DispatchPhase, Div, ParentElement, Render, StatelessInteractive,
+    View, ViewContext,
 };
 
 use crate::Workspace;
@@ -28,10 +28,6 @@ struct ToggleModal {
     name: String,
 }
 
-// complete change of plan?
-// on_action(ToggleModal{ name})
-// register_modal(name, |workspace, cx| { ... })
-
 impl ModalRegistry {
     pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
     where
@@ -40,12 +36,10 @@ impl ModalRegistry {
     {
         let build_view = Arc::new(build_view);
 
-        dbg!("yonder");
         self.registered_modals.push((
             TypeId::of::<A>(),
             Box::new(move |mut div| {
                 let build_view = build_view.clone();
-                dbg!("this point");
 
                 div.on_action(
                     move |workspace: &mut Workspace,
@@ -75,9 +69,7 @@ impl ModalLayer {
         Self { open_modal: None }
     }
 
-    pub fn render(&self, cx: &ViewContext<Workspace>) -> impl Component<Workspace> {
-        dbg!("rendering ModalLayer");
-
+    pub fn render(&self, workspace: &Workspace, cx: &ViewContext<Workspace>) -> Div<Workspace> {
         let mut div = div();
 
         // div, c workspace.toggle_modal()div.on_action()) {

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)
         }
     }
@@ -3707,7 +3707,9 @@ impl Render for Workspace {
             .bg(cx.theme().colors().background)
             .child(self.render_titlebar(cx))
             .child(
-                div()
+                self.modal_layer
+                    .read(cx)
+                    .render(self, cx)
                     .flex_1()
                     .w_full()
                     .flex()
@@ -3840,8 +3842,6 @@ impl Render for Workspace {
                               //         .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
                               // ),
             )
-            //     .child(self.modal_layer.clone())
-            .child(self.modal_layer.read(cx).render(cx))
     }
 }