Files
kjol/tools/tsgo/internal/ls/displaypartswriter.go
2026-07-09 16:50:43 -04:00

218 lines
6.7 KiB
Go

package ls
import (
"strings"
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/stringutil"
)
var _ printer.EmitTextWriter = &displayPartsWriter{}
// displayPartsWriter implements EmitTextWriter and captures classified text runs
// for VS colorized labels, while also building a plain string.
// When vsCapability is false, only the plain string is built; runs are skipped.
type displayPartsWriter struct {
builder strings.Builder
runs []*lsproto.VSClassifiedTextRun
vsCapability bool
lastWritten string
}
func newDisplayPartsWriter(vsCapability bool) *displayPartsWriter {
return &displayPartsWriter{vsCapability: vsCapability}
}
func (w *displayPartsWriter) addRun(classification lsproto.ClassificationTypeName, text string) {
if text == "" {
return
}
if w.vsCapability {
w.runs = append(w.runs, &lsproto.VSClassifiedTextRun{
ClassificationTypeName: string(classification),
Text: text,
})
}
w.lastWritten = text
w.builder.WriteString(text)
}
// WriteClassified writes text with an explicit classification type.
func (w *displayPartsWriter) WriteClassified(text string, classification lsproto.ClassificationTypeName) {
w.addRun(classification, text)
}
// WriteFrom copies the accumulated content from another displayPartsWriter.
func (w *displayPartsWriter) WriteFrom(other *displayPartsWriter) {
w.builder.WriteString(other.String())
if w.vsCapability {
w.runs = append(w.runs, other.GetRuns()...)
}
if other.lastWritten != "" {
w.lastWritten = other.lastWritten
}
}
func (w *displayPartsWriter) GetRuns() []*lsproto.VSClassifiedTextRun {
return w.runs
}
func (w *displayPartsWriter) String() string {
return w.builder.String()
}
func (w *displayPartsWriter) Clear() {
w.lastWritten = ""
w.builder.Reset()
w.runs = nil
}
func (w displayPartsWriter) DecreaseIndent() {}
func (w displayPartsWriter) GetColumn() core.UTF16Offset { return 0 }
func (w displayPartsWriter) GetIndent() int { return 0 }
func (w displayPartsWriter) GetLine() int { return 0 }
func (w displayPartsWriter) GetTextPos() int {
return w.builder.Len()
}
func (w displayPartsWriter) HasTrailingComment() bool { return false }
func (w displayPartsWriter) HasTrailingWhitespace() bool {
if w.builder.Len() == 0 {
return false
}
ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
if ch == utf8.RuneError {
return false
}
return stringutil.IsWhiteSpaceLike(ch)
}
func (w displayPartsWriter) IncreaseIndent() {}
func (w displayPartsWriter) IsAtStartOfLine() bool { return false }
func (w *displayPartsWriter) RawWrite(s string) {
w.addRun(lsproto.ClassificationTypeNameText, s)
}
func (w *displayPartsWriter) Write(s string) {
w.addRun(lsproto.ClassificationTypeNameText, s)
}
func (w *displayPartsWriter) WriteComment(text string) {
// Strada's writeComment uses unknownWrite → SymbolDisplayPartKind.text → "text"
w.addRun(lsproto.ClassificationTypeNameText, text)
}
func (w *displayPartsWriter) WriteKeyword(text string) {
w.addRun(lsproto.ClassificationTypeNameKeyword, text)
}
func (w *displayPartsWriter) WriteLine() {
w.addRun(lsproto.ClassificationTypeNameWhiteSpace, " ")
}
func (w *displayPartsWriter) WriteLineForce(force bool) {
w.addRun(lsproto.ClassificationTypeNameWhiteSpace, " ")
}
func (w *displayPartsWriter) WriteLiteral(s string) {
// Strada's writeLiteral → SymbolDisplayPartKind.stringLiteral → "string"
w.addRun(lsproto.ClassificationTypeNameString, s)
}
func (w *displayPartsWriter) WriteOperator(text string) {
w.addRun(lsproto.ClassificationTypeNameOperator, text)
}
func (w *displayPartsWriter) WriteParameter(text string) {
w.addRun(lsproto.ClassificationTypeNameParameterName, text)
}
func (w *displayPartsWriter) WriteProperty(text string) {
w.addRun(lsproto.ClassificationTypeNamePropertyName, text)
}
func (w *displayPartsWriter) WritePunctuation(text string) {
w.addRun(lsproto.ClassificationTypeNamePunctuation, text)
}
func (w *displayPartsWriter) WriteSpace(text string) {
w.addRun(lsproto.ClassificationTypeNameWhiteSpace, text)
}
func (w *displayPartsWriter) WriteStringLiteral(text string) {
w.addRun(lsproto.ClassificationTypeNameString, text)
}
func (w *displayPartsWriter) WriteSymbol(text string, symbol *ast.Symbol) {
classification := classificationForSymbol(symbol)
w.addRun(classification, text)
}
func (w *displayPartsWriter) WriteTrailingSemicolon(text string) {
w.addRun(lsproto.ClassificationTypeNamePunctuation, text)
}
// classificationForSymbol determines the Roslyn classification type name based on a symbol's flags.
// Matches the Strada translation chain: displayPartKind() → GetClassificationName().
func classificationForSymbol(symbol *ast.Symbol) lsproto.ClassificationTypeName {
if symbol == nil {
return lsproto.ClassificationTypeNameText
}
flags := symbol.Flags
switch {
case flags&ast.SymbolFlagsVariable != 0:
if isFirstDeclarationOfSymbolParameter(symbol) {
return lsproto.ClassificationTypeNameParameterName
}
return lsproto.ClassificationTypeNameLocalName
case flags&ast.SymbolFlagsProperty != 0:
return lsproto.ClassificationTypeNamePropertyName
case flags&ast.SymbolFlagsGetAccessor != 0:
return lsproto.ClassificationTypeNamePropertyName
case flags&ast.SymbolFlagsSetAccessor != 0:
return lsproto.ClassificationTypeNamePropertyName
case flags&ast.SymbolFlagsEnumMember != 0:
return lsproto.ClassificationTypeNameFieldName
case flags&ast.SymbolFlagsFunction != 0:
return lsproto.ClassificationTypeNameMethodName
case flags&ast.SymbolFlagsClass != 0:
return lsproto.ClassificationTypeNameClassName
case flags&ast.SymbolFlagsInterface != 0:
return lsproto.ClassificationTypeNameInterfaceName
case flags&ast.SymbolFlagsEnum != 0:
return lsproto.ClassificationTypeNameEnumName
case flags&ast.SymbolFlagsModule != 0:
return lsproto.ClassificationTypeNameModuleName
case flags&ast.SymbolFlagsMethod != 0:
return lsproto.ClassificationTypeNameMethodName
case flags&ast.SymbolFlagsTypeParameter != 0:
return lsproto.ClassificationTypeNameTypeParameterName
case flags&ast.SymbolFlagsTypeAlias != 0:
return lsproto.ClassificationTypeNameIdentifier
case flags&ast.SymbolFlagsAlias != 0:
return lsproto.ClassificationTypeNameIdentifier
default:
return lsproto.ClassificationTypeNameText
}
}
// isFirstDeclarationOfSymbolParameter checks if the symbol's first declaration is a parameter.
func isFirstDeclarationOfSymbolParameter(symbol *ast.Symbol) bool {
declarations := symbol.Declarations
if len(declarations) == 0 {
return false
}
return declarations[0].Kind == ast.KindParameter
}