2025-10-15 10:12:44 +03:00

924 lines
29 KiB
Go

package lsp
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"runtime/debug"
"slices"
"sync"
"sync/atomic"
"syscall"
"time"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ls"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/ata"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/logging"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
"github.com/go-json-experiment/json"
"golang.org/x/sync/errgroup"
"golang.org/x/text/language"
)
type ServerOptions struct {
In Reader
Out Writer
Err io.Writer
Cwd string
FS vfs.FS
DefaultLibraryPath string
TypingsLocation string
ParseCache *project.ParseCache
}
func NewServer(opts *ServerOptions) *Server {
if opts.Cwd == "" {
panic("Cwd is required")
}
return &Server{
r: opts.In,
w: opts.Out,
stderr: opts.Err,
logger: logging.NewLogger(opts.Err),
requestQueue: make(chan *lsproto.RequestMessage, 100),
outgoingQueue: make(chan *lsproto.Message, 100),
pendingClientRequests: make(map[lsproto.ID]pendingClientRequest),
pendingServerRequests: make(map[lsproto.ID]chan *lsproto.ResponseMessage),
cwd: opts.Cwd,
fs: opts.FS,
defaultLibraryPath: opts.DefaultLibraryPath,
typingsLocation: opts.TypingsLocation,
parseCache: opts.ParseCache,
}
}
var (
_ ata.NpmExecutor = (*Server)(nil)
_ project.Client = (*Server)(nil)
)
type pendingClientRequest struct {
req *lsproto.RequestMessage
cancel context.CancelFunc
}
type Reader interface {
Read() (*lsproto.Message, error)
}
type Writer interface {
Write(msg *lsproto.Message) error
}
type lspReader struct {
r *lsproto.BaseReader
}
type lspWriter struct {
w *lsproto.BaseWriter
}
func (r *lspReader) Read() (*lsproto.Message, error) {
data, err := r.r.Read()
if err != nil {
return nil, err
}
req := &lsproto.Message{}
if err := json.Unmarshal(data, req); err != nil {
return nil, fmt.Errorf("%w: %w", lsproto.ErrInvalidRequest, err)
}
return req, nil
}
func ToReader(r io.Reader) Reader {
return &lspReader{r: lsproto.NewBaseReader(r)}
}
func (w *lspWriter) Write(msg *lsproto.Message) error {
data, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
return w.w.Write(data)
}
func ToWriter(w io.Writer) Writer {
return &lspWriter{w: lsproto.NewBaseWriter(w)}
}
var (
_ Reader = (*lspReader)(nil)
_ Writer = (*lspWriter)(nil)
)
type Server struct {
r Reader
w Writer
stderr io.Writer
logger logging.Logger
clientSeq atomic.Int32
requestQueue chan *lsproto.RequestMessage
outgoingQueue chan *lsproto.Message
pendingClientRequests map[lsproto.ID]pendingClientRequest
pendingClientRequestsMu sync.Mutex
pendingServerRequests map[lsproto.ID]chan *lsproto.ResponseMessage
pendingServerRequestsMu sync.Mutex
cwd string
fs vfs.FS
defaultLibraryPath string
typingsLocation string
initializeParams *lsproto.InitializeParams
positionEncoding lsproto.PositionEncodingKind
locale language.Tag
watchEnabled bool
watcherID atomic.Uint32
watchers collections.SyncSet[project.WatcherID]
session *project.Session
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
compilerOptionsForInferredProjects *core.CompilerOptions
// parseCache can be passed in so separate tests can share ASTs
parseCache *project.ParseCache
}
// WatchFiles implements project.Client.
func (s *Server) WatchFiles(ctx context.Context, id project.WatcherID, watchers []*lsproto.FileSystemWatcher) error {
_, err := s.sendRequest(ctx, lsproto.MethodClientRegisterCapability, &lsproto.RegistrationParams{
Registrations: []*lsproto.Registration{
{
Id: string(id),
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
RegisterOptions: ptrTo(any(lsproto.DidChangeWatchedFilesRegistrationOptions{
Watchers: watchers,
})),
},
},
})
if err != nil {
return fmt.Errorf("failed to register file watcher: %w", err)
}
s.watchers.Add(id)
return nil
}
// UnwatchFiles implements project.Client.
func (s *Server) UnwatchFiles(ctx context.Context, id project.WatcherID) error {
if s.watchers.Has(id) {
_, err := s.sendRequest(ctx, lsproto.MethodClientUnregisterCapability, &lsproto.UnregistrationParams{
Unregisterations: []*lsproto.Unregistration{
{
Id: string(id),
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
},
},
})
if err != nil {
return fmt.Errorf("failed to unregister file watcher: %w", err)
}
s.watchers.Delete(id)
return nil
}
return fmt.Errorf("no file watcher exists with ID %s", id)
}
// RefreshDiagnostics implements project.Client.
func (s *Server) RefreshDiagnostics(ctx context.Context) error {
if s.initializeParams.Capabilities == nil ||
s.initializeParams.Capabilities.Workspace == nil ||
s.initializeParams.Capabilities.Workspace.Diagnostics == nil ||
!ptrIsTrue(s.initializeParams.Capabilities.Workspace.Diagnostics.RefreshSupport) {
return nil
}
if _, err := s.sendRequest(ctx, lsproto.MethodWorkspaceDiagnosticRefresh, nil); err != nil {
return fmt.Errorf("failed to refresh diagnostics: %w", err)
}
return nil
}
func (s *Server) Run() error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return s.dispatchLoop(ctx) })
g.Go(func() error { return s.writeLoop(ctx) })
// Don't run readLoop in the group, as it blocks on stdin read and cannot be cancelled.
readLoopErr := make(chan error, 1)
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
case err := <-readLoopErr:
return err
}
})
go func() { readLoopErr <- s.readLoop(ctx) }()
if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) && ctx.Err() != nil {
return err
}
return nil
}
func (s *Server) readLoop(ctx context.Context) error {
for {
if err := ctx.Err(); err != nil {
return err
}
msg, err := s.read()
if err != nil {
if errors.Is(err, lsproto.ErrInvalidRequest) {
s.sendError(nil, err)
continue
}
return err
}
if s.initializeParams == nil && msg.Kind == lsproto.MessageKindRequest {
req := msg.AsRequest()
if req.Method == lsproto.MethodInitialize {
resp, err := s.handleInitialize(ctx, req.Params.(*lsproto.InitializeParams), req)
if err != nil {
return err
}
s.sendResult(req.ID, resp)
} else {
s.sendError(req.ID, lsproto.ErrServerNotInitialized)
}
continue
}
if msg.Kind == lsproto.MessageKindResponse {
resp := msg.AsResponse()
s.pendingServerRequestsMu.Lock()
if respChan, ok := s.pendingServerRequests[*resp.ID]; ok {
respChan <- resp
close(respChan)
delete(s.pendingServerRequests, *resp.ID)
}
s.pendingServerRequestsMu.Unlock()
} else {
req := msg.AsRequest()
if req.Method == lsproto.MethodCancelRequest {
s.cancelRequest(req.Params.(*lsproto.CancelParams).Id)
} else {
s.requestQueue <- req
}
}
}
}
func (s *Server) cancelRequest(rawID lsproto.IntegerOrString) {
id := lsproto.NewID(rawID)
s.pendingClientRequestsMu.Lock()
defer s.pendingClientRequestsMu.Unlock()
if pendingReq, ok := s.pendingClientRequests[*id]; ok {
pendingReq.cancel()
delete(s.pendingClientRequests, *id)
}
}
func (s *Server) read() (*lsproto.Message, error) {
return s.r.Read()
}
func (s *Server) dispatchLoop(ctx context.Context) error {
ctx, lspExit := context.WithCancel(ctx)
defer lspExit()
for {
select {
case <-ctx.Done():
return ctx.Err()
case req := <-s.requestQueue:
requestCtx := core.WithLocale(ctx, s.locale)
if req.ID != nil {
var cancel context.CancelFunc
requestCtx, cancel = context.WithCancel(core.WithRequestID(requestCtx, req.ID.String()))
s.pendingClientRequestsMu.Lock()
s.pendingClientRequests[*req.ID] = pendingClientRequest{
req: req,
cancel: cancel,
}
s.pendingClientRequestsMu.Unlock()
}
handle := func() {
if err := s.handleRequestOrNotification(requestCtx, req); err != nil {
if errors.Is(err, context.Canceled) {
s.sendError(req.ID, lsproto.ErrRequestCancelled)
} else if errors.Is(err, io.EOF) {
lspExit()
} else {
s.sendError(req.ID, err)
}
}
if req.ID != nil {
s.pendingClientRequestsMu.Lock()
delete(s.pendingClientRequests, *req.ID)
s.pendingClientRequestsMu.Unlock()
}
}
if isBlockingMethod(req.Method) {
handle()
} else {
go handle()
}
}
}
}
func (s *Server) writeLoop(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case msg := <-s.outgoingQueue:
if err := s.w.Write(msg); err != nil {
return fmt.Errorf("failed to write message: %w", err)
}
}
}
}
func (s *Server) sendRequest(ctx context.Context, method lsproto.Method, params any) (any, error) {
id := lsproto.NewIDString(fmt.Sprintf("ts%d", s.clientSeq.Add(1)))
req := lsproto.NewRequestMessage(method, id, params)
responseChan := make(chan *lsproto.ResponseMessage, 1)
s.pendingServerRequestsMu.Lock()
s.pendingServerRequests[*id] = responseChan
s.pendingServerRequestsMu.Unlock()
s.outgoingQueue <- req.Message()
select {
case <-ctx.Done():
s.pendingServerRequestsMu.Lock()
defer s.pendingServerRequestsMu.Unlock()
if respChan, ok := s.pendingServerRequests[*id]; ok {
close(respChan)
delete(s.pendingServerRequests, *id)
}
return nil, ctx.Err()
case resp := <-responseChan:
if resp.Error != nil {
return nil, fmt.Errorf("request failed: %s", resp.Error.String())
}
return resp.Result, nil
}
}
func (s *Server) sendResult(id *lsproto.ID, result any) {
s.sendResponse(&lsproto.ResponseMessage{
ID: id,
Result: result,
})
}
func (s *Server) sendError(id *lsproto.ID, err error) {
code := lsproto.ErrInternalError.Code
if errCode := (*lsproto.ErrorCode)(nil); errors.As(err, &errCode) {
code = errCode.Code
}
// TODO(jakebailey): error data
s.sendResponse(&lsproto.ResponseMessage{
ID: id,
Error: &lsproto.ResponseError{
Code: code,
Message: err.Error(),
},
})
}
func (s *Server) sendResponse(resp *lsproto.ResponseMessage) {
s.outgoingQueue <- resp.Message()
}
func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.RequestMessage) error {
if handler := handlers()[req.Method]; handler != nil {
return handler(s, ctx, req)
}
s.Log("unknown method", req.Method)
if req.ID != nil {
s.sendError(req.ID, lsproto.ErrInvalidRequest)
}
return nil
}
type handlerMap map[lsproto.Method]func(*Server, context.Context, *lsproto.RequestMessage) error
var handlers = sync.OnceValue(func() handlerMap {
handlers := make(handlerMap)
registerRequestHandler(handlers, lsproto.InitializeInfo, (*Server).handleInitialize)
registerNotificationHandler(handlers, lsproto.InitializedInfo, (*Server).handleInitialized)
registerRequestHandler(handlers, lsproto.ShutdownInfo, (*Server).handleShutdown)
registerNotificationHandler(handlers, lsproto.ExitInfo, (*Server).handleExit)
registerNotificationHandler(handlers, lsproto.TextDocumentDidOpenInfo, (*Server).handleDidOpen)
registerNotificationHandler(handlers, lsproto.TextDocumentDidChangeInfo, (*Server).handleDidChange)
registerNotificationHandler(handlers, lsproto.TextDocumentDidSaveInfo, (*Server).handleDidSave)
registerNotificationHandler(handlers, lsproto.TextDocumentDidCloseInfo, (*Server).handleDidClose)
registerNotificationHandler(handlers, lsproto.WorkspaceDidChangeWatchedFilesInfo, (*Server).handleDidChangeWatchedFiles)
registerNotificationHandler(handlers, lsproto.SetTraceInfo, (*Server).handleSetTrace)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDiagnosticInfo, (*Server).handleDocumentDiagnostic)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentHoverInfo, (*Server).handleHover)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDefinitionInfo, (*Server).handleDefinition)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentTypeDefinitionInfo, (*Server).handleTypeDefinition)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCompletionInfo, (*Server).handleCompletion)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentReferencesInfo, (*Server).handleReferences)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentImplementationInfo, (*Server).handleImplementations)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSignatureHelpInfo, (*Server).handleSignatureHelp)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFormattingInfo, (*Server).handleDocumentFormat)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRangeFormattingInfo, (*Server).handleDocumentRangeFormat)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentOnTypeFormattingInfo, (*Server).handleDocumentOnTypeFormat)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentSymbolInfo, (*Server).handleDocumentSymbol)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*Server).handleRename)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentHighlightInfo, (*Server).handleDocumentHighlight)
registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol)
registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve)
return handlers
})
func registerNotificationHandler[Req any](handlers handlerMap, info lsproto.NotificationInfo[Req], fn func(*Server, context.Context, Req) error) {
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
if s.session == nil && req.Method != lsproto.MethodInitialized {
return lsproto.ErrServerNotInitialized
}
var params Req
// Ignore empty params; all generated params are either pointers or any.
if req.Params != nil {
params = req.Params.(Req)
}
if err := fn(s, ctx, params); err != nil {
return err
}
return ctx.Err()
}
}
func registerRequestHandler[Req, Resp any](
handlers handlerMap,
info lsproto.RequestInfo[Req, Resp],
fn func(*Server, context.Context, Req, *lsproto.RequestMessage) (Resp, error),
) {
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
if s.session == nil && req.Method != lsproto.MethodInitialize {
return lsproto.ErrServerNotInitialized
}
var params Req
// Ignore empty params.
if req.Params != nil {
params = req.Params.(Req)
}
resp, err := fn(s, ctx, params, req)
if err != nil {
return err
}
if ctx.Err() != nil {
return ctx.Err()
}
s.sendResult(req.ID, resp)
return nil
}
}
func registerLanguageServiceDocumentRequestHandler[Req lsproto.HasTextDocumentURI, Resp any](handlers handlerMap, info lsproto.RequestInfo[Req, Resp], fn func(*Server, context.Context, *ls.LanguageService, Req) (Resp, error)) {
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
var params Req
// Ignore empty params.
if req.Params != nil {
params = req.Params.(Req)
}
ls, err := s.session.GetLanguageService(ctx, params.TextDocumentURI())
if err != nil {
return err
}
defer s.recover(req)
resp, err := fn(s, ctx, ls, params)
if err != nil {
return err
}
if ctx.Err() != nil {
return ctx.Err()
}
s.sendResult(req.ID, resp)
return nil
}
}
func (s *Server) recover(req *lsproto.RequestMessage) {
if r := recover(); r != nil {
stack := debug.Stack()
s.Log("panic handling request", req.Method, r, string(stack))
if req.ID != nil {
s.sendError(req.ID, fmt.Errorf("%w: panic handling request %s: %v", lsproto.ErrInternalError, req.Method, r))
} else {
s.Log("unhandled panic in notification", req.Method, r)
}
}
}
func (s *Server) handleInitialize(ctx context.Context, params *lsproto.InitializeParams, _ *lsproto.RequestMessage) (lsproto.InitializeResponse, error) {
if s.initializeParams != nil {
return nil, lsproto.ErrInvalidRequest
}
s.initializeParams = params
s.positionEncoding = lsproto.PositionEncodingKindUTF16
if genCapabilities := s.initializeParams.Capabilities.General; genCapabilities != nil && genCapabilities.PositionEncodings != nil {
if slices.Contains(*genCapabilities.PositionEncodings, lsproto.PositionEncodingKindUTF8) {
s.positionEncoding = lsproto.PositionEncodingKindUTF8
}
}
if s.initializeParams.Locale != nil {
locale, err := language.Parse(*s.initializeParams.Locale)
if err != nil {
return nil, err
}
s.locale = locale
}
if s.initializeParams.Trace != nil && *s.initializeParams.Trace == "verbose" {
s.logger.SetVerbose(true)
}
response := &lsproto.InitializeResult{
ServerInfo: &lsproto.ServerInfo{
Name: "typescript-go",
Version: ptrTo(core.Version()),
},
Capabilities: &lsproto.ServerCapabilities{
PositionEncoding: ptrTo(s.positionEncoding),
TextDocumentSync: &lsproto.TextDocumentSyncOptionsOrKind{
Options: &lsproto.TextDocumentSyncOptions{
OpenClose: ptrTo(true),
Change: ptrTo(lsproto.TextDocumentSyncKindIncremental),
Save: &lsproto.BooleanOrSaveOptions{
Boolean: ptrTo(true),
},
},
},
HoverProvider: &lsproto.BooleanOrHoverOptions{
Boolean: ptrTo(true),
},
DefinitionProvider: &lsproto.BooleanOrDefinitionOptions{
Boolean: ptrTo(true),
},
TypeDefinitionProvider: &lsproto.BooleanOrTypeDefinitionOptionsOrTypeDefinitionRegistrationOptions{
Boolean: ptrTo(true),
},
ReferencesProvider: &lsproto.BooleanOrReferenceOptions{
Boolean: ptrTo(true),
},
ImplementationProvider: &lsproto.BooleanOrImplementationOptionsOrImplementationRegistrationOptions{
Boolean: ptrTo(true),
},
DiagnosticProvider: &lsproto.DiagnosticOptionsOrRegistrationOptions{
Options: &lsproto.DiagnosticOptions{
InterFileDependencies: true,
},
},
CompletionProvider: &lsproto.CompletionOptions{
TriggerCharacters: &ls.TriggerCharacters,
ResolveProvider: ptrTo(true),
// !!! other options
},
SignatureHelpProvider: &lsproto.SignatureHelpOptions{
TriggerCharacters: &[]string{"(", ","},
},
DocumentFormattingProvider: &lsproto.BooleanOrDocumentFormattingOptions{
Boolean: ptrTo(true),
},
DocumentRangeFormattingProvider: &lsproto.BooleanOrDocumentRangeFormattingOptions{
Boolean: ptrTo(true),
},
DocumentOnTypeFormattingProvider: &lsproto.DocumentOnTypeFormattingOptions{
FirstTriggerCharacter: "{",
MoreTriggerCharacter: &[]string{"}", ";", "\n"},
},
WorkspaceSymbolProvider: &lsproto.BooleanOrWorkspaceSymbolOptions{
Boolean: ptrTo(true),
},
DocumentSymbolProvider: &lsproto.BooleanOrDocumentSymbolOptions{
Boolean: ptrTo(true),
},
RenameProvider: &lsproto.BooleanOrRenameOptions{
Boolean: ptrTo(true),
},
DocumentHighlightProvider: &lsproto.BooleanOrDocumentHighlightOptions{
Boolean: ptrTo(true),
},
},
}
return response, nil
}
func (s *Server) handleInitialized(ctx context.Context, params *lsproto.InitializedParams) error {
if shouldEnableWatch(s.initializeParams) {
s.watchEnabled = true
}
cwd := s.cwd
if s.initializeParams.Capabilities != nil &&
s.initializeParams.Capabilities.Workspace != nil &&
s.initializeParams.Capabilities.Workspace.WorkspaceFolders != nil &&
ptrIsTrue(s.initializeParams.Capabilities.Workspace.WorkspaceFolders) &&
s.initializeParams.WorkspaceFolders != nil &&
s.initializeParams.WorkspaceFolders.WorkspaceFolders != nil &&
len(*s.initializeParams.WorkspaceFolders.WorkspaceFolders) == 1 {
cwd = lsproto.DocumentUri((*s.initializeParams.WorkspaceFolders.WorkspaceFolders)[0].Uri).FileName()
} else if s.initializeParams.RootUri.DocumentUri != nil {
cwd = s.initializeParams.RootUri.DocumentUri.FileName()
} else if s.initializeParams.RootPath != nil && s.initializeParams.RootPath.String != nil {
cwd = *s.initializeParams.RootPath.String
}
if !tspath.PathIsAbsolute(cwd) {
cwd = s.cwd
}
s.session = project.NewSession(&project.SessionInit{
Options: &project.SessionOptions{
CurrentDirectory: cwd,
DefaultLibraryPath: s.defaultLibraryPath,
TypingsLocation: s.typingsLocation,
PositionEncoding: s.positionEncoding,
WatchEnabled: s.watchEnabled,
LoggingEnabled: true,
DebounceDelay: 500 * time.Millisecond,
},
FS: s.fs,
Logger: s.logger,
Client: s,
NpmExecutor: s,
ParseCache: s.parseCache,
})
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
if s.compilerOptionsForInferredProjects != nil {
s.session.DidChangeCompilerOptionsForInferredProjects(ctx, s.compilerOptionsForInferredProjects)
}
return nil
}
func (s *Server) handleShutdown(ctx context.Context, params any, _ *lsproto.RequestMessage) (lsproto.ShutdownResponse, error) {
s.session.Close()
return lsproto.ShutdownResponse{}, nil
}
func (s *Server) handleExit(ctx context.Context, params any) error {
return io.EOF
}
func (s *Server) handleDidOpen(ctx context.Context, params *lsproto.DidOpenTextDocumentParams) error {
s.session.DidOpenFile(ctx, params.TextDocument.Uri, params.TextDocument.Version, params.TextDocument.Text, params.TextDocument.LanguageId)
return nil
}
func (s *Server) handleDidChange(ctx context.Context, params *lsproto.DidChangeTextDocumentParams) error {
s.session.DidChangeFile(ctx, params.TextDocument.Uri, params.TextDocument.Version, params.ContentChanges)
return nil
}
func (s *Server) handleDidSave(ctx context.Context, params *lsproto.DidSaveTextDocumentParams) error {
s.session.DidSaveFile(ctx, params.TextDocument.Uri)
return nil
}
func (s *Server) handleDidClose(ctx context.Context, params *lsproto.DidCloseTextDocumentParams) error {
s.session.DidCloseFile(ctx, params.TextDocument.Uri)
return nil
}
func (s *Server) handleDidChangeWatchedFiles(ctx context.Context, params *lsproto.DidChangeWatchedFilesParams) error {
s.session.DidChangeWatchedFiles(ctx, params.Changes)
return nil
}
func (s *Server) handleSetTrace(ctx context.Context, params *lsproto.SetTraceParams) error {
switch params.Value {
case "verbose":
s.logger.SetVerbose(true)
case "messages":
s.logger.SetVerbose(false)
case "off":
// !!! logging cannot be completely turned off for now
s.logger.SetVerbose(false)
default:
return fmt.Errorf("unknown trace value: %s", params.Value)
}
return nil
}
func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) {
return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri)
}
func (s *Server) handleHover(ctx context.Context, ls *ls.LanguageService, params *lsproto.HoverParams) (lsproto.HoverResponse, error) {
return ls.ProvideHover(ctx, params.TextDocument.Uri, params.Position)
}
func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.LanguageService, params *lsproto.SignatureHelpParams) (lsproto.SignatureHelpResponse, error) {
return languageService.ProvideSignatureHelp(
ctx,
params.TextDocument.Uri,
params.Position,
params.Context,
s.initializeParams.Capabilities.TextDocument.SignatureHelp,
&ls.UserPreferences{},
)
}
func (s *Server) handleDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.DefinitionParams) (lsproto.DefinitionResponse, error) {
return ls.ProvideDefinition(ctx, params.TextDocument.Uri, params.Position)
}
func (s *Server) handleTypeDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.TypeDefinitionParams) (lsproto.TypeDefinitionResponse, error) {
return ls.ProvideTypeDefinition(ctx, params.TextDocument.Uri, params.Position)
}
func (s *Server) handleReferences(ctx context.Context, ls *ls.LanguageService, params *lsproto.ReferenceParams) (lsproto.ReferencesResponse, error) {
// findAllReferences
return ls.ProvideReferences(ctx, params)
}
func (s *Server) handleImplementations(ctx context.Context, ls *ls.LanguageService, params *lsproto.ImplementationParams) (lsproto.ImplementationResponse, error) {
// goToImplementation
return ls.ProvideImplementations(ctx, params)
}
func (s *Server) handleCompletion(ctx context.Context, languageService *ls.LanguageService, params *lsproto.CompletionParams) (lsproto.CompletionResponse, error) {
// !!! get user preferences
return languageService.ProvideCompletion(
ctx,
params.TextDocument.Uri,
params.Position,
params.Context,
getCompletionClientCapabilities(s.initializeParams),
&ls.UserPreferences{
IncludeCompletionsForModuleExports: core.TSTrue,
IncludeCompletionsForImportStatements: core.TSTrue,
})
}
func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsproto.CompletionItem, reqMsg *lsproto.RequestMessage) (lsproto.CompletionResolveResponse, error) {
data, err := ls.GetCompletionItemData(params)
if err != nil {
return nil, err
}
languageService, err := s.session.GetLanguageService(ctx, ls.FileNameToDocumentURI(data.FileName))
if err != nil {
return nil, err
}
defer s.recover(reqMsg)
return languageService.ResolveCompletionItem(
ctx,
params,
data,
getCompletionClientCapabilities(s.initializeParams),
&ls.UserPreferences{
IncludeCompletionsForModuleExports: core.TSTrue,
IncludeCompletionsForImportStatements: core.TSTrue,
},
)
}
func (s *Server) handleDocumentFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentFormattingParams) (lsproto.DocumentFormattingResponse, error) {
return ls.ProvideFormatDocument(
ctx,
params.TextDocument.Uri,
params.Options,
)
}
func (s *Server) handleDocumentRangeFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentRangeFormattingParams) (lsproto.DocumentRangeFormattingResponse, error) {
return ls.ProvideFormatDocumentRange(
ctx,
params.TextDocument.Uri,
params.Options,
params.Range,
)
}
func (s *Server) handleDocumentOnTypeFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentOnTypeFormattingParams) (lsproto.DocumentOnTypeFormattingResponse, error) {
return ls.ProvideFormatDocumentOnType(
ctx,
params.TextDocument.Uri,
params.Options,
params.Position,
params.Ch,
)
}
func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.WorkspaceSymbolParams, reqMsg *lsproto.RequestMessage) (lsproto.WorkspaceSymbolResponse, error) {
snapshot, release := s.session.Snapshot()
defer release()
defer s.recover(reqMsg)
programs := core.Map(snapshot.ProjectCollection.Projects(), (*project.Project).GetProgram)
return ls.ProvideWorkspaceSymbols(ctx, programs, snapshot.Converters(), params.Query)
}
func (s *Server) handleDocumentSymbol(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentSymbolParams) (lsproto.DocumentSymbolResponse, error) {
return ls.ProvideDocumentSymbols(ctx, params.TextDocument.Uri)
}
func (s *Server) handleRename(ctx context.Context, ls *ls.LanguageService, params *lsproto.RenameParams) (lsproto.RenameResponse, error) {
return ls.ProvideRename(ctx, params)
}
func (s *Server) handleDocumentHighlight(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentHighlightParams) (lsproto.DocumentHighlightResponse, error) {
return ls.ProvideDocumentHighlights(ctx, params.TextDocument.Uri, params.Position)
}
func (s *Server) Log(msg ...any) {
fmt.Fprintln(s.stderr, msg...)
}
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
func (s *Server) SetCompilerOptionsForInferredProjects(ctx context.Context, options *core.CompilerOptions) {
s.compilerOptionsForInferredProjects = options
if s.session != nil {
s.session.DidChangeCompilerOptionsForInferredProjects(ctx, options)
}
}
// NpmInstall implements ata.NpmExecutor
func (s *Server) NpmInstall(cwd string, args []string) ([]byte, error) {
cmd := exec.Command("npm", args...)
cmd.Dir = cwd
return cmd.Output()
}
func isBlockingMethod(method lsproto.Method) bool {
switch method {
case lsproto.MethodInitialize,
lsproto.MethodInitialized,
lsproto.MethodTextDocumentDidOpen,
lsproto.MethodTextDocumentDidChange,
lsproto.MethodTextDocumentDidSave,
lsproto.MethodTextDocumentDidClose,
lsproto.MethodWorkspaceDidChangeWatchedFiles:
return true
}
return false
}
func ptrTo[T any](v T) *T {
return &v
}
func ptrIsTrue(v *bool) bool {
if v == nil {
return false
}
return *v
}
func shouldEnableWatch(params *lsproto.InitializeParams) bool {
if params == nil || params.Capabilities == nil || params.Capabilities.Workspace == nil {
return false
}
return params.Capabilities.Workspace.DidChangeWatchedFiles != nil &&
ptrIsTrue(params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration)
}
func getCompletionClientCapabilities(params *lsproto.InitializeParams) *lsproto.CompletionClientCapabilities {
if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil {
return nil
}
return params.Capabilities.TextDocument.Completion
}