// 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 } 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, } } // 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) } // inText reports whether a position is within the bounds of the current // text. inText := func(p Pos) 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 } if !inText(edit.Start) { return nil, fmt.Errorf("start position %v is out of bounds", edit.Start) } if !inText(edit.End) { 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 }