100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
package ls
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
)
|
|
|
|
func (l *LanguageService) ProvideOnAutoInsert(ctx context.Context, params *lsproto.VSOnAutoInsertParams) (lsproto.VSOnAutoInsertResponse, error) {
|
|
if l.UserPreferences().EnableAutoClosingTags.IsFalse() {
|
|
return lsproto.VSOnAutoInsertResponse{}, nil
|
|
}
|
|
if params.VSCh != ">" {
|
|
return lsproto.VSOnAutoInsertResponse{}, nil
|
|
}
|
|
|
|
_, sourceFile := l.getProgramAndFile(params.VSTextDocument.Uri)
|
|
position := l.converters.LineAndCharacterToPosition(sourceFile, params.VSPosition)
|
|
|
|
token := astnav.FindPrecedingToken(sourceFile, int(position))
|
|
if token == nil {
|
|
return lsproto.VSOnAutoInsertResponse{}, nil
|
|
}
|
|
|
|
var closingText string
|
|
var element *ast.Node
|
|
if token.Kind == ast.KindGreaterThanToken && ast.IsJsxOpeningElement(token.Parent) {
|
|
element = token.Parent.Parent
|
|
} else if ast.IsJsxText(token) && ast.IsJsxElement(token.Parent) {
|
|
element = token.Parent
|
|
}
|
|
|
|
if element != nil && isUnclosedTag(element.AsJsxElement()) {
|
|
tagNameNode := element.AsJsxElement().OpeningElement.TagName()
|
|
// Slight divergence from Strada - we don't use the verbatim text from the opening tag.
|
|
closingText = "</" + ast.EntityNameToString(tagNameNode, scanner.GetTextOfNode) + ">"
|
|
} else {
|
|
var fragment *ast.Node
|
|
if token.Kind == ast.KindGreaterThanToken && ast.IsJsxOpeningFragment(token.Parent) {
|
|
fragment = token.Parent.Parent
|
|
} else if ast.IsJsxText(token) && ast.IsJsxFragment(token.Parent) {
|
|
fragment = token.Parent
|
|
}
|
|
|
|
if fragment != nil && isUnclosedFragment(fragment.AsJsxFragment()) {
|
|
closingText = "</>"
|
|
}
|
|
}
|
|
|
|
if closingText == "" {
|
|
return lsproto.VSOnAutoInsertResponse{}, nil
|
|
}
|
|
|
|
return lsproto.VSOnAutoInsertResponse{
|
|
VSOnAutoInsertResponseItem: &lsproto.VSOnAutoInsertResponseItem{
|
|
VSTextEditFormat: lsproto.InsertTextFormatSnippet,
|
|
VSTextEdit: &lsproto.TextEdit{
|
|
Range: lsproto.Range{Start: params.VSPosition, End: params.VSPosition},
|
|
// Tag names can contain `$` (valid JSX identifier characters), so
|
|
// escape the closing text to avoid being interpreted as a snippet
|
|
// placeholder/variable.
|
|
NewText: "$0" + escapeSnippetText(closingText),
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func isUnclosedTag(node *ast.JsxElement) bool {
|
|
openingElement := node.OpeningElement
|
|
closingElement := node.ClosingElement
|
|
if !ast.TagNamesAreEquivalent(openingElement.TagName(), closingElement.TagName()) {
|
|
return true
|
|
}
|
|
|
|
parent := node.Parent
|
|
if ast.IsJsxElement(parent) {
|
|
parent := parent.AsJsxElement()
|
|
return ast.TagNamesAreEquivalent(openingElement.TagName(), parent.OpeningElement.TagName()) && isUnclosedTag(parent)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isUnclosedFragment(node *ast.JsxFragment) bool {
|
|
closingFragment := node.ClosingFragment
|
|
if closingFragment.Flags&ast.NodeFlagsThisNodeHasError != 0 {
|
|
return true
|
|
}
|
|
|
|
parent := node.Parent
|
|
if ast.IsJsxFragment(parent) && isUnclosedFragment(parent.AsJsxFragment()) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|