...
1 package prompt
2
3 import (
4 "strings"
5 "unicode/utf8"
6
7 "github.com/c-bata/go-prompt/internal/bisect"
8 istrings "github.com/c-bata/go-prompt/internal/strings"
9 runewidth "github.com/mattn/go-runewidth"
10 )
11
12
13 type Document struct {
14 Text string
15
16
17
18 cursorPosition int
19 lastKey Key
20 }
21
22
23 func NewDocument() *Document {
24 return &Document{
25 Text: "",
26 cursorPosition: 0,
27 }
28 }
29
30
31 func (d *Document) LastKeyStroke() Key {
32 return d.lastKey
33 }
34
35
36
37 func (d *Document) DisplayCursorPosition() int {
38 var position int
39 runes := []rune(d.Text)[:d.cursorPosition]
40 for i := range runes {
41 position += runewidth.RuneWidth(runes[i])
42 }
43 return position
44 }
45
46
47 func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
48 s := d.Text
49 cnt := 0
50
51 for len(s) > 0 {
52 cnt++
53 r, size := utf8.DecodeRuneInString(s)
54 if cnt == d.cursorPosition+offset {
55 return r
56 }
57 s = s[size:]
58 }
59 return 0
60 }
61
62
63 func (d *Document) TextBeforeCursor() string {
64 r := []rune(d.Text)
65 return string(r[:d.cursorPosition])
66 }
67
68
69 func (d *Document) TextAfterCursor() string {
70 r := []rune(d.Text)
71 return string(r[d.cursorPosition:])
72 }
73
74
75
76 func (d *Document) GetWordBeforeCursor() string {
77 x := d.TextBeforeCursor()
78 return x[d.FindStartOfPreviousWord():]
79 }
80
81
82
83 func (d *Document) GetWordAfterCursor() string {
84 x := d.TextAfterCursor()
85 return x[:d.FindEndOfCurrentWord()]
86 }
87
88
89
90 func (d *Document) GetWordBeforeCursorWithSpace() string {
91 x := d.TextBeforeCursor()
92 return x[d.FindStartOfPreviousWordWithSpace():]
93 }
94
95
96
97 func (d *Document) GetWordAfterCursorWithSpace() string {
98 x := d.TextAfterCursor()
99 return x[:d.FindEndOfCurrentWordWithSpace()]
100 }
101
102
103 func (d *Document) GetWordBeforeCursorUntilSeparator(sep string) string {
104 x := d.TextBeforeCursor()
105 return x[d.FindStartOfPreviousWordUntilSeparator(sep):]
106 }
107
108
109 func (d *Document) GetWordAfterCursorUntilSeparator(sep string) string {
110 x := d.TextAfterCursor()
111 return x[:d.FindEndOfCurrentWordUntilSeparator(sep)]
112 }
113
114
115
116 func (d *Document) GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
117 x := d.TextBeforeCursor()
118 return x[d.FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep):]
119 }
120
121
122
123 func (d *Document) GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
124 x := d.TextAfterCursor()
125 return x[:d.FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep)]
126 }
127
128
129
130 func (d *Document) FindStartOfPreviousWord() int {
131 x := d.TextBeforeCursor()
132 i := strings.LastIndexByte(x, ' ')
133 if i != -1 {
134 return i + 1
135 }
136 return 0
137 }
138
139
140
141 func (d *Document) FindStartOfPreviousWordWithSpace() int {
142 x := d.TextBeforeCursor()
143 end := istrings.LastIndexNotByte(x, ' ')
144 if end == -1 {
145 return 0
146 }
147
148 start := strings.LastIndexByte(x[:end], ' ')
149 if start == -1 {
150 return 0
151 }
152 return start + 1
153 }
154
155
156
157 func (d *Document) FindStartOfPreviousWordUntilSeparator(sep string) int {
158 if sep == "" {
159 return d.FindStartOfPreviousWord()
160 }
161
162 x := d.TextBeforeCursor()
163 i := strings.LastIndexAny(x, sep)
164 if i != -1 {
165 return i + 1
166 }
167 return 0
168 }
169
170
171
172 func (d *Document) FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep string) int {
173 if sep == "" {
174 return d.FindStartOfPreviousWordWithSpace()
175 }
176
177 x := d.TextBeforeCursor()
178 end := istrings.LastIndexNotAny(x, sep)
179 if end == -1 {
180 return 0
181 }
182 start := strings.LastIndexAny(x[:end], sep)
183 if start == -1 {
184 return 0
185 }
186 return start + 1
187 }
188
189
190
191 func (d *Document) FindEndOfCurrentWord() int {
192 x := d.TextAfterCursor()
193 i := strings.IndexByte(x, ' ')
194 if i != -1 {
195 return i
196 }
197 return len(x)
198 }
199
200
201
202 func (d *Document) FindEndOfCurrentWordWithSpace() int {
203 x := d.TextAfterCursor()
204
205 start := istrings.IndexNotByte(x, ' ')
206 if start == -1 {
207 return len(x)
208 }
209
210 end := strings.IndexByte(x[start:], ' ')
211 if end == -1 {
212 return len(x)
213 }
214
215 return start + end
216 }
217
218
219
220 func (d *Document) FindEndOfCurrentWordUntilSeparator(sep string) int {
221 if sep == "" {
222 return d.FindEndOfCurrentWord()
223 }
224
225 x := d.TextAfterCursor()
226 i := strings.IndexAny(x, sep)
227 if i != -1 {
228 return i
229 }
230 return len(x)
231 }
232
233
234
235 func (d *Document) FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep string) int {
236 if sep == "" {
237 return d.FindEndOfCurrentWordWithSpace()
238 }
239
240 x := d.TextAfterCursor()
241
242 start := istrings.IndexNotAny(x, sep)
243 if start == -1 {
244 return len(x)
245 }
246
247 end := strings.IndexAny(x[start:], sep)
248 if end == -1 {
249 return len(x)
250 }
251
252 return start + end
253 }
254
255
256 func (d *Document) CurrentLineBeforeCursor() string {
257 s := strings.Split(d.TextBeforeCursor(), "\n")
258 return s[len(s)-1]
259 }
260
261
262 func (d *Document) CurrentLineAfterCursor() string {
263 return strings.Split(d.TextAfterCursor(), "\n")[0]
264 }
265
266
267
268 func (d *Document) CurrentLine() string {
269 return d.CurrentLineBeforeCursor() + d.CurrentLineAfterCursor()
270 }
271
272
273 func (d *Document) lineStartIndexes() []int {
274
275
276
277 lc := d.LineCount()
278 lengths := make([]int, lc)
279 for i, l := range d.Lines() {
280 lengths[i] = len(l)
281 }
282
283
284 indexes := make([]int, lc+1)
285 indexes[0] = 0
286 pos := 0
287 for i, l := range lengths {
288 pos += l + 1
289 indexes[i+1] = pos
290 }
291 if lc > 1 {
292
293 indexes = indexes[:lc]
294 }
295 return indexes
296 }
297
298
299
300 func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
301 indexes := d.lineStartIndexes()
302 pos = bisect.Right(indexes, index) - 1
303 lineStartIndex = indexes[pos]
304 return
305 }
306
307
308 func (d *Document) CursorPositionRow() (row int) {
309 row, _ = d.findLineStartIndex(d.cursorPosition)
310 return
311 }
312
313
314 func (d *Document) CursorPositionCol() (col int) {
315
316
317 _, index := d.findLineStartIndex(d.cursorPosition)
318 col = d.cursorPosition - index
319 return
320 }
321
322
323 func (d *Document) GetCursorLeftPosition(count int) int {
324 if count < 0 {
325 return d.GetCursorRightPosition(-count)
326 }
327 if d.CursorPositionCol() > count {
328 return -count
329 }
330 return -d.CursorPositionCol()
331 }
332
333
334 func (d *Document) GetCursorRightPosition(count int) int {
335 if count < 0 {
336 return d.GetCursorLeftPosition(-count)
337 }
338 if len(d.CurrentLineAfterCursor()) > count {
339 return count
340 }
341 return len(d.CurrentLineAfterCursor())
342 }
343
344
345
346 func (d *Document) GetCursorUpPosition(count int, preferredColumn int) int {
347 var col int
348 if preferredColumn == -1 {
349 col = d.CursorPositionCol()
350 } else {
351 col = preferredColumn
352 }
353
354 row := d.CursorPositionRow() - count
355 if row < 0 {
356 row = 0
357 }
358 return d.TranslateRowColToIndex(row, col) - d.cursorPosition
359 }
360
361
362
363 func (d *Document) GetCursorDownPosition(count int, preferredColumn int) int {
364 var col int
365 if preferredColumn == -1 {
366 col = d.CursorPositionCol()
367 } else {
368 col = preferredColumn
369 }
370 row := d.CursorPositionRow() + count
371 return d.TranslateRowColToIndex(row, col) - d.cursorPosition
372 }
373
374
375 func (d *Document) Lines() []string {
376
377 return strings.Split(d.Text, "\n")
378 }
379
380
381
382 func (d *Document) LineCount() int {
383 return len(d.Lines())
384 }
385
386
387
388 func (d *Document) TranslateIndexToPosition(index int) (row int, col int) {
389 row, rowIndex := d.findLineStartIndex(index)
390 col = index - rowIndex
391 return
392 }
393
394
395
396 func (d *Document) TranslateRowColToIndex(row int, column int) (index int) {
397 indexes := d.lineStartIndexes()
398 if row < 0 {
399 row = 0
400 } else if row > len(indexes) {
401 row = len(indexes) - 1
402 }
403 index = indexes[row]
404 line := d.Lines()[row]
405
406
407 if column > 0 || len(line) > 0 {
408 if column > len(line) {
409 index += len(line)
410 } else {
411 index += column
412 }
413 }
414
415
416
417
418 if index > len(d.Text) {
419 index = len(d.Text)
420 }
421 if index < 0 {
422 index = 0
423 }
424 return index
425 }
426
427
428 func (d *Document) OnLastLine() bool {
429 return d.CursorPositionRow() == (d.LineCount() - 1)
430 }
431
432
433 func (d *Document) GetEndOfLinePosition() int {
434 return len([]rune(d.CurrentLineAfterCursor()))
435 }
436
437 func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
438 trimmed := strings.TrimSpace(d.CurrentLine())
439 margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
440 return
441 }
442
View as plain text