mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-15 08:08:18 +00:00
fix(editor): keep Enter for new lines in a completion's snippet (#2967)
Completing an XML tag inserts the server's snippet <tag>$1</tag>$0. Enter moved to the next snippet field in every snippet, so it jumped to the end of the line instead of splitting the tag. Enter now moves between fields only in live templates and Emmet; in a completion's snippet Tab does and Enter inserts a line break. Closes #2951
This commit is contained in:
@@ -4,7 +4,13 @@ import { EditorSelection, EditorState, type Extension } from "@codemirror/state"
|
||||
import { EditorView, runScopeHandlers } from "@codemirror/view";
|
||||
import { completedSyntaxState } from "@/test/syntax";
|
||||
import { languages } from "@codemirror/language-data";
|
||||
import { CompletionContext, autocompletion, completionStatus, hasNextSnippetField } from "@codemirror/autocomplete";
|
||||
import {
|
||||
CompletionContext,
|
||||
autocompletion,
|
||||
completionStatus,
|
||||
hasNextSnippetField,
|
||||
snippet,
|
||||
} from "@codemirror/autocomplete";
|
||||
import {
|
||||
EXCLUSIVE_COMPLETION_KEY,
|
||||
expandLiveTemplateAtCaret,
|
||||
@@ -203,6 +209,17 @@ describe("template fields", () => {
|
||||
expect(v.state.selection.main.head).toBe("for (let i = 0; i < ".length);
|
||||
});
|
||||
|
||||
it("leaves Enter to the editor in a completion's snippet", async () => {
|
||||
const v = await editor("typescript", "<dep");
|
||||
// An LSP tag completion: `<dependency>$1</dependency>$0`.
|
||||
snippet("<dependency>${1}</dependency>${0}")(v, { label: "dependency" }, 0, 4);
|
||||
expect(v.state.selection.main.head).toBe("<dependency>".length);
|
||||
expect(hasNextSnippetField(v.state)).toBe(true);
|
||||
expect(press(v, "Enter")).toBe(false);
|
||||
expect(tab(v)).toBe(true);
|
||||
expect(v.state.selection.main.head).toBe(v.state.doc.length);
|
||||
});
|
||||
|
||||
it("Esc first drops the field's selection, then leaves the template", async () => {
|
||||
const v = await editor("typescript", "fori");
|
||||
tab(v);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
closeCompletion,
|
||||
completionStatus,
|
||||
nextSnippetField,
|
||||
pickedCompletion,
|
||||
prevSnippetField,
|
||||
snippet,
|
||||
snippetKeymap,
|
||||
@@ -190,10 +191,22 @@ const unlessLookup =
|
||||
(view) =>
|
||||
completionStatus(view.state) === "active" ? false : cmd(view);
|
||||
|
||||
/** Tab and Enter both move to the next field (Enter is the lookup's while it is open). */
|
||||
/**
|
||||
* Whether the active snippet came from a live template or Emmet, where Enter moves to the
|
||||
* next field. A completion's snippet (an LSP `<tag>$1</tag>$0`) keeps Enter for new lines.
|
||||
*/
|
||||
const templateSnippet = StateField.define<boolean>({
|
||||
create: () => false,
|
||||
update: (value, tr) => (movedToField(tr) === 0 && tr.docChanged ? !tr.annotation(pickedCompletion) : value),
|
||||
});
|
||||
|
||||
/** Tab moves to the next field; Enter too in templates (the lookup's while it is open). */
|
||||
const templateFieldKeys = snippetKeymap.of([
|
||||
{ key: "Tab", run: unlessLookup(nextSnippetField), shift: prevSnippetField },
|
||||
{ key: "Enter", run: unlessLookup(nextSnippetField) },
|
||||
{
|
||||
key: "Enter",
|
||||
run: unlessLookup((view) => view.state.field(templateSnippet, false) === true && nextSnippetField(view)),
|
||||
},
|
||||
{ key: "Escape", run: escapeTemplate },
|
||||
]);
|
||||
|
||||
@@ -590,6 +603,7 @@ export function templateEditorExtension(ctx: EditorDocContext): Extension {
|
||||
return [
|
||||
templateDoc.of({ path: ctx.path, language: ctx.language }),
|
||||
fieldCompletion,
|
||||
templateSnippet,
|
||||
completeOnFieldMove,
|
||||
templateFieldKeys,
|
||||
templateLookup,
|
||||
|
||||
Reference in New Issue
Block a user