Add editor::InsertSnippet action (#44428)

Closes #20036

This introduces new action `editor: insert snippet`. It supports three
modes:

```
["editor::InsertSnippet", {"name": "snippet_name"}]
["editor::InsertSnippet", {"language": "language_name", "name": "snippet_name"}]
["editor::InsertSnippet", {"snippet": "snippet with $1 tab stops"}]
```

## Example usage

### `keymap.json`

```json
  {
    "context": "Editor",
    "bindings": {
      // named global snippet
      "cmd-k cmd-r": ["editor::InsertSnippet", {"name": "all rights reserved"}],
      // named language-specific snippet
      "cmd-k cmd-p": ["editor::InsertSnippet", {"language": "rust", "name": "debug-print a value"}],
      // inline snippet
      "cmd-k cmd-e": ["editor::InsertSnippet", {"snippet": "println!(\"This snippet has multiple lines.\")\nprintln!(\"It belongs to $1 and is very $2.\")"}],
    },
  },
```

### `~/.config/zed/snippets/rust.json`

```json
{
  "debug-print a value": {
    "body": "println!(\"$1 = {:?}\", $1)",
  },
}
```

### `~/.config/zed/snippets/snippets.json`

```json
{
  "all rights reserved": {
    "body": "Copyright © ${1:2025} ${2:your name}. All rights reserved.",
  },
}
```

## Future extensions

- Support multiline inline snippets using an array of strings using
something similar to `ListOrDirect` in
`snippet_provider::format::VsCodeSnippet`
- When called with no arguments, open a modal to select a snippet to
insert

## Release notes

Release Notes:

- Added `editor::InsertSnippet` action
This commit is contained in:
Andrew Farkas
2025-12-08 21:38:24 +00:00
committed by GitHub
parent 22e1bcccad
commit 6b2d1f153d
5 changed files with 142 additions and 2 deletions
+76
View File
@@ -26895,6 +26895,82 @@ async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_insert_snippet(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.update_editor(|editor, _, cx| {
editor.project().unwrap().update(cx, |project, cx| {
project.snippets().update(cx, |snippets, _cx| {
let snippet = project::snippet_provider::Snippet {
prefix: vec![], // no prefix needed!
body: "an Unspecified".to_string(),
description: Some("shhhh it's a secret".to_string()),
name: "super secret snippet".to_string(),
};
snippets.add_snippet_for_test(
None,
PathBuf::from("test_snippets.json"),
vec![Arc::new(snippet)],
);
let snippet = project::snippet_provider::Snippet {
prefix: vec![], // no prefix needed!
body: " Location".to_string(),
description: Some("the word 'location'".to_string()),
name: "location word".to_string(),
};
snippets.add_snippet_for_test(
Some("Markdown".to_string()),
PathBuf::from("test_snippets.json"),
vec![Arc::new(snippet)],
);
});
})
});
cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
cx.update_editor(|editor, window, cx| {
editor.insert_snippet_at_selections(
&InsertSnippet {
language: None,
name: Some("super secret snippet".to_string()),
snippet: None,
},
window,
cx,
);
// Language is specified in the action,
// so the buffer language does not need to match
editor.insert_snippet_at_selections(
&InsertSnippet {
language: Some("Markdown".to_string()),
name: Some("location word".to_string()),
snippet: None,
},
window,
cx,
);
editor.insert_snippet_at_selections(
&InsertSnippet {
language: None,
name: None,
snippet: Some("$0 after".to_string()),
},
window,
cx,
);
});
cx.assert_editor_state(
r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
);
}
#[gpui::test(iterations = 10)]
async fn test_document_colors(cx: &mut TestAppContext) {
let expected_color = Rgba {