Allow inserting text into the editor via the action (#16131)

Kirill Bulatov and Alex Kladov created

Improves workflows, based on the keymaps that group actions behind a
certain symbol.
E.g.

```json5
", o k": "zed::OpenKeymap",
", o K": "zed::OpenDefaultKeymap",
// other `,`-based keymaps
```

Now, it's possible to do 
```json
", ,": ["editor::HandleInput", ","]
```

and type `,` without waiting for the timeout due to `,`-based bindings.

Release Notes:

- Add an `editor::HandleInput` action to ease typing symbols that are
part of keymaps. E.g. if `, o k` keybinding is bound, `", ,":
["editor::HandleInput", ","]` would allow to type `,` without timeouts.

---------

Co-authored-by: Alex Kladov <aleksey.kladov@gmail.com>

Change summary

crates/editor/src/actions.rs      |  4 +++
crates/editor/src/editor_tests.rs | 44 +++++++++++++++++++++++++++++++++
crates/editor/src/element.rs      | 12 ++++++--
3 files changed, 57 insertions(+), 3 deletions(-)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -132,6 +132,9 @@ pub struct ShowCompletions {
     pub(super) trigger: Option<String>,
 }
 
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct HandleInput(pub String);
+
 impl_actions!(
     editor,
     [
@@ -141,6 +144,7 @@ impl_actions!(
         ExpandExcerptsUp,
         ExpandExcerptsDown,
         FoldAt,
+        HandleInput,
         MoveDownByLines,
         MovePageDown,
         MovePageUp,

crates/editor/src/editor_tests.rs 🔗

@@ -13105,6 +13105,50 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
     assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
 }
 
+#[gpui::test]
+async fn test_input_text(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    let mut cx = EditorTestContext::new(cx).await;
+
+    cx.set_state(
+        &r#"ˇone
+        two
+
+        three
+        fourˇ
+        five
+
+        siˇx"#
+            .unindent(),
+    );
+
+    cx.dispatch_action(HandleInput(String::new()));
+    cx.assert_editor_state(
+        &r#"ˇone
+        two
+
+        three
+        fourˇ
+        five
+
+        siˇx"#
+            .unindent(),
+    );
+
+    cx.dispatch_action(HandleInput("AAAA".to_string()));
+    cx.assert_editor_state(
+        &r#"AAAAˇone
+        two
+
+        three
+        fourAAAAˇ
+        five
+
+        siAAAAˇx"#
+            .unindent(),
+    );
+}
+
 fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
     let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
     point..point

crates/editor/src/element.rs 🔗

@@ -22,9 +22,9 @@ use crate::{
     BlockId, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
     DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
     EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
-    HalfPageUp, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown, PageUp,
-    Point, RangeToAnchorExt, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
-    CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
+    HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
+    PageUp, Point, RangeToAnchorExt, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
+    ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
 };
 use client::ParticipantIndex;
 use collections::{BTreeMap, HashMap};
@@ -231,6 +231,12 @@ impl EditorElement {
         register_action(view, cx, |editor, _: &HalfPageDown, cx| {
             editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
         });
+        register_action(view, cx, |editor, HandleInput(text): &HandleInput, cx| {
+            if text.is_empty() {
+                return;
+            }
+            editor.handle_input(&text, cx);
+        });
         register_action(view, cx, |editor, _: &HalfPageUp, cx| {
             editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
         });