Start work on new text input handling in Editor

Max Brunsfeld created

Change summary

crates/collab/src/integration_tests.rs |  10 -
crates/editor/src/display_map.rs       |   5 +
crates/editor/src/editor.rs            | 133 ++++++++++++++++++++++-----
crates/theme/src/theme.rs              |   1 
crates/zed/src/zed.rs                  |  12 -
styles/src/styleTree/editor.ts         |  11 +
6 files changed, 129 insertions(+), 43 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs πŸ”—

@@ -11,8 +11,8 @@ use client::{
 };
 use collections::{BTreeMap, HashMap, HashSet};
 use editor::{
-    self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
-    ToOffset, ToggleCodeActions, Undo,
+    self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset,
+    ToggleCodeActions, Undo,
 };
 use futures::{channel::mpsc, Future, StreamExt as _};
 use gpui::{
@@ -154,9 +154,7 @@ async fn test_share_project(
     //     .await;
 
     // Edit the buffer as client B and see that edit as client A.
-    editor_b.update(cx_b, |editor, cx| {
-        editor.handle_input(&Input("ok, ".into()), cx)
-    });
+    editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
     buffer_a
         .condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
         .await;
@@ -1751,7 +1749,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     // Type a completion trigger character as the guest.
     editor_b.update(cx_b, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-        editor.handle_input(&Input(".".into()), cx);
+        editor.handle_input(".", cx);
         cx.focus(&editor_b);
     });
 

crates/editor/src/display_map.rs πŸ”—

@@ -195,6 +195,11 @@ impl DisplayMap {
             .insert(Some(type_id), Arc::new((style, ranges)));
     }
 
+    pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
+        let highlights = self.text_highlights.get(&Some(type_id))?;
+        Some((highlights.0, &highlights.1))
+    }
+
     pub fn clear_text_highlights(
         &mut self,
         type_id: TypeId,

crates/editor/src/editor.rs πŸ”—

@@ -228,6 +228,7 @@ impl_internal_actions!(editor, [Scroll, Select, Jump]);
 
 enum DocumentHighlightRead {}
 enum DocumentHighlightWrite {}
+enum InputComposition {}
 
 #[derive(Copy, Clone, PartialEq, Eq)]
 pub enum Direction {
@@ -240,7 +241,6 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
     cx.add_action(Editor::select);
     cx.add_action(Editor::cancel);
-    cx.add_action(Editor::handle_input);
     cx.add_action(Editor::newline);
     cx.add_action(Editor::backspace);
     cx.add_action(Editor::delete);
@@ -1813,13 +1813,11 @@ impl Editor {
         cx.propagate_action();
     }
 
-    pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext<Self>) {
+    pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
         if !self.input_enabled {
-            cx.propagate_action();
             return;
         }
 
-        let text = action.0.as_ref();
         if !self.skip_autoclose_end(text, cx) {
             self.transact(cx, |this, cx| {
                 if !this.surround_with_bracket_pair(text, cx) {
@@ -5522,6 +5520,13 @@ 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 clear_text_highlights<T: 'static>(
         &mut self,
         cx: &mut ViewContext<Self>,
@@ -5871,6 +5876,80 @@ impl View for Editor {
 
         context
     }
+
+    fn text_for_range(&self, range: Range<usize>, cx: &AppContext) -> Option<String> {
+        Some(
+            self.buffer
+                .read(cx)
+                .read(cx)
+                .text_for_range(range)
+                .collect(),
+        )
+    }
+
+    fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
+        Some(self.selections.newest(cx).range())
+    }
+
+    fn set_selected_text_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>) {
+        self.change_selections(None, cx, |selections| selections.select_ranges([range]));
+    }
+
+    fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
+        let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
+        Some(range.to_offset(&*self.buffer.read(cx).read(cx)))
+    }
+
+    fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
+        self.clear_text_highlights::<InputComposition>(cx);
+    }
+
+    fn replace_text_in_range(
+        &mut self,
+        range: Option<Range<usize>>,
+        text: &str,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.transact(cx, |this, cx| {
+            if let Some(range) = range {
+                this.set_selected_text_range(range, cx);
+            }
+            this.handle_input(text, cx);
+        });
+    }
+
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range: Option<Range<usize>>,
+        text: &str,
+        _new_selected_range: Option<Range<usize>>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.transact(cx, |this, cx| {
+            let range = range.or_else(|| {
+                let ranges = this.text_highlights::<InputComposition>(cx)?.1;
+                let range = ranges.first()?;
+                let snapshot = this.buffer.read(cx).read(cx);
+                Some(range.to_offset(&*snapshot))
+            });
+            if let Some(range) = range {
+                this.set_selected_text_range(range, cx);
+            }
+
+            let selection = this.selections.newest_anchor();
+            let marked_range = {
+                let snapshot = this.buffer.read(cx).read(cx);
+                selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
+            };
+            this.highlight_text::<InputComposition>(
+                vec![marked_range],
+                this.style(cx).composition_mark,
+                cx,
+            );
+
+            this.handle_input(text, cx);
+        });
+    }
 }
 
 fn build_style(
@@ -8241,9 +8320,9 @@ mod tests {
         // is pasted at each cursor.
         cx.set_state("|two oneβœ… four three six five |");
         cx.update_editor(|e, cx| {
-            e.handle_input(&Input("( ".into()), cx);
+            e.handle_input("( ", cx);
             e.paste(&Paste, cx);
-            e.handle_input(&Input(") ".into()), cx);
+            e.handle_input(") ", cx);
         });
         cx.assert_editor_state(indoc! {"
             ( oneβœ… 
@@ -8918,9 +8997,9 @@ mod tests {
                 ])
             });
 
-            view.handle_input(&Input("{".to_string()), cx);
-            view.handle_input(&Input("{".to_string()), cx);
-            view.handle_input(&Input("{".to_string()), cx);
+            view.handle_input("{", cx);
+            view.handle_input("{", cx);
+            view.handle_input("{", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -8933,9 +9012,9 @@ mod tests {
             );
 
             view.move_right(&MoveRight, cx);
-            view.handle_input(&Input("}".to_string()), cx);
-            view.handle_input(&Input("}".to_string()), cx);
-            view.handle_input(&Input("}".to_string()), cx);
+            view.handle_input("}", cx);
+            view.handle_input("}", cx);
+            view.handle_input("}", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -8948,8 +9027,8 @@ mod tests {
             );
 
             view.undo(&Undo, cx);
-            view.handle_input(&Input("/".to_string()), cx);
-            view.handle_input(&Input("*".to_string()), cx);
+            view.handle_input("/", cx);
+            view.handle_input("*", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -8968,7 +9047,7 @@ mod tests {
                     DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
                 ])
             });
-            view.handle_input(&Input("*".to_string()), cx);
+            view.handle_input("*", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -8986,7 +9065,7 @@ mod tests {
             view.change_selections(None, cx, |s| {
                 s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
             });
-            view.handle_input(&Input("{".to_string()), cx);
+            view.handle_input("{", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -9002,7 +9081,7 @@ mod tests {
             view.change_selections(None, cx, |s| {
                 s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)])
             });
-            view.handle_input(&Input("{".to_string()), cx);
+            view.handle_input("{", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -9019,7 +9098,7 @@ mod tests {
             );
 
             view.undo(&Undo, cx);
-            view.handle_input(&Input("[".to_string()), cx);
+            view.handle_input("[", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -9039,7 +9118,7 @@ mod tests {
             view.change_selections(None, cx, |s| {
                 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
             });
-            view.handle_input(&Input("[".to_string()), cx);
+            view.handle_input("[", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -9095,9 +9174,9 @@ mod tests {
                 ])
             });
 
-            view.handle_input(&Input("{".to_string()), cx);
-            view.handle_input(&Input("{".to_string()), cx);
-            view.handle_input(&Input("{".to_string()), cx);
+            view.handle_input("{", cx);
+            view.handle_input("{", cx);
+            view.handle_input("{", cx);
             assert_eq!(
                 view.text(cx),
                 "
@@ -9177,9 +9256,9 @@ mod tests {
                 ])
             });
 
-            editor.handle_input(&Input("{".to_string()), cx);
-            editor.handle_input(&Input("{".to_string()), cx);
-            editor.handle_input(&Input("_".to_string()), cx);
+            editor.handle_input("{", cx);
+            editor.handle_input("{", cx);
+            editor.handle_input("_", cx);
             assert_eq!(
                 editor.text(cx),
                 "
@@ -9905,7 +9984,7 @@ mod tests {
                 ])
             });
 
