mirror of
https://github.com/golang/go
synced 2024-11-18 15:24:41 -07:00
0bd3dbed90
In preparation for later changes to the implementation of the workspace Symbol method, we add the Symbol method to fake.Editor. This requires the definition of a number of associated fake types (editor-friendly, byte-offset-based versions of protocol UTF16-based types) for example fake.SymbolInformation and the types it references. We also implement a basic regtest for the Symbol method, exposing Symbol on regtest.Env like other LSP server methods. To aid with the writing of Symbol result assertions, we provide some helper functions to simplify the process of defining matches that are evaluated against the result set. Change-Id: If73b493e1e791c8201423a303af8041f5a15ccfc Reviewed-on: https://go-review.googlesource.com/c/tools/+/228121 Run-TryBot: Paul Jolly <paul@myitcv.org.uk> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
154 lines
4.1 KiB
Go
154 lines
4.1 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"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
)
|
|
|
|
// Pos represents a position in a text buffer. Both Line and Column are
|
|
// 0-indexed.
|
|
type Pos struct {
|
|
Line, Column int
|
|
}
|
|
|
|
// Range corresponds to protocol.Range, but uses the editor friend Pos
|
|
// instead of UTF-16 oriented protocol.Position
|
|
type Range struct {
|
|
Start Pos
|
|
End Pos
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Location is the editor friendly equivalent of protocol.Location
|
|
type Location struct {
|
|
Path string
|
|
Range Range
|
|
}
|
|
|
|
// SymbolInformation is an editor friendly version of
|
|
// protocol.SymbolInformation, with location information transformed to byte
|
|
// offsets. Field names correspond to the protocol type.
|
|
type SymbolInformation struct {
|
|
Name string
|
|
Kind protocol.SymbolKind
|
|
Location Location
|
|
}
|
|
|
|
// 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([]rune(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, edits []Edit) ([]string, error) {
|
|
newEdits := make([]Edit, len(edits))
|
|
copy(newEdits, edits)
|
|
sort.Slice(newEdits, func(i, j int) bool {
|
|
if newEdits[i].Start.Line < newEdits[j].Start.Line {
|
|
return true
|
|
}
|
|
if newEdits[i].Start.Line > newEdits[j].Start.Line {
|
|
return false
|
|
}
|
|
return newEdits[i].Start.Column < newEdits[j].Start.Column
|
|
})
|
|
|
|
// Validate edits.
|
|
for _, edit := range newEdits {
|
|
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)
|
|
}
|
|
}
|
|
|
|
var (
|
|
b strings.Builder
|
|
line, column int
|
|
)
|
|
advance := func(toLine, toColumn int) {
|
|
for ; line < toLine; line++ {
|
|
b.WriteString(string([]rune(content[line])[column:]) + "\n")
|
|
column = 0
|
|
}
|
|
b.WriteString(string([]rune(content[line])[column:toColumn]))
|
|
column = toColumn
|
|
}
|
|
for _, edit := range newEdits {
|
|
advance(edit.Start.Line, edit.Start.Column)
|
|
b.WriteString(edit.Text)
|
|
line = edit.End.Line
|
|
column = edit.End.Column
|
|
}
|
|
advance(len(content)-1, len([]rune(content[len(content)-1])))
|
|
return strings.Split(b.String(), "\n"), nil
|
|
}
|