Skip to content

feat: add textDocument/_vs_onAutoInsert handler for VS closing tag auto-insertion#3436

Open
lucygramley wants to merge 2 commits intomicrosoft:mainfrom
lucygramley:fix/closing-tag-completion-textedit
Open

feat: add textDocument/_vs_onAutoInsert handler for VS closing tag auto-insertion#3436
lucygramley wants to merge 2 commits intomicrosoft:mainfrom
lucygramley:fix/closing-tag-completion-textedit

Conversation

@lucygramley
Copy link
Copy Markdown

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.

  • 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

…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>
Copilot AI review requested due to automatic review settings April 17, 2026 22:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_onAutoInsert request handling in the LSP server and advertise the _vs_onAutoInsertProvider capability 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 TextEdit data and enhance fourslash baseline range selection accordingly.
  • Fix lsproto code generator to respect per-property jsonName overrides 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.

Comment thread internal/lsp/server.go
Comment on lines +1457 to +1469
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,
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
Comment thread internal/lsp/server.go
Comment on lines +1467 to +1473
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,
},
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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,

Copilot uses AI. Check for mistakes.
documentation: "Request to get document highlights across multiple files.",
},
{
method: "textDocument/_vs_onAutoInsert",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, Go convention would say VS, not Vs

Comment on lines +349 to +353
/**
* Override the JSON field name for serialization.
* If omitted, the property name is used.
*/
jsonName?: string;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread internal/lsp/server.go
}

func (s *Server) handleVsOnAutoInsert(ctx context.Context, ls *ls.LanguageService, params *lsproto.VsOnAutoInsertParams) (lsproto.VsOnAutoInsertResponse, error) {
if params.Character == ">" {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread internal/lsp/server.go
Comment on lines +1098 to +1100
VsOnAutoInsertProvider: &lsproto.VsOnAutoInsertOptions{
TriggerCharacters: []string{">"},
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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{
Copy link
Copy Markdown
Member

@jakebailey jakebailey Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment on lines +177 to +188
{
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).",
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be optional, no?

@jakebailey
Copy link
Copy Markdown
Member

There's a lot of overlap between this code and what's in _extension/src/languageFeatures/tagClosing.ts; that also construct snippets. It seems to me that we should be reconsidering how everything works and try and even it out instead of adding all new code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants