1// Copyright 2020 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package fake 6 7import ( 8 "fmt" 9 "strings" 10 11 "golang.org/x/tools/internal/lsp/protocol" 12) 13 14// Pos represents a 0-indexed position in a text buffer. 15type Pos struct { 16 Line, Column int 17} 18 19func (p Pos) toProtocolPosition() protocol.Position { 20 return protocol.Position{ 21 Line: float64(p.Line), 22 Character: float64(p.Column), 23 } 24} 25 26func fromProtocolPosition(pos protocol.Position) Pos { 27 return Pos{ 28 Line: int(pos.Line), 29 Column: int(pos.Character), 30 } 31} 32 33// Edit represents a single (contiguous) buffer edit. 34type Edit struct { 35 Start, End Pos 36 Text string 37} 38 39// NewEdit creates an edit replacing all content between 40// (startLine, startColumn) and (endLine, endColumn) with text. 41func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit { 42 return Edit{ 43 Start: Pos{Line: startLine, Column: startColumn}, 44 End: Pos{Line: endLine, Column: endColumn}, 45 Text: text, 46 } 47} 48 49func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent { 50 return protocol.TextDocumentContentChangeEvent{ 51 Range: &protocol.Range{ 52 Start: e.Start.toProtocolPosition(), 53 End: e.End.toProtocolPosition(), 54 }, 55 Text: e.Text, 56 } 57} 58 59func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit { 60 return Edit{ 61 Start: fromProtocolPosition(textEdit.Range.Start), 62 End: fromProtocolPosition(textEdit.Range.End), 63 Text: textEdit.NewText, 64 } 65} 66 67// inText reports whether p is a valid position in the text buffer. 68func inText(p Pos, content []string) bool { 69 if p.Line < 0 || p.Line >= len(content) { 70 return false 71 } 72 // Note the strict right bound: the column indexes character _separators_, 73 // not characters. 74 if p.Column < 0 || p.Column > len(content[p.Line]) { 75 return false 76 } 77 return true 78} 79 80// editContent implements a simplistic, inefficient algorithm for applying text 81// edits to our buffer representation. It returns an error if the edit is 82// invalid for the current content. 83func editContent(content []string, edit Edit) ([]string, error) { 84 if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) { 85 return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start) 86 } 87 if !inText(edit.Start, content) { 88 return nil, fmt.Errorf("start position %v is out of bounds", edit.Start) 89 } 90 if !inText(edit.End, content) { 91 return nil, fmt.Errorf("end position %v is out of bounds", edit.End) 92 } 93 // Splice the edit text in between the first and last lines of the edit. 94 prefix := string([]rune(content[edit.Start.Line])[:edit.Start.Column]) 95 suffix := string([]rune(content[edit.End.Line])[edit.End.Column:]) 96 newLines := strings.Split(prefix+edit.Text+suffix, "\n") 97 newContent := append(content[:edit.Start.Line], newLines...) 98 return append(newContent, content[edit.End.Line+1:]...), nil 99} 100