| // 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 |
| } |
| |
| func (p Pos) String() string { |
| return fmt.Sprintf("%v:%v", p.Line, p.Column) |
| } |
| |
| // 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: uint32(p.Line), |
| Character: uint32(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. |
| // |
| // TODO(rfindley): this function does not handle non-ascii text correctly. |
| 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 |
| } |