-            view.handle_input(&Input("X".to_string()), cx);
+            view.handle_input("X", cx);
             assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
             assert_eq!(
                 view.selections.ranges(cx),
@@ -9945,7 +10024,7 @@ mod tests {
             assert_eq!(view.text(cx), expected_text);
             view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 
-            view.handle_input(&Input("X".to_string()), cx);
+            view.handle_input("X", cx);
 
             let (expected_text, expected_selections) = marked_text_ranges(indoc! {"
                 aaaa

crates/theme/src/theme.rs πŸ”—

@@ -450,6 +450,7 @@ pub struct Editor {
     pub unnecessary_code_fade: f32,
     pub hover_popover: HoverPopover,
     pub link_definition: HighlightStyle,
+    pub composition_mark: HighlightStyle,
     pub jump_icon: Interactive<IconButton>,
 }
 

crates/zed/src/zed.rs πŸ”—

@@ -920,11 +920,7 @@ mod tests {
             item.downcast::<Editor>().unwrap()
         });
 
-        cx.update(|cx| {
-            editor.update(cx, |editor, cx| {
-                editor.handle_input(&editor::Input("x".into()), cx)
-            })
-        });
+        cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input("x", cx)));
         app_state
             .fs
             .as_fake()
@@ -971,7 +967,7 @@ mod tests {
                 editor.language_at(0, cx).unwrap(),
                 &languages::PLAIN_TEXT
             ));
