diff --git a/internal/lsp/diff/diff.go b/internal/lsp/diff/diff.go index a789a7f0..cf7158d8 100644 --- a/internal/lsp/diff/diff.go +++ b/internal/lsp/diff/diff.go @@ -200,15 +200,14 @@ func shortestEditSequence(a, b []string) ([][]int, int) { // Return if we've exceeded the maximum values. if x == M && y == N { - // Save the state of the array, and exit function + // Makes sure to save the state of the array before returning. copy(copyV, V) trace[d] = copyV - return trace, offset } } - // Save the state of the array, and continue loop + // Save the state of the array. copy(copyV, V) trace[d] = copyV } diff --git a/internal/lsp/general.go b/internal/lsp/general.go index 683e537e..1e1af991 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -26,11 +26,11 @@ func (s *Server) initialize(ctx context.Context, params *protocol.InitializePara } s.isInitialized = true // mark server as initialized now - // TODO(iancottrell): Change this default to protocol.Incremental and remove the option - s.textDocumentSyncKind = protocol.Full + // TODO: Remove the option once we are certain there are no issues here. + s.textDocumentSyncKind = protocol.Incremental if opts, ok := params.InitializationOptions.(map[string]interface{}); ok { - if opt, ok := opts["incrementalSync"].(bool); ok && opt { - s.textDocumentSyncKind = protocol.Incremental + if opt, ok := opts["noIncrementalSync"].(bool); ok && opt { + s.textDocumentSyncKind = protocol.Full } } diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index b20b1a2c..fdb424c4 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -7,6 +7,7 @@ package lsp import ( "bytes" "context" + "fmt" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/lsp/protocol" @@ -25,23 +26,29 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided") } - var text string - switch s.textDocumentSyncKind { - case protocol.Incremental: - var err error - text, err = s.applyChanges(ctx, params) - if err != nil { - return err + uri := span.NewURI(params.TextDocument.URI) + + // Check if the client sent the full content of the file. + // We accept a full content change even if the server expected incremental changes. + text, isFullChange := fullChange(params.ContentChanges) + + // We only accept an incremental change if the server expected it. + if !isFullChange { + switch s.textDocumentSyncKind { + case protocol.Full: + return fmt.Errorf("expected a full content change, received incremental changes for %s", uri) + case protocol.Incremental: + // Determine the new file content. + var err error + text, err = s.applyChanges(ctx, uri, params.ContentChanges) + if err != nil { + return err + } } - case protocol.Full: - // We expect the full content of file, i.e. a single change with no range. - change := params.ContentChanges[0] - if change.RangeLength != 0 { - return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided") - } - text = change.Text } - return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), []byte(text)) + + // Cache the new file content and send fresh diagnostics. + return s.cacheAndDiagnose(ctx, uri, []byte(text)) } func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content []byte) error { @@ -56,23 +63,24 @@ func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content []b return nil } -func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (string, error) { - if len(params.ContentChanges) == 1 && params.ContentChanges[0].Range == nil { - // If range is empty, we expect the full content of file, i.e. a single change with no range. - change := params.ContentChanges[0] - if change.RangeLength != 0 { - return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided") - } - return change.Text, nil +func fullChange(changes []protocol.TextDocumentContentChangeEvent) (string, bool) { + if len(changes) > 1 { + return "", false } + // The length of the changes must be 1 at this point. + if changes[0].Range == nil && changes[0].RangeLength == 0 { + return changes[0].Text, true + } + return "", false +} - uri := span.NewURI(params.TextDocument.URI) +func (s *Server) applyChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) (string, error) { content, _, err := s.session.GetFile(uri).Read(ctx) if err != nil { return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found") } fset := s.session.Cache().FileSet() - for _, change := range params.ContentChanges { + for _, change := range changes { // Update column mapper along with the content. m := protocol.NewColumnMapper(uri, uri.Filename(), fset, nil, content)