github.com/v2fly/tools@v0.100.0/internal/lsp/fake/edit.go (about) 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 5 package fake 6 7 import ( 8 "fmt" 9 "sort" 10 "strings" 11 12 "github.com/v2fly/tools/internal/lsp/protocol" 13 ) 14 15 // Pos represents a position in a text buffer. Both Line and Column are 16 // 0-indexed. 17 type Pos struct { 18 Line, Column int 19 } 20 21 // Range corresponds to protocol.Range, but uses the editor friend Pos 22 // instead of UTF-16 oriented protocol.Position 23 type Range struct { 24 Start Pos 25 End Pos 26 } 27 28 func (p Pos) ToProtocolPosition() protocol.Position { 29 return protocol.Position{ 30 Line: uint32(p.Line), 31 Character: uint32(p.Column), 32 } 33 } 34 35 func fromProtocolPosition(pos protocol.Position) Pos { 36 return Pos{ 37 Line: int(pos.Line), 38 Column: int(pos.Character), 39 } 40 } 41 42 // Edit represents a single (contiguous) buffer edit. 43 type Edit struct { 44 Start, End Pos 45 Text string 46 } 47 48 // Location is the editor friendly equivalent of protocol.Location 49 type Location struct { 50 Path string 51 Range Range 52 } 53 54 // SymbolInformation is an editor friendly version of 55 // protocol.SymbolInformation, with location information transformed to byte 56 // offsets. Field names correspond to the protocol type. 57 type SymbolInformation struct { 58 Name string 59 Kind protocol.SymbolKind 60 Location Location 61 } 62 63 // NewEdit creates an edit replacing all content between 64 // (startLine, startColumn) and (endLine, endColumn) with text. 65 func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit { 66 return Edit{ 67 Start: Pos{Line: startLine, Column: startColumn}, 68 End: Pos{Line: endLine, Column: endColumn}, 69 Text: text, 70 } 71 } 72 73 func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent { 74 return protocol.TextDocumentContentChangeEvent{ 75 Range: &protocol.Range{ 76 Start: e.Start.ToProtocolPosition(), 77 End: e.End.ToProtocolPosition(), 78 }, 79 Text: e.Text, 80 } 81 } 82 83 func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit { 84 return Edit{ 85 Start: fromProtocolPosition(textEdit.Range.Start), 86 End: fromProtocolPosition(textEdit.Range.End), 87 Text: textEdit.NewText, 88 } 89 } 90 91 // inText reports whether p is a valid position in the text buffer. 92 func inText(p Pos, content []string) bool { 93 if p.Line < 0 || p.Line >= len(content) { 94 return false 95 } 96 // Note the strict right bound: the column indexes character _separators_, 97 // not characters. 98 if p.Column < 0 || p.Column > len([]rune(content[p.Line])) { 99 return false 100 } 101 return true 102 } 103 104 // editContent implements a simplistic, inefficient algorithm for applying text 105 // edits to our buffer representation. It returns an error if the edit is 106 // invalid for the current content. 107 func editContent(content []string, edits []Edit) ([]string, error) { 108 newEdits := make([]Edit, len(edits)) 109 copy(newEdits, edits) 110 sort.Slice(newEdits, func(i, j int) bool { 111 if newEdits[i].Start.Line < newEdits[j].Start.Line { 112 return true 113 } 114 if newEdits[i].Start.Line > newEdits[j].Start.Line { 115 return false 116 } 117 return newEdits[i].Start.Column < newEdits[j].Start.Column 118 }) 119 120 // Validate edits. 121 for _, edit := range newEdits { 122 if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) { 123 return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start) 124 } 125 if !inText(edit.Start, content) { 126 return nil, fmt.Errorf("start position %v is out of bounds", edit.Start) 127 } 128 if !inText(edit.End, content) { 129 return nil, fmt.Errorf("end position %v is out of bounds", edit.End) 130 } 131 } 132 133 var ( 134 b strings.Builder 135 line, column int 136 ) 137 advance := func(toLine, toColumn int) { 138 for ; line < toLine; line++ { 139 b.WriteString(string([]rune(content[line])[column:]) + "\n") 140 column = 0 141 } 142 b.WriteString(string([]rune(content[line])[column:toColumn])) 143 column = toColumn 144 } 145 for _, edit := range newEdits { 146 advance(edit.Start.Line, edit.Start.Column) 147 b.WriteString(edit.Text) 148 line = edit.End.Line 149 column = edit.End.Column 150 } 151 advance(len(content)-1, len([]rune(content[len(content)-1]))) 152 return strings.Split(b.String(), "\n"), nil 153 }