feat: add textDocument/_vs_onAutoInsert handler for VS closing tag auto-insertion#3436
feat: add textDocument/_vs_onAutoInsert handler for VS closing tag auto-insertion#3436lucygramley wants to merge 2 commits intomicrosoft:mainfrom
Conversation
…to-insertion Implement the VS-specific _vs_onAutoInsert LSP method so that closing HTML/JSX tags are automatically inserted when typing '>' in Visual Studio. - Add VsOnAutoInsertOptions, VsOnAutoInsertParams, and VsOnAutoInsertResponseItem protocol types with correct _vs_ prefixed JSON field names matching VS LSP client expectations - Register _vs_onAutoInsertProvider server capability with _vs_triggerCharacters - Add handler that delegates to existing ProvideClosingTagCompletion - Fix jsonName bug in code generator unmarshal switch case Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds Visual Studio–specific support for automatic closing-tag insertion via the textDocument/_vs_onAutoInsert LSP request, integrating it with the existing JSX closing-tag completion logic and updating lsproto codegen to support overridden JSON field names.
Changes:
- Register and implement
textDocument/_vs_onAutoInsertrequest handling in the LSP server and advertise the_vs_onAutoInsertProvidercapability with_vs_triggerCharacters. - Extend lsproto with VS-specific protocol types and
_vs_-prefixed JSON field mappings. - Update JSX closing-tag completion to provide VS-formatted snippet
TextEditdata and enhance fourslash baseline range selection accordingly. - Fix lsproto code generator to respect per-property
jsonNameoverrides in JSON tags and unmarshal switch cases.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/lsp/server.go | Registers the new VS auto-insert request, advertises capability, and implements the handler delegating to closing-tag completion. |
| internal/lsp/lsproto/lsp_generated.go | Adds new protocol capability/types/method wiring and VS-prefixed JSON fields in generated protocol code. |
| internal/lsp/lsproto/_generate/metaModelSchema.mts | Extends the meta-model schema to allow a jsonName override per property. |
| internal/lsp/lsproto/_generate/generate.mts | Implements jsonName support in generated tags/unmarshal logic and defines VS auto-insert/custom structure/request metadata. |
| internal/ls/jsxclosingtag.go | Enriches closing-tag completion results with VS snippet TextEdit + format. |
| internal/fourslash/fourslash.go | Updates closing-tag baseline range selection to prefer VS text-edit range when present. |
| func (s *Server) handleVsOnAutoInsert(ctx context.Context, ls *ls.LanguageService, params *lsproto.VsOnAutoInsertParams) (lsproto.VsOnAutoInsertResponse, error) { | ||
| if params.Character == ">" { | ||
| closingTag, err := ls.ProvideClosingTagCompletion(ctx, &lsproto.TextDocumentPositionParams{ | ||
| TextDocument: params.TextDocument, | ||
| Position: params.Position, | ||
| }) | ||
| if err != nil { | ||
| return lsproto.VsOnAutoInsertResponse{}, err | ||
| } | ||
| if closingTag.CustomClosingTagCompletion != nil { | ||
| return lsproto.VsOnAutoInsertResponse{ | ||
| VsOnAutoInsertResponseItem: &lsproto.VsOnAutoInsertResponseItem{ | ||
| TextEditFormat: lsproto.InsertTextFormatSnippet, |
There was a problem hiding this comment.
This PR adds a new VS-specific request handler (textDocument/_vs_onAutoInsert) and advertises a new server capability, but there is no corresponding LSP integration test validating the capability is announced and that the handler returns the expected snippet/textEdit for JSX. Please add a minimal server test covering at least one successful auto-insert and one null response case.
| return lsproto.VsOnAutoInsertResponse{ | ||
| VsOnAutoInsertResponseItem: &lsproto.VsOnAutoInsertResponseItem{ | ||
| TextEditFormat: lsproto.InsertTextFormatSnippet, | ||
| TextEdit: &lsproto.TextEdit{ | ||
| Range: lsproto.Range{Start: params.Position, End: params.Position}, | ||
| NewText: "$0" + closingTag.CustomClosingTagCompletion.NewText, | ||
| }, |
There was a problem hiding this comment.
handleVsOnAutoInsert duplicates the snippet/text-edit construction that ProvideClosingTagCompletion already computes (CustomClosingTagCompletion.VsTextEdit / VsTextEditFormat). Consider reusing those fields when present to avoid divergence if closing-tag behavior/ranges change, and only fall back to rebuilding a TextEdit when the VS fields are absent.
| return lsproto.VsOnAutoInsertResponse{ | |
| VsOnAutoInsertResponseItem: &lsproto.VsOnAutoInsertResponseItem{ | |
| TextEditFormat: lsproto.InsertTextFormatSnippet, | |
| TextEdit: &lsproto.TextEdit{ | |
| Range: lsproto.Range{Start: params.Position, End: params.Position}, | |
| NewText: "$0" + closingTag.CustomClosingTagCompletion.NewText, | |
| }, | |
| completion := closingTag.CustomClosingTagCompletion | |
| textEditFormat := completion.VsTextEditFormat | |
| textEdit := completion.VsTextEdit | |
| if textEdit == nil { | |
| textEditFormat = lsproto.InsertTextFormatSnippet | |
| textEdit = &lsproto.TextEdit{ | |
| Range: lsproto.Range{Start: params.Position, End: params.Position}, | |
| NewText: "$0" + completion.NewText, | |
| } | |
| } | |
| return lsproto.VsOnAutoInsertResponse{ | |
| VsOnAutoInsertResponseItem: &lsproto.VsOnAutoInsertResponseItem{ | |
| TextEditFormat: textEditFormat, | |
| TextEdit: textEdit, |
| documentation: "Request to get document highlights across multiple files.", | ||
| }, | ||
| { | ||
| method: "textDocument/_vs_onAutoInsert", |
There was a problem hiding this comment.
We should not be putting custom stuff into the main namespaces; use custom/ like the other custom stuff.
I also don't know that we need to say "VS" everywhere?
There was a problem hiding this comment.
Also, Go convention would say VS, not Vs
| /** | ||
| * Override the JSON field name for serialization. | ||
| * If omitted, the property name is used. | ||
| */ | ||
| jsonName?: string; |
There was a problem hiding this comment.
I don't think we need this; the spec is written in terms of the raw JSON, and it's the Go side that should be derived from it
| } | ||
|
|
||
| func (s *Server) handleVsOnAutoInsert(ctx context.Context, ls *ls.LanguageService, params *lsproto.VsOnAutoInsertParams) (lsproto.VsOnAutoInsertResponse, error) { | ||
| if params.Character == ">" { |
There was a problem hiding this comment.
We don't put code like this in this package; this belong in the ls package.
But I'm a little confused why we have this extra call wehn we already have ProvideClosingTagCompletion. Why can't that API be extended and we not have two slightly different calls?
| VsOnAutoInsertProvider: &lsproto.VsOnAutoInsertOptions{ | ||
| TriggerCharacters: []string{">"}, | ||
| }, |
There was a problem hiding this comment.
Doing this makes me wonder if we should instead formalize this feature and implement it the same way in both VS and VS Code.
| // Slight divergence from Strada - we don't use the verbatim text from the opening tag. | ||
| NewText: "</" + ast.EntityNameToString(tagNameNode, scanner.GetTextOfNode) + ">", | ||
| NewText: closingText, | ||
| VsTextEdit: &lsproto.TextEdit{ |
There was a problem hiding this comment.
We should not set this if the client is not VS, as it'll be wasted data on other clients. note how we do check the other VS stuff as of my last PR.
(same below too)
| { | ||
| name: "vsTextEdit", | ||
| jsonName: "_vs_textEdit", | ||
| type: { kind: "reference", name: "TextEdit" }, | ||
| documentation: "The text edit to apply for the closing tag insertion (VS format).", | ||
| }, | ||
| { | ||
| name: "vsTextEditFormat", | ||
| jsonName: "_vs_textEditFormat", | ||
| type: { kind: "reference", name: "InsertTextFormat" }, | ||
| documentation: "The format of the text edit (plaintext or snippet) (VS format).", | ||
| }, |
There was a problem hiding this comment.
These should be optional, no?
|
There's a lot of overlap between this code and what's in |
Implement the VS-specific _vs_onAutoInsert LSP method so that closing HTML/JSX tags are automatically inserted and properly mapped when typing '>' in Visual Studio.