-            editor.handle_input(&editor::Input("hi".into()), cx);
+            editor.handle_input("hi", cx);
             assert!(editor.is_dirty(cx));
         });
 
@@ -997,7 +993,7 @@ mod tests {
 
         // Edit the file and save it again. This time, there is no filename prompt.
         editor.update(cx, |editor, cx| {
-            editor.handle_input(&editor::Input(" there".into()), cx);
+            editor.handle_input(" there", cx);
             assert_eq!(editor.is_dirty(cx.as_ref()), true);
         });
         let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
@@ -1057,7 +1053,7 @@ mod tests {
                 editor.language_at(0, cx).unwrap(),
                 &languages::PLAIN_TEXT
             ));
-            editor.handle_input(&editor::Input("hi".into()), cx);
+            editor.handle_input("hi", cx);
             assert!(editor.is_dirty(cx.as_ref()));
         });
 

styles/src/styleTree/editor.ts πŸ”—

@@ -2,6 +2,7 @@ import Theme from "../themes/common/theme";
 import {
   backgroundColor,
   border,
+  borderColor,
   iconColor,
   player,
   popoverShadow,
@@ -138,8 +139,8 @@ export default function editor(theme: Theme) {
     invalidHintDiagnostic: diagnostic(theme, "muted"),
     invalidInformationDiagnostic: diagnostic(theme, "muted"),
     invalidWarningDiagnostic: diagnostic(theme, "muted"),
-    hover_popover: hoverPopover(theme),
-    link_definition: {
+    hoverPopover: hoverPopover(theme),
+    linkDefinition: {
       color: theme.syntax.linkUri.color,
       underline: theme.syntax.linkUri.underline,
     },
@@ -159,6 +160,12 @@ export default function editor(theme: Theme) {
         background: backgroundColor(theme, "on500", "base"),
       }
     },
+    compositionMark: {
+      underline: {
+        thickness: 1.0,
+        color: borderColor(theme, "active")
+      },
+    },
     syntax,
   };
 }