github.com/aretext/aretext@v1.3.0/locate/character.go (about) 1 package locate 2 3 import ( 4 "io" 5 6 "github.com/aretext/aretext/cellwidth" 7 "github.com/aretext/aretext/text" 8 "github.com/aretext/aretext/text/segment" 9 ) 10 11 // NextCharInLine locates the next grapheme cluster in the current line. 12 func NextCharInLine(tree *text.Tree, count uint64, includeEndOfLineOrFile bool, pos uint64) uint64 { 13 reader := tree.ReaderAtPosition(pos) 14 segmentIter := segment.NewGraphemeClusterIter(reader) 15 seg := segment.Empty() 16 var endOfLineOrFile bool 17 var prevPrevOffset, prevOffset uint64 18 for i := uint64(0); i <= count; i++ { 19 err := segmentIter.NextSegment(seg) 20 if err == io.EOF { 21 endOfLineOrFile = true 22 break 23 } else if err != nil { 24 panic(err) 25 } 26 27 if seg.HasNewline() { 28 endOfLineOrFile = true 29 break 30 } 31 prevPrevOffset = prevOffset 32 prevOffset += seg.NumRunes() 33 } 34 if endOfLineOrFile && includeEndOfLineOrFile { 35 return pos + prevOffset 36 } 37 return pos + prevPrevOffset 38 } 39 40 // PrevCharInLine locates the previous grapheme cluster in the current line. 41 func PrevCharInLine(tree *text.Tree, count uint64, includeEndOfLineOrFile bool, pos uint64) uint64 { 42 reader := tree.ReverseReaderAtPosition(pos) 43 segmentIter := segment.NewReverseGraphemeClusterIter(reader) 44 seg := segment.Empty() 45 var offset uint64 46 for i := uint64(0); i < count; i++ { 47 err := segmentIter.NextSegment(seg) 48 if err == io.EOF { 49 break 50 } else if err != nil { 51 panic(err) 52 } 53 if offset+seg.NumRunes() > pos { 54 return 0 55 } 56 if seg.HasNewline() { 57 if includeEndOfLineOrFile { 58 offset += seg.NumRunes() 59 } 60 break 61 } 62 offset += seg.NumRunes() 63 } 64 return pos - offset 65 } 66 67 // PrevChar locates the grapheme cluster before a position, which may be on a previous line. 68 func PrevChar(tree *text.Tree, count uint64, pos uint64) uint64 { 69 reader := tree.ReverseReaderAtPosition(pos) 70 iter := segment.NewReverseGraphemeClusterIter(reader) 71 seg := segment.Empty() 72 for i := uint64(0); i < count; i++ { 73 err := iter.NextSegment(seg) 74 if err == io.EOF { 75 break 76 } else if err != nil { 77 panic(err) 78 } 79 pos -= seg.NumRunes() 80 } 81 return pos 82 } 83 84 // NextMatchingCharInLine locates the count'th next occurrence of a rune in the line. 85 func NextMatchingCharInLine(tree *text.Tree, char rune, count uint64, includeChar bool, pos uint64) (bool, uint64) { 86 var matchCount uint64 87 var offset, prevOffset uint64 88 reader := tree.ReaderAtPosition(pos) 89 segmentIter := segment.NewGraphemeClusterIter(reader) 90 seg := segment.Empty() 91 for { 92 err := segmentIter.NextSegment(seg) 93 if err == io.EOF || (err == nil && seg.HasNewline()) { 94 // No match found before end of line or file. 95 return false, 0 96 } else if err != nil { 97 panic(err) 98 } 99 100 if offset > 0 { 101 for _, r := range seg.Runes() { 102 if r == char { 103 matchCount++ 104 if matchCount == count { 105 if includeChar { 106 return true, pos + offset 107 } else { 108 return true, pos + prevOffset 109 } 110 } 111 } 112 } 113 } 114 115 prevOffset = offset 116 offset += seg.NumRunes() 117 } 118 } 119 120 // PrevMatchingCharInLine locates the count'th previous occurrence of a rune in the line. 121 func PrevMatchingCharInLine(tree *text.Tree, char rune, count uint64, includeChar bool, pos uint64) (bool, uint64) { 122 var matchCount uint64 123 var offset, prevOffset uint64 124 reader := tree.ReverseReaderAtPosition(pos) 125 segmentIter := segment.NewReverseGraphemeClusterIter(reader) 126 seg := segment.Empty() 127 for { 128 err := segmentIter.NextSegment(seg) 129 if err == io.EOF || (err == nil && seg.HasNewline()) { 130 // No match found before end of line or file. 131 return false, 0 132 } else if err != nil { 133 panic(err) 134 } 135 136 prevOffset = offset 137 offset += seg.NumRunes() 138 139 for _, r := range seg.Runes() { 140 if r == char { 141 matchCount++ 142 if matchCount == count { 143 if includeChar { 144 return true, pos - offset 145 } else { 146 return true, pos - prevOffset 147 } 148 } 149 } 150 } 151 } 152 } 153 154 // PrevAutoIndent locates the previous tab stop if autoIndent is enabled. 155 // If autoIndent is disabled or the characters before the cursor are not spaces/tabs, it returns the original position. 156 func PrevAutoIndent(tree *text.Tree, autoIndentEnabled bool, tabSize uint64, pos uint64) uint64 { 157 if !autoIndentEnabled { 158 return pos 159 } 160 161 prevTabAlignedPos := findPrevTabAlignedPos(tree, tabSize, pos) 162 prevWhitespaceStartPos := findPrevWhitespaceStartPos(tree, tabSize, pos) 163 if prevTabAlignedPos < prevWhitespaceStartPos { 164 return prevWhitespaceStartPos 165 } else { 166 return prevTabAlignedPos 167 } 168 } 169 170 func findPrevTabAlignedPos(tree *text.Tree, tabSize uint64, startPos uint64) uint64 { 171 pos := StartOfLineAtPos(tree, startPos) 172 reader := tree.ReaderAtPosition(pos) 173 iter := segment.NewGraphemeClusterIter(reader) 174 seg := segment.Empty() 175 var offset uint64 176 lastAlignedPos := pos 177 for pos < startPos { 178 if offset%tabSize == 0 { 179 lastAlignedPos = pos 180 } 181 err := iter.NextSegment(seg) 182 if err == io.EOF { 183 break 184 } else if err != nil { 185 panic(err) 186 } 187 offset += cellwidth.GraphemeClusterWidth(seg.Runes(), offset, tabSize) 188 pos += seg.NumRunes() 189 } 190 return lastAlignedPos 191 } 192 193 func findPrevWhitespaceStartPos(tree *text.Tree, tabSize uint64, pos uint64) uint64 { 194 reader := tree.ReverseReaderAtPosition(pos) 195 iter := segment.NewReverseGraphemeClusterIter(reader) 196 seg := segment.Empty() 197 for { 198 err := iter.NextSegment(seg) 199 if err == io.EOF { 200 break 201 } else if err != nil { 202 panic(err) 203 } 204 205 r := seg.Runes()[0] 206 if r != ' ' && r != '\t' { 207 break 208 } 209 pos -= seg.NumRunes() 210 } 211 return pos 212 } 213 214 // NonWhitespaceOrNewline locates the next non-whitespace character or newline on or after a position. 215 func NextNonWhitespaceOrNewline(tree *text.Tree, pos uint64) uint64 { 216 reader := tree.ReaderAtPosition(pos) 217 segmentIter := segment.NewGraphemeClusterIter(reader) 218 seg := segment.Empty() 219 var offset uint64 220 for { 221 err := segmentIter.NextSegment(seg) 222 if err == io.EOF || (err == nil && (!seg.IsWhitespace() || seg.HasNewline())) { 223 break 224 } else if err != nil { 225 panic(err) 226 } 227 offset += seg.NumRunes() 228 } 229 return pos + offset 230 } 231 232 // NextNewline locates the next newline on or after the specified position. 233 // It returns both the positon of the newline as well as its length in runes, 234 // since the grapheme cluster could be either '\n' or '\r\n'. 235 func NextNewline(tree *text.Tree, pos uint64) (uint64, uint64, bool) { 236 reader := tree.ReaderAtPosition(pos) 237 segmentIter := segment.NewGraphemeClusterIter(reader) 238 seg := segment.Empty() 239 var offset uint64 240 for { 241 err := segmentIter.NextSegment(seg) 242 if err == io.EOF { 243 return 0, 0, false 244 } else if err != nil { 245 panic(err) 246 } else if seg.HasNewline() { 247 return pos + offset, seg.NumRunes(), true 248 } 249 offset += seg.NumRunes() 250 } 251 } 252 253 // NumGraphemeClustersInRange counts the number of grapheme clusters from the start position (inclusive) to the end position (exclusive). 254 func NumGraphemeClustersInRange(tree *text.Tree, startPos, endPos uint64) uint64 { 255 reader := tree.ReaderAtPosition(startPos) 256 segmentIter := segment.NewGraphemeClusterIter(reader) 257 seg := segment.Empty() 258 var offset, count uint64 259 for startPos+offset < endPos { 260 err := segmentIter.NextSegment(seg) 261 if err == io.EOF || (err == nil && seg.HasNewline()) { 262 break 263 } else if err != nil { 264 panic(err) 265 } 266 count++ 267 offset += seg.NumRunes() 268 } 269 return count 270 271 }