internal/lsp/server.go (1,233 lines of code) (raw):
// MODIFIED IN THIS FORK: 2025-05-27 Piotr Tomiak 1bf5d43a77c728148f273b555d20220cc5ac6ef8 Ensure that a broken handle method does not prevent server from functioning.
// MODIFIED IN THIS FORK: 2025-05-21 Piotr Tomiak dfd337df3e64d6fb2bc18c46bf4de05bcbea5a64 Support WebStorm types in LSP mode
package lsp
import (
"context"
"errors"
"fmt"
"io"
"iter"
"runtime/debug"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/go-json-experiment/json"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/jsonutil"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/ls/lsconv"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/project"
"github.com/microsoft/typescript-go/internal/project/ata"
"github.com/microsoft/typescript-go/internal/project/logging"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"golang.org/x/sync/errgroup"
)
type ServerOptions struct {
In Reader
Out Writer
Err io.Writer
Cwd string
FS vfs.FS
DefaultLibraryPath string
TypingsLocation string
ParseCache *project.ParseCache
NpmInstall func(cwd string, args []string) ([]byte, error)
// Test options
Client project.Client
Logger logging.Logger
}
func NewServer(opts *ServerOptions) *Server {
if opts.Cwd == "" {
panic("Cwd is required")
}
var logger logging.Logger
if opts.Logger != nil {
logger = opts.Logger
} else {
logger = logging.NewLogger(opts.Err)
}
return &Server{
r: opts.In,
w: opts.Out,
stderr: opts.Err,
logger: logger,
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,
npmInstall: opts.NpmInstall,
client: opts.Client,
}
}
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.ErrorCodeInvalidRequest, 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
clientCapabilities lsproto.ResolvedClientCapabilities
positionEncoding lsproto.PositionEncodingKind
locale locale.Locale
watchEnabled bool
watcherID atomic.Uint32
watchers collections.SyncSet[project.WatcherID]
session *project.Session
// Test options for initializing session
client project.Client
// !!! 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
npmInstall func(cwd string, args []string) ([]byte, error)
}
func (s *Server) Session() *project.Session { return s.session }
// WatchFiles implements project.Client.
func (s *Server) WatchFiles(ctx context.Context, id project.WatcherID, watchers []*lsproto.FileSystemWatcher) error {
_, err := sendClientRequest(ctx, s, lsproto.ClientRegisterCapabilityInfo, &lsproto.RegistrationParams{
Registrations: []*lsproto.Registration{
{
Id: string(id),
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
RegisterOptions: &lsproto.RegisterOptions{
DidChangeWatchedFiles: &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 := sendClientRequest(ctx, s, lsproto.ClientUnregisterCapabilityInfo, &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.clientCapabilities.Workspace.Diagnostics.RefreshSupport {
return nil
}
if _, err := sendClientRequest(ctx, s, lsproto.WorkspaceDiagnosticRefreshInfo, nil); err != nil {
return fmt.Errorf("failed to refresh diagnostics: %w", err)
}
return nil
}
// PublishDiagnostics implements project.Client.
func (s *Server) PublishDiagnostics(ctx context.Context, params *lsproto.PublishDiagnosticsParams) error {
notification := lsproto.TextDocumentPublishDiagnosticsInfo.NewNotificationMessage(params)
s.outgoingQueue <- notification.Message()
return nil
}
func (s *Server) RefreshInlayHints(ctx context.Context) error {
if !s.clientCapabilities.Workspace.InlayHint.RefreshSupport {
return nil
}
if _, err := sendClientRequest(ctx, s, lsproto.WorkspaceInlayHintRefreshInfo, nil); err != nil {
return fmt.Errorf("failed to refresh inlay hints: %w", err)
}
return nil
}
func (s *Server) RefreshCodeLens(ctx context.Context) error {
if !s.clientCapabilities.Workspace.CodeLens.RefreshSupport {
return nil
}
if _, err := sendClientRequest(ctx, s, lsproto.WorkspaceCodeLensRefreshInfo, nil); err != nil {
return fmt.Errorf("failed to refresh code lens: %w", err)
}
return nil
}
func (s *Server) RequestConfiguration(ctx context.Context) (*lsutil.UserPreferences, error) {
caps := lsproto.GetClientCapabilities(ctx)
if !caps.Workspace.Configuration {
// if no configuration request capapbility, return default preferences
return s.session.NewUserPreferences(), nil
}
configs, err := sendClientRequest(ctx, s, lsproto.WorkspaceConfigurationInfo, &lsproto.ConfigurationParams{
Items: []*lsproto.ConfigurationItem{
{
Section: ptrTo("typescript"),
},
},
})
if err != nil {
return nil, fmt.Errorf("configure request failed: %w", err)
}
s.Log(fmt.Sprintf("\n\nconfiguration: %+v, %T\n\n", configs, configs))
userPreferences := s.session.NewUserPreferences()
for _, item := range configs {
if parsed := userPreferences.Parse(item); parsed != nil {
return parsed, nil
}
}
return userPreferences, nil
}
func (s *Server) Run(ctx context.Context) error {
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.ErrorCodeInvalidRequest) {
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.ErrorCodeServerNotInitialized)
}
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 := locale.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.ErrorCodeRequestCancelled)
} 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 sendClientRequest[Req, Resp any](ctx context.Context, s *Server, info lsproto.RequestInfo[Req, Resp], params Req) (Resp, error) {
id := lsproto.NewIDString(fmt.Sprintf("ts%d", s.clientSeq.Add(1)))
req := info.NewRequestMessage(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 *new(Resp), ctx.Err()
case resp := <-responseChan:
if resp.Error != nil {
return *new(Resp), fmt.Errorf("request failed: %s", resp.Error.String())
}
return info.UnmarshalResult(resp.Result)
}
}
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.ErrorCodeInternalError
if errCode := lsproto.ErrorCode(0); errors.As(err, &errCode) {
code = errCode
}
// TODO(jakebailey): error data
s.sendResponse(&lsproto.ResponseMessage{
ID: id,
Error: &lsproto.ResponseError{
Code: int32(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 {
ctx = lsproto.WithClientCapabilities(ctx, &s.clientCapabilities)
switch req.Params.(type) {
case *lsproto.JbHandleCustomTsServerCommandParams:
return s.jbHandleCustomTsServerCommand(ctx, req)
}
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.ErrorCodeInvalidRequest)
}
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.WorkspaceDidChangeConfigurationInfo, (*Server).handleDidChangeWorkspaceConfiguration)
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.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.TextDocumentDocumentHighlightInfo, (*Server).handleDocumentHighlight)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSelectionRangeInfo, (*Server).handleSelectionRange)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentInlayHintInfo, (*Server).handleInlayHint)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeLensInfo, (*Server).handleCodeLens)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeActionInfo, (*Server).handleCodeAction)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentPrepareCallHierarchyInfo, (*Server).handlePrepareCallHierarchy)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFoldingRangeInfo, (*Server).handleFoldingRange)
registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentReferencesInfo, (*Server).handleReferences, combineReferences)
registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*Server).handleRename, combineRenameResponse)
registerRequestHandler(handlers, lsproto.CallHierarchyIncomingCallsInfo, (*Server).handleCallHierarchyIncomingCalls)
registerRequestHandler(handlers, lsproto.CallHierarchyOutgoingCallsInfo, (*Server).handleCallHierarchyOutgoingCalls)
registerRequestHandler(handlers, lsproto.CallHierarchyIncomingCallsInfo, (*Server).handleCallHierarchyIncomingCalls)
registerRequestHandler(handlers, lsproto.CallHierarchyOutgoingCallsInfo, (*Server).handleCallHierarchyOutgoingCalls)
registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol)
registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve)
registerRequestHandler(handlers, lsproto.CodeLensResolveInfo, (*Server).handleCodeLensResolve)
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.ErrorCodeServerNotInitialized
}
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.ErrorCodeServerNotInitialized
}
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
}
}
type projectAndTextDocumentPosition struct {
project *project.Project
ls *ls.LanguageService
Uri lsproto.DocumentUri
Position lsproto.Position
forOriginalLocation bool
}
type response[Resp any] struct {
complete bool
result Resp
forOriginalLocation bool
}
func registerMultiProjectReferenceRequestHandler[Req lsproto.HasTextDocumentPosition, Resp any](
handlers handlerMap,
info lsproto.RequestInfo[Req, Resp],
fn func(*Server, context.Context, *ls.LanguageService, Req, *ast.Node, []*ls.SymbolAndEntries) (Resp, error),
combineResults func(iter.Seq[Resp]) Resp,
) {
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)
}
// !!! sheetal: multiple projects that contain the file through symlinks
defaultProject, defaultLs, allProjects, err := s.session.GetLanguageServiceAndProjectsForFile(ctx, params.TextDocumentURI())
if err != nil {
return err
}
defer s.recover(req)
var results collections.SyncMap[tspath.Path, *response[Resp]]
var defaultDefinition *ls.NonLocalDefinition
canSearchProject := func(project *project.Project) bool {
_, searched := results.Load(project.Id())
return !searched
}
wg := core.NewWorkGroup(false)
var errMu sync.Mutex
var enqueueItem func(item projectAndTextDocumentPosition)
enqueueItem = func(item projectAndTextDocumentPosition) {
var response response[Resp]
if _, loaded := results.LoadOrStore(item.project.Id(), &response); loaded {
return
}
wg.Queue(func() {
if ctx.Err() != nil {
return
}
defer s.recover(req)
// Process the item
ls := item.ls
if ls == nil {
// Get it now
ls = s.session.GetLanguageServiceForProjectWithFile(ctx, item.project, item.Uri)
if ls == nil {
return
}
}
originalNode, symbolsAndEntries, ok := ls.ProvideSymbolsAndEntries(ctx, item.Uri, item.Position, info.Method == lsproto.MethodTextDocumentRename)
if ok {
for _, entry := range symbolsAndEntries {
// Find the default definition that can be in another project
// Later we will use this load ancestor tree that references this location and expand search
if item.project == defaultProject && defaultDefinition == nil {
defaultDefinition = ls.GetNonLocalDefinition(ctx, entry)
}
ls.ForEachOriginalDefinitionLocation(ctx, entry, func(uri lsproto.DocumentUri, position lsproto.Position) {
// Get default configured project for this file
defProjects, errProjects := s.session.GetProjectsForFile(ctx, uri)
if errProjects != nil {
return
}
for _, defProject := range defProjects {
// Optimization: don't enqueue if will be discarded
if canSearchProject(defProject) {
enqueueItem(projectAndTextDocumentPosition{
project: defProject,
Uri: uri,
Position: position,
forOriginalLocation: true,
})
}
}
})
}
}
if result, errSearch := fn(s, ctx, ls, params, originalNode, symbolsAndEntries); errSearch == nil {
response.complete = true
response.result = result
response.forOriginalLocation = item.forOriginalLocation
} else {
errMu.Lock()
defer errMu.Unlock()
if err != nil {
err = errSearch
}
}
})
}
// Initial set of projects and locations in the queue, starting with default project
enqueueItem(projectAndTextDocumentPosition{
project: defaultProject,
ls: defaultLs,
Uri: params.TextDocumentURI(),
Position: params.TextDocumentPosition(),
})
for _, project := range allProjects {
if project != defaultProject {
enqueueItem(projectAndTextDocumentPosition{
project: project,
// TODO!! symlinks need to change the URI
Uri: params.TextDocumentURI(),
Position: params.TextDocumentPosition(),
})
}
}
getResultsIterator := func() iter.Seq[Resp] {
return func(yield func(Resp) bool) {
var seenProjects collections.SyncSet[tspath.Path]
if response, loaded := results.Load(defaultProject.Id()); loaded && response.complete {
if !yield(response.result) {
return
}
}
seenProjects.Add(defaultProject.Id())
for _, project := range allProjects {
if seenProjects.AddIfAbsent(project.Id()) {
if response, loaded := results.Load(project.Id()); loaded && response.complete {
if !yield(response.result) {
return
}
}
}
}
// Prefer the searches from locations for default definition
results.Range(func(key tspath.Path, response *response[Resp]) bool {
if !response.forOriginalLocation && seenProjects.AddIfAbsent(key) && response.complete {
return yield(response.result)
}
return true
})
// Then the searches from original locations
results.Range(func(key tspath.Path, response *response[Resp]) bool {
if response.forOriginalLocation && seenProjects.AddIfAbsent(key) && response.complete {
return yield(response.result)
}
return true
})
}
}
// Outer loop - to complete work if more is added after completing existing queue
for {
// Process existing known projects first
wg.RunAndWait()
if ctx.Err() != nil {
return ctx.Err()
}
// No need to use mu here since we are not in parallel at this point
if err != nil {
return err
}
wg = core.NewWorkGroup(false)
hasMoreWork := false
if defaultDefinition != nil {
requestedProjectTrees := make(map[tspath.Path]struct{})
results.Range(func(key tspath.Path, response *response[Resp]) bool {
if response.complete {
requestedProjectTrees[key] = struct{}{}
}
return true
})
// Load more projects based on default definition found
for _, loadedProject := range s.session.GetSnapshotLoadingProjectTree(ctx, requestedProjectTrees).ProjectCollection.Projects() {
if ctx.Err() != nil {
return ctx.Err()
}
// Can loop forever without this (enqueue here, dequeue above, repeat)
if !canSearchProject(loadedProject) || loadedProject.GetProgram() == nil {
continue
}
// Enqueue the project and location for further processing
if loadedProject.HasFile(defaultDefinition.TextDocumentURI().FileName()) {
enqueueItem(projectAndTextDocumentPosition{
project: loadedProject,
Uri: defaultDefinition.TextDocumentURI(),
Position: defaultDefinition.TextDocumentPosition(),
})
hasMoreWork = true
} else if sourcePos := defaultDefinition.GetSourcePosition(); sourcePos != nil && loadedProject.HasFile(sourcePos.TextDocumentURI().FileName()) {
enqueueItem(projectAndTextDocumentPosition{
project: loadedProject,
Uri: sourcePos.TextDocumentURI(),
Position: sourcePos.TextDocumentPosition(),
})
hasMoreWork = true
} else if generatedPos := defaultDefinition.GetGeneratedPosition(); generatedPos != nil && loadedProject.HasFile(generatedPos.TextDocumentURI().FileName()) {
enqueueItem(projectAndTextDocumentPosition{
project: loadedProject,
Uri: generatedPos.TextDocumentURI(),
Position: generatedPos.TextDocumentPosition(),
})
hasMoreWork = true
}
}
}
if !hasMoreWork {
break
}
}
var resp Resp
if results.Size() > 1 {
resp = combineResults(getResultsIterator())
} else {
// Single result, return that directly
for value := range getResultsIterator() {
resp = value
break
}
}
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.ErrorCodeInternalError, 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.ErrorCodeInvalidRequest
}
s.initializeParams = params
s.clientCapabilities = resolveClientCapabilities(params.Capabilities)
if _, err := fmt.Fprint(s.stderr, "Resolved client capabilities: "); err != nil {
return nil, err
}
if err := jsonutil.MarshalIndentWrite(s.stderr, &s.clientCapabilities, "", "\t"); err != nil {
return nil, err
}
s.positionEncoding = lsproto.PositionEncodingKindUTF16
if slices.Contains(s.clientCapabilities.General.PositionEncodings, lsproto.PositionEncodingKindUTF8) {
s.positionEncoding = lsproto.PositionEncodingKindUTF8
}
if s.initializeParams.Locale != nil {
s.locale, _ = locale.Parse(*s.initializeParams.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),
},
FoldingRangeProvider: &lsproto.BooleanOrFoldingRangeOptionsOrFoldingRangeRegistrationOptions{
Boolean: ptrTo(true),
},
RenameProvider: &lsproto.BooleanOrRenameOptions{
Boolean: ptrTo(true),
},
DocumentHighlightProvider: &lsproto.BooleanOrDocumentHighlightOptions{
Boolean: ptrTo(true),
},
SelectionRangeProvider: &lsproto.BooleanOrSelectionRangeOptionsOrSelectionRangeRegistrationOptions{
Boolean: ptrTo(true),
},
InlayHintProvider: &lsproto.BooleanOrInlayHintOptionsOrInlayHintRegistrationOptions{
Boolean: ptrTo(true),
},
CodeLensProvider: &lsproto.CodeLensOptions{
ResolveProvider: ptrTo(true),
},
CodeActionProvider: &lsproto.BooleanOrCodeActionOptions{
CodeActionOptions: &lsproto.CodeActionOptions{
CodeActionKinds: &[]lsproto.CodeActionKind{
lsproto.CodeActionKindQuickFix,
},
},
},
CallHierarchyProvider: &lsproto.BooleanOrCallHierarchyOptionsOrCallHierarchyRegistrationOptions{
Boolean: ptrTo(true),
},
},
}
return response, nil
}
func (s *Server) handleInitialized(ctx context.Context, params *lsproto.InitializedParams) error {
if s.clientCapabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration {
s.watchEnabled = true
}
cwd := s.cwd
if s.clientCapabilities.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
}
var disablePushDiagnostics bool
if s.initializeParams != nil && s.initializeParams.InitializationOptions != nil {
if s.initializeParams.InitializationOptions.DisablePushDiagnostics != nil {
disablePushDiagnostics = *s.initializeParams.InitializationOptions.DisablePushDiagnostics
}
}
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,
PushDiagnosticsEnabled: !disablePushDiagnostics,
Locale: s.locale,
},
FS: s.fs,
Logger: s.logger,
Client: s,
NpmExecutor: s,
ParseCache: s.parseCache,
})
userPreferences, err := s.RequestConfiguration(ctx)
if err != nil {
return err
}
s.session.InitializeWithConfig(userPreferences)
_, err = sendClientRequest(ctx, s, lsproto.ClientRegisterCapabilityInfo, &lsproto.RegistrationParams{
Registrations: []*lsproto.Registration{
{
Id: "typescript-config-watch-id",
Method: string(lsproto.MethodWorkspaceDidChangeConfiguration),
RegisterOptions: &lsproto.RegisterOptions{
DidChangeConfiguration: &lsproto.DidChangeConfigurationRegistrationOptions{
Section: &lsproto.StringOrStrings{
// !!! Both the 'javascript' and 'js/ts' scopes need to be watched for settings as well.
Strings: &[]string{"typescript"},
},
},
},
},
},
})
if err != nil {
return fmt.Errorf("failed to register configuration change watcher: %w", err)
}
// !!! temporary.
// Remove when we have `handleDidChangeConfiguration`/implicit project config support
// derived from 'js/ts.implicitProjectConfig.*'.
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) handleDidChangeWorkspaceConfiguration(ctx context.Context, params *lsproto.DidChangeConfigurationParams) error {
settings, ok := params.Settings.(map[string]any)
if !ok {
return nil
}
// !!! Both the 'javascript' and 'js/ts' scopes need to be checked for settings as well.
tsSettings := settings["typescript"]
userPreferences := s.session.UserPreferences()
if parsed := userPreferences.Parse(tsSettings); parsed != nil {
userPreferences = parsed
}
s.session.Configure(userPreferences)
return nil
}
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,
)
}
func (s *Server) handleFoldingRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.FoldingRangeParams) (lsproto.FoldingRangeResponse, error) {
return ls.ProvideFoldingRange(ctx, params.TextDocument.Uri)
}
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, originalNode *ast.Node, symbolAndEntries []*ls.SymbolAndEntries) (lsproto.ReferencesResponse, error) {
// findAllReferences
return ls.ProvideReferencesFromSymbolAndEntries(ctx, params, originalNode, symbolAndEntries)
}
func combineReferences(results iter.Seq[lsproto.ReferencesResponse]) lsproto.ReferencesResponse {
var combined []lsproto.Location
var seenLocations collections.Set[lsproto.Location]
for resp := range results {
if resp.Locations != nil {
for _, loc := range *resp.Locations {
if !seenLocations.Has(loc) {
seenLocations.Add(loc)
combined = append(combined, loc)
}
}
}
}
return lsproto.LocationsOrNull{Locations: &combined}
}
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) {
return languageService.ProvideCompletion(
ctx,
params.TextDocument.Uri,
params.Position,
params.Context,
)
}
func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsproto.CompletionItem, reqMsg *lsproto.RequestMessage) (lsproto.CompletionResolveResponse, error) {
data := params.Data
languageService, err := s.session.GetLanguageService(ctx, lsconv.FileNameToDocumentURI(data.FileName))
if err != nil {
return nil, err
}
defer s.recover(reqMsg)
return languageService.ResolveCompletionItem(
ctx,
params,
data,
)
}
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 := s.session.GetSnapshotLoadingProjectTree(ctx, nil)
defer s.recover(reqMsg)
programs := core.Map(snapshot.ProjectCollection.Projects(), (*project.Project).GetProgram)
return ls.ProvideWorkspaceSymbols(
ctx,
programs,
snapshot.Converters(),
snapshot.UserPreferences(),
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, originalNode *ast.Node, symbolAndEntries []*ls.SymbolAndEntries) (lsproto.RenameResponse, error) {
return ls.ProvideRenameFromSymbolAndEntries(ctx, params, originalNode, symbolAndEntries)
}
func combineRenameResponse(results iter.Seq[lsproto.RenameResponse]) lsproto.RenameResponse {
combined := make(map[lsproto.DocumentUri][]*lsproto.TextEdit)
seenChanges := make(map[lsproto.DocumentUri]*collections.Set[lsproto.Range])
// !!! this is not used any more so we will skip this part of deduplication and combining
// DocumentChanges *[]TextDocumentEditOrCreateFileOrRenameFileOrDeleteFile `json:"documentChanges,omitzero"`
// ChangeAnnotations *map[string]*ChangeAnnotation `json:"changeAnnotations,omitzero"`
for resp := range results {
if resp.WorkspaceEdit != nil && resp.WorkspaceEdit.Changes != nil {
for doc, changes := range *resp.WorkspaceEdit.Changes {
seenSet, ok := seenChanges[doc]
if !ok {
seenSet = &collections.Set[lsproto.Range]{}
seenChanges[doc] = seenSet
}
changesForDoc, exists := combined[doc]
if !exists {
changesForDoc = []*lsproto.TextEdit{}
}
for _, change := range changes {
if !seenSet.Has(change.Range) {
seenSet.Add(change.Range)
changesForDoc = append(changesForDoc, change)
}
}
combined[doc] = changesForDoc
}
}
}
if len(combined) > 0 {
return lsproto.RenameResponse{
WorkspaceEdit: &lsproto.WorkspaceEdit{
Changes: &combined,
},
}
}
return lsproto.RenameResponse{}
}
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) handleSelectionRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.SelectionRangeParams) (lsproto.SelectionRangeResponse, error) {
return ls.ProvideSelectionRanges(ctx, params)
}
func (s *Server) handleCodeAction(ctx context.Context, ls *ls.LanguageService, params *lsproto.CodeActionParams) (lsproto.CodeActionResponse, error) {
return ls.ProvideCodeActions(ctx, params)
}
func (s *Server) handleInlayHint(
ctx context.Context,
languageService *ls.LanguageService,
params *lsproto.InlayHintParams,
) (lsproto.InlayHintResponse, error) {
return languageService.ProvideInlayHint(ctx, params)
}
func (s *Server) handleCodeLens(ctx context.Context, ls *ls.LanguageService, params *lsproto.CodeLensParams) (lsproto.CodeLensResponse, error) {
return ls.ProvideCodeLenses(ctx, params.TextDocument.Uri)
}
func (s *Server) handleCodeLensResolve(ctx context.Context, codeLens *lsproto.CodeLens, reqMsg *lsproto.RequestMessage) (*lsproto.CodeLens, error) {
ls, err := s.session.GetLanguageService(ctx, codeLens.Data.Uri)
if err != nil {
return nil, err
}
defer s.recover(reqMsg)
return ls.ResolveCodeLens(ctx, codeLens, s.initializeParams.InitializationOptions.CodeLensShowLocationsCommandName)
}
func (s *Server) handlePrepareCallHierarchy(
ctx context.Context,
languageService *ls.LanguageService,
params *lsproto.CallHierarchyPrepareParams,
) (lsproto.CallHierarchyPrepareResponse, error) {
return languageService.ProvidePrepareCallHierarchy(ctx, params.TextDocument.Uri, params.Position)
}
func (s *Server) handleCallHierarchyIncomingCalls(
ctx context.Context,
params *lsproto.CallHierarchyIncomingCallsParams,
_ *lsproto.RequestMessage,
) (lsproto.CallHierarchyIncomingCallsResponse, error) {
languageService, err := s.session.GetLanguageService(ctx, params.Item.Uri)
if err != nil {
return lsproto.CallHierarchyIncomingCallsOrNull{}, err
}
return languageService.ProvideCallHierarchyIncomingCalls(ctx, params.Item)
}
func (s *Server) handleCallHierarchyOutgoingCalls(
ctx context.Context,
params *lsproto.CallHierarchyOutgoingCallsParams,
_ *lsproto.RequestMessage,
) (lsproto.CallHierarchyOutgoingCallsResponse, error) {
languageService, err := s.session.GetLanguageService(ctx, params.Item.Uri)
if err != nil {
return lsproto.CallHierarchyOutgoingCallsOrNull{}, err
}
return languageService.ProvideCallHierarchyOutgoingCalls(ctx, params.Item)
}
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) {
return s.npmInstall(cwd, args)
}
func isBlockingMethod(method lsproto.Method) bool {
switch method {
case lsproto.MethodInitialize,
lsproto.MethodInitialized,
lsproto.MethodTextDocumentDidOpen,
lsproto.MethodTextDocumentDidChange,
lsproto.MethodTextDocumentDidSave,
lsproto.MethodTextDocumentDidClose,
lsproto.MethodWorkspaceDidChangeWatchedFiles,
lsproto.MethodWorkspaceDidChangeConfiguration,
lsproto.MethodWorkspaceConfiguration:
return true
}
return false
}
func ptrTo[T any](v T) *T {
return &v
}
func resolveClientCapabilities(caps *lsproto.ClientCapabilities) lsproto.ResolvedClientCapabilities {
resolved := lsproto.ResolveClientCapabilities(caps)
// Some clients claim that push and pull diagnostics have different capabilities,
// including vscode-languageclient v9. Work around this by defaulting any missing
// pull diagnostic caps with the pull diagnostic equivalents.
//
// TODO: remove when we upgrade to vscode-languageclient v10, which fixes this issue.
publish := resolved.TextDocument.PublishDiagnostics
diagnostic := &resolved.TextDocument.Diagnostic
if !diagnostic.RelatedInformation && publish.RelatedInformation {
diagnostic.RelatedInformation = true
}
if !diagnostic.CodeDescriptionSupport && publish.CodeDescriptionSupport {
diagnostic.CodeDescriptionSupport = true
}
if !diagnostic.DataSupport && publish.DataSupport {
diagnostic.DataSupport = true
}
if len(diagnostic.TagSupport.ValueSet) == 0 && len(publish.TagSupport.ValueSet) > 0 {
diagnostic.TagSupport.ValueSet = publish.TagSupport.ValueSet
}
return resolved
}