mirror of
https://github.com/golang/go
synced 2024-11-19 11:04:47 -07:00
49e4010bbf
Add two new fake editor commands: Formatting and OrganizeImports, which delegate to textDocument/formatting and textDocument/codeAction respectively. Use this in simple regtests, as well as on save. Implementing this required fixing a broken assumption about text edits in the editor: previously these edits were incrementally mutating the buffer, but the correct implementation should simultaneously mutate the buffer (i.e., all positions in an edit set refer to the starting buffer state). This never mattered before because we were only operating on one edit at a time. Updates golang/go#36879 Change-Id: I6dec343c4e202288fa20c26df2fbafe9340a1bce Reviewed-on: https://go-review.googlesource.com/c/tools/+/221539 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rohan Challa <rohan@golang.org>
131 lines
3.5 KiB
Go
131 lines
3.5 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 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([]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
|
|
}
|