vendor tsgo
This commit is contained in:
181
tools/tsgo/internal/ls/format.go
Normal file
181
tools/tsgo/internal/ls/format.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package ls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/astnav"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/format"
|
||||
"github.com/microsoft/typescript-go/internal/ls/lsutil"
|
||||
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
||||
"github.com/microsoft/typescript-go/internal/scanner"
|
||||
)
|
||||
|
||||
func (l *LanguageService) toLSProtoTextEdits(file *ast.SourceFile, changes []core.TextChange) []*lsproto.TextEdit {
|
||||
result := make([]*lsproto.TextEdit, 0, len(changes))
|
||||
for _, c := range changes {
|
||||
result = append(result, &lsproto.TextEdit{
|
||||
NewText: c.NewText,
|
||||
Range: l.createLspRangeFromBounds(c.Pos(), c.End(), file),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *LanguageService) ProvideFormatDocument(
|
||||
ctx context.Context,
|
||||
documentURI lsproto.DocumentUri,
|
||||
options *lsproto.FormattingOptions,
|
||||
) (lsproto.DocumentFormattingResponse, error) {
|
||||
if l.UserPreferences().EnableFormatting.IsFalse() {
|
||||
return lsproto.TextEditsOrNull{}, nil
|
||||
}
|
||||
_, file := l.getProgramAndFile(documentURI)
|
||||
formatOpts := lsutil.FromLSFormatOptions(l.FormatOptions(), options)
|
||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsForDocument(
|
||||
ctx,
|
||||
file,
|
||||
formatOpts,
|
||||
))
|
||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
||||
}
|
||||
|
||||
func (l *LanguageService) ProvideFormatDocumentRange(
|
||||
ctx context.Context,
|
||||
documentURI lsproto.DocumentUri,
|
||||
options *lsproto.FormattingOptions,
|
||||
r lsproto.Range,
|
||||
) (lsproto.DocumentRangeFormattingResponse, error) {
|
||||
if l.UserPreferences().EnableFormatting.IsFalse() {
|
||||
return lsproto.TextEditsOrNull{}, nil
|
||||
}
|
||||
_, file := l.getProgramAndFile(documentURI)
|
||||
formatOpts := lsutil.FromLSFormatOptions(l.FormatOptions(), options)
|
||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsForRange(
|
||||
ctx,
|
||||
file,
|
||||
formatOpts,
|
||||
l.converters.FromLSPRange(file, r),
|
||||
))
|
||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
||||
}
|
||||
|
||||
func (l *LanguageService) ProvideFormatDocumentOnType(
|
||||
ctx context.Context,
|
||||
documentURI lsproto.DocumentUri,
|
||||
options *lsproto.FormattingOptions,
|
||||
position lsproto.Position,
|
||||
character string,
|
||||
) (lsproto.DocumentOnTypeFormattingResponse, error) {
|
||||
if l.UserPreferences().EnableFormatting.IsFalse() {
|
||||
return lsproto.TextEditsOrNull{}, nil
|
||||
}
|
||||
_, file := l.getProgramAndFile(documentURI)
|
||||
formatOpts := lsutil.FromLSFormatOptions(l.FormatOptions(), options)
|
||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsAfterKeystroke(
|
||||
ctx,
|
||||
file,
|
||||
formatOpts,
|
||||
int(l.converters.LineAndCharacterToPosition(file, position)),
|
||||
character,
|
||||
))
|
||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
||||
}
|
||||
|
||||
func (l *LanguageService) getFormattingEditsForRange(
|
||||
ctx context.Context,
|
||||
file *ast.SourceFile,
|
||||
options lsutil.FormatCodeSettings,
|
||||
r core.TextRange,
|
||||
) []core.TextChange {
|
||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
||||
return format.FormatSelection(ctx, file, r.Pos(), r.End())
|
||||
}
|
||||
|
||||
func (l *LanguageService) getFormattingEditsForDocument(
|
||||
ctx context.Context,
|
||||
file *ast.SourceFile,
|
||||
options lsutil.FormatCodeSettings,
|
||||
) []core.TextChange {
|
||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
||||
return format.FormatDocument(ctx, file)
|
||||
}
|
||||
|
||||
func (l *LanguageService) getFormattingEditsAfterKeystroke(
|
||||
ctx context.Context,
|
||||
file *ast.SourceFile,
|
||||
options lsutil.FormatCodeSettings,
|
||||
position int,
|
||||
key string,
|
||||
) []core.TextChange {
|
||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
||||
|
||||
tokenAtPosition := astnav.GetTokenAtPosition(file, position)
|
||||
if isInComment(file, position, tokenAtPosition) == nil {
|
||||
switch key {
|
||||
case "{":
|
||||
return format.FormatOnOpeningCurly(ctx, file, position)
|
||||
case "}":
|
||||
return format.FormatOnClosingCurly(ctx, file, position)
|
||||
case ";":
|
||||
return format.FormatOnSemicolon(ctx, file, position)
|
||||
case "\n":
|
||||
return format.FormatOnEnter(ctx, file, position)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlike the TS implementation, this function *will not* compute default values for
|
||||
// `precedingToken` and `tokenAtPosition`.
|
||||
// It is the caller's responsibility to call `astnav.GetTokenAtPosition` to compute a default `tokenAtPosition`,
|
||||
// or `astnav.FindPrecedingToken` to compute a default `precedingToken`.
|
||||
func getRangeOfEnclosingComment(
|
||||
file *ast.SourceFile,
|
||||
position int,
|
||||
precedingToken *ast.Node,
|
||||
tokenAtPosition *ast.Node,
|
||||
) *ast.CommentRange {
|
||||
jsdoc := ast.FindAncestor(tokenAtPosition, (*ast.Node).IsJSDoc)
|
||||
if jsdoc != nil {
|
||||
tokenAtPosition = jsdoc.Parent
|
||||
}
|
||||
tokenStart := astnav.GetStartOfNode(tokenAtPosition, file, false /*includeJSDoc*/)
|
||||
if tokenStart <= position && position < tokenAtPosition.End() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Between two consecutive tokens, all comments are either trailing on the former
|
||||
// or leading on the latter (and none are in both lists).
|
||||
var trailingRangesOfPreviousToken iter.Seq[ast.CommentRange]
|
||||
if precedingToken != nil {
|
||||
trailingRangesOfPreviousToken = scanner.GetTrailingCommentRanges(&ast.NodeFactory{}, file.Text(), precedingToken.End())
|
||||
}
|
||||
leadingRangesOfNextToken := getLeadingCommentRangesOfNode(tokenAtPosition, file)
|
||||
commentRanges := core.ConcatenateSeq(trailingRangesOfPreviousToken, leadingRangesOfNextToken)
|
||||
for commentRange := range commentRanges {
|
||||
// The end marker of a single-line comment does not include the newline character.
|
||||
// In the following case where the cursor is at `^`, we are inside a comment:
|
||||
//
|
||||
// // asdf ^\n
|
||||
//
|
||||
// But for closed multi-line comments, we don't want to be inside the comment in the following case:
|
||||
//
|
||||
// /* asdf */^
|
||||
//
|
||||
// Internally, we represent the end of the comment prior to the newline and at the '/', respectively.
|
||||
//
|
||||
// However, unterminated multi-line comments lack a `/`, end at the end of the file, and *do* contain their end.
|
||||
//
|
||||
if commentRange.ContainsExclusive(position) ||
|
||||
position == commentRange.End() &&
|
||||
(commentRange.Kind == ast.KindSingleLineCommentTrivia || position == len(file.Text())) {
|
||||
return &commentRange
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user