mirror of
https://github.com/golang/go
synced 2024-11-18 23:34:45 -07:00
0fd2d649e6
Add a new Forwarder type to the lsprpc package, which implements the jsonrpc2.StreamServer interface. This will be used to establish some parity in the implementation of shared and singleton gopls servers. Much more testing is needed, as is handling for the many edge cases around forwarding the LSP, but since this is functionally equivalent to TCP forwarding (and the -remote flag was already broken), I went ahead and used the Forwarder to replace the forward method in the serve command. This means that we can now use the combination of -listen and -remote to chain together gopls servers... not that there's any reason to do this. Also, wrap the new regression tests with a focus on expressiveness when testing the happy path, as well as parameterizing them so that they can be run against different client/server execution environments. This started to be sizable enough to warrant moving them to a separate regtest package. The lsprpc package tests will instead focus on unit testing the client-server binding logic. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib98131a58aabc69299845d2ecefceccfc1199574 Reviewed-on: https://go-review.googlesource.com/c/tools/+/218698 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
100 lines
2.9 KiB
Go
100 lines
2.9 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package fake
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
)
|
|
|
|
// Pos represents a 0-indexed position in a text buffer.
|
|
type Pos struct {
|
|
Line, Column int
|
|
}
|
|
|
|
func (p Pos) toProtocolPosition() protocol.Position {
|
|
return protocol.Position{
|
|
Line: float64(p.Line),
|
|
Character: float64(p.Column),
|
|
}
|
|
}
|
|
|
|
func fromProtocolPosition(pos protocol.Position) Pos {
|
|
return Pos{
|
|
Line: int(pos.Line),
|
|
Column: int(pos.Character),
|
|
}
|
|
}
|
|
|
|
// Edit represents a single (contiguous) buffer edit.
|
|
type Edit struct {
|
|
Start, End Pos
|
|
Text string
|
|
}
|
|
|
|
// NewEdit creates an edit replacing all content between
|
|
// (startLine, startColumn) and (endLine, endColumn) with text.
|
|
func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
|
|
return Edit{
|
|
Start: Pos{Line: startLine, Column: startColumn},
|
|
End: Pos{Line: endLine, Column: endColumn},
|
|
Text: text,
|
|
}
|
|
}
|
|
|
|
func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
|
|
return protocol.TextDocumentContentChangeEvent{
|
|
Range: &protocol.Range{
|
|
Start: e.Start.toProtocolPosition(),
|
|
End: e.End.toProtocolPosition(),
|
|
},
|
|
Text: e.Text,
|
|
}
|
|
}
|
|
|
|
func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
|
|
return Edit{
|
|
Start: fromProtocolPosition(textEdit.Range.Start),
|
|
End: fromProtocolPosition(textEdit.Range.End),
|
|
Text: textEdit.NewText,
|
|
}
|
|
}
|
|
|
|
// inText reports whether p is a valid position in the text buffer.
|
|
func inText(p Pos, content []string) bool {
|
|
if p.Line < 0 || p.Line >= len(content) {
|
|
return false
|
|
}
|
|
// Note the strict right bound: the column indexes character _separators_,
|
|
// not characters.
|
|
if p.Column < 0 || p.Column > len(content[p.Line]) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// editContent implements a simplistic, inefficient algorithm for applying text
|
|
// edits to our buffer representation. It returns an error if the edit is
|
|
// invalid for the current content.
|
|
func editContent(content []string, edit Edit) ([]string, error) {
|
|
if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
|
|
return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
|
|
}
|
|
if !inText(edit.Start, content) {
|
|
return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
|
|
}
|
|
if !inText(edit.End, content) {
|
|
return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
|
|
}
|
|
// Splice the edit text in between the first and last lines of the edit.
|
|
prefix := string([]rune(content[edit.Start.Line])[:edit.Start.Column])
|
|
suffix := string([]rune(content[edit.End.Line])[edit.End.Column:])
|
|
newLines := strings.Split(prefix+edit.Text+suffix, "\n")
|
|
newContent := append(content[:edit.Start.Line], newLines...)
|
|
return append(newContent, content[edit.End.Line+1:]...), nil
|
|
}
|