https://github.com/akkartik/mu/blob/main/shell/gap-buffer.mu
  1 # primitive for editing text
  2 
  3 type gap-buffer {
  4   left: grapheme-stack
  5   right: grapheme-stack
  6   # some fields for scanning incrementally through a gap-buffer
  7   left-read-index: int
  8   right-read-index: int
  9 }
 10 
 11 fn initialize-gap-buffer _self: (addr gap-buffer), max-word-size: int {
 12   var self/esi: (addr gap-buffer) <- copy _self
 13   var left/eax: (addr grapheme-stack) <- get self, left
 14   initialize-grapheme-stack left, max-word-size
 15   var right/eax: (addr grapheme-stack) <- get self, right
 16   initialize-grapheme-stack right, max-word-size
 17 }
 18 
 19 fn clear-gap-buffer _self: (addr gap-buffer) {
 20   var self/esi: (addr gap-buffer) <- copy _self
 21   var left/eax: (addr grapheme-stack) <- get self, left
 22   clear-grapheme-stack left
 23   var right/eax: (addr grapheme-stack) <- get self, right
 24   clear-grapheme-stack right
 25 }
 26 
 27 # just for tests
 28 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
 29   initialize-gap-buffer self, 0x10/max-word-size
 30   var stream-storage: (stream byte 0x10/max-word-size)
 31   var stream/ecx: (addr stream byte) <- address stream-storage
 32   write stream, s
 33   {
 34     var done?/eax: boolean <- stream-empty? stream
 35     compare done?, 0/false
 36     break-if-!=
 37     var g/eax: grapheme <- read-grapheme stream
 38     add-grapheme-at-gap self, g
 39     loop
 40   }
 41 }
 42 
 43 fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
 44   var self/esi: (addr gap-buffer) <- copy _self
 45   clear-stream out
 46   var left/eax: (addr grapheme-stack) <- get self, left
 47   emit-stack-from-bottom left, out
 48   var right/eax: (addr grapheme-stack) <- get self, right
 49   emit-stack-from-top right, out
 50 }
 51 
 52 # dump stack from bottom to top
 53 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
 54   var self/esi: (addr grapheme-stack) <- copy _self
 55   var data-ah/edi: (addr handle array grapheme) <- get self, data
 56   var _data/eax: (addr array grapheme) <- lookup *data-ah
 57   var data/edi: (addr array grapheme) <- copy _data
 58   var top-addr/ecx: (addr int) <- get self, top
 59   var i/eax: int <- copy 0
 60   {
 61     compare i, *top-addr
 62     break-if->=
 63     var g/edx: (addr grapheme) <- index data, i
 64     write-grapheme out, *g
 65     i <- increment
 66     loop
 67   }
 68 }
 69 
 70 # dump stack from top to bottom
 71 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
 72   var self/esi: (addr grapheme-stack) <- copy _self
 73   var data-ah/edi: (addr handle array grapheme) <- get self, data
 74   var _data/eax: (addr array grapheme) <- lookup *data-ah
 75   var data/edi: (addr array grapheme) <- copy _data
 76   var top-addr/ecx: (addr int) <- get self, top
 77   var i/eax: int <- copy *top-addr
 78   i <- decrement
 79   {
 80     compare i, 0
 81     break-if-<
 82     var g/edx: (addr grapheme) <- index data, i
 83     write-grapheme out, *g
 84     i <- decrement
 85     loop
 86   }
 87 }
 88 
 89 # We implicitly render everything editable in a single color, and assume the
 90 # cursor is a single other color.
 91 fn render-gap-buffer-wrapping-right-then-down screen: (addr screen), _gap: (addr gap-buffer), xmin: int, ymin: int, xmax: int, ymax: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
 92   var gap/esi: (addr gap-buffer) <- copy _gap
 93   var left/edx: (addr grapheme-stack) <- get gap, left
 94   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
 95   var matching-open-paren-depth/edi: int <- copy 0
 96   highlight-matching-open-paren?, matching-open-paren-depth <- highlight-matching-open-paren? gap, render-cursor?
 97   var x2/eax: int <- copy 0
 98   var y2/ecx: int <- copy 0
 99   x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, left, xmin, ymin, xmax, ymax, xmin, ymin, highlight-matching-open-paren?, matching-open-paren-depth
100   var right/edx: (addr grapheme-stack) <- get gap, right
101   x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, right, xmin, ymin, xmax, ymax, x2, y2, render-cursor?
102   # decide whether we still need to print a cursor
103   var bg/ebx: int <- copy 0
104   compare render-cursor?, 0/false
105   {
106     break-if-=
107     # if the right side is empty, grapheme stack didn't print the cursor
108     var empty?/eax: boolean <- grapheme-stack-empty? right
109     compare empty?, 0/false
110     break-if-=
111     bg <- copy 7/cursor
112   }
113   # print a grapheme either way so that cursor position doesn't affect printed width
114   var space/edx: grapheme <- copy 0x20
115   x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, 3/fg=cyan, bg
116   return x2, y2
117 }
118 
119 fn render-gap-buffer screen: (addr screen), gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean -> _/eax: int {
120   var _width/eax: int <- copy 0
121   var _height/ecx: int <- copy 0
122   _width, _height <- screen-size screen
123   var width/edx: int <- copy _width
124   var height/ebx: int <- copy _height
125   var x2/eax: int <- copy 0
126   var y2/ecx: int <- copy 0
127   x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?
128   return x2  # y2? yolo
129 }
130 
131 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
132   var gap/esi: (addr gap-buffer) <- copy _gap
133   var left/eax: (addr grapheme-stack) <- get gap, left
134   var tmp/eax: (addr int) <- get left, top
135   var left-length/ecx: int <- copy *tmp
136   var right/esi: (addr grapheme-stack) <- get gap, right
137   tmp <- get right, top
138   var result/eax: int <- copy *tmp
139   result <- add left-length
140   return result
141 }
142 
143 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
144   var self/esi: (addr gap-buffer) <- copy _self
145   var left/eax: (addr grapheme-stack) <- get self, left
146   push-grapheme-stack left, g
147 }
148 
149 fn gap-to-start self: (addr gap-buffer) {
150   {
151     var curr/eax: grapheme <- gap-left self
152     compare curr, -1
153     loop-if-!=
154   }
155 }
156 
157 fn gap-to-end self: (addr gap-buffer) {
158   {
159     var curr/eax: grapheme <- gap-right self
160     compare curr, -1
161     loop-if-!=
162   }
163 }
164 
165 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
166   var self/esi: (addr gap-buffer) <- copy _self
167   var left/eax: (addr grapheme-stack) <- get self, left
168   var result/eax: boolean <- grapheme-stack-empty? left
169   return result
170 }
171 
172 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
173   var self/esi: (addr gap-buffer) <- copy _self
174   var right/eax: (addr grapheme-stack) <- get self, right
175   var result/eax: boolean <- grapheme-stack-empty? right
176   return result
177 }
178 
179 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
180   var self/esi: (addr gap-buffer) <- copy _self
181   var g/eax: grapheme <- copy 0
182   var right/ecx: (addr grapheme-stack) <- get self, right
183   g <- pop-grapheme-stack right
184   compare g, -1
185   {
186     break-if-=
187     var left/ecx: (addr grapheme-stack) <- get self, left
188     push-grapheme-stack left, g
189   }
190   return g
191 }
192 
193 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
194   var self/esi: (addr gap-buffer) <- copy _self
195   var g/eax: grapheme <- copy 0
196   {
197     var left/ecx: (addr grapheme-stack) <- get self, left
198     g <- pop-grapheme-stack left
199   }
200   compare g, -1
201   {
202     break-if-=
203     var right/ecx: (addr grapheme-stack) <- get self, right
204     push-grapheme-stack right, g
205   }
206   return g
207 }
208 
209 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
210   var self/eax: (addr gap-buffer) <- copy _self
211   var left/eax: (addr grapheme-stack) <- get self, left
212   var top-addr/eax: (addr int) <- get left, top
213   var result/eax: int <- copy *top-addr
214   return result
215 }
216 
217 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
218   var self/esi: (addr gap-buffer) <- copy _self
219   # try to read from left
220   var left/eax: (addr grapheme-stack) <- get self, left
221   var top-addr/ecx: (addr int) <- get left, top
222   compare *top-addr, 0
223   {
224     break-if-<=
225     var data-ah/eax: (addr handle array grapheme) <- get left, data
226     var data/eax: (addr array grapheme) <- lookup *data-ah
227     var result-addr/eax: (addr grapheme) <- index data, 0
228     return *result-addr
229   }
230   # try to read from right
231   var right/eax: (addr grapheme-stack) <- get self, right
232   top-addr <- get right, top
233   compare *top-addr, 0
234   {
235     break-if-<=
236     var data-ah/eax: (addr handle array grapheme) <- get right, data
237     var data/eax: (addr array grapheme) <- lookup *data-ah
238     var top/ecx: int <- copy *top-addr
239     top <- decrement
240     var result-addr/eax: (addr grapheme) <- index data, top
241     return *result-addr
242   }
243   # give up
244   return -1
245 }
246 
247 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
248   var self/esi: (addr gap-buffer) <- copy _self
249   # try to read from left
250   var left/ecx: (addr grapheme-stack) <- get self, left
251   var top-addr/edx: (addr int) <- get left, top
252   compare *top-addr, 0
253   {
254     break-if-<=
255     var result/eax: grapheme <- pop-grapheme-stack left
256     push-grapheme-stack left, result
257     return result
258   }
259   # give up
260   return -1
261 }
262 
263 fn delete-before-gap _self: (addr gap-buffer) {
264   var self/eax: (addr gap-buffer) <- copy _self
265   var left/eax: (addr grapheme-stack) <- get self, left
266   var dummy/eax: grapheme <- pop-grapheme-stack left
267 }
268 
269 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
270   var self/eax: (addr gap-buffer) <- copy _self
271   var right/eax: (addr grapheme-stack) <- get self, right
272   var result/eax: grapheme <- pop-grapheme-stack right
273   return result
274 }
275 
276 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
277   var self/esi: (addr gap-buffer) <- copy _self
278   # complication: graphemes may be multiple bytes
279   # so don't rely on length
280   # instead turn the expected result into a stream and arrange to read from it in order
281   var stream-storage: (stream byte 0x10/max-word-size)
282   var expected-stream/ecx: (addr stream byte) <- address stream-storage
283   write expected-stream, s
284   # compare left
285   var left/edx: (addr grapheme-stack) <- get self, left
286   var result/eax: boolean <- prefix-match? left, expected-stream
287   compare result, 0/false
288   {
289     break-if-!=
290     return result
291   }
292   # compare right
293   var right/edx: (addr grapheme-stack) <- get self, right
294   result <- suffix-match? right, expected-stream
295   compare result, 0/false
296   {
297     break-if-!=
298     return result
299   }
300   # ensure there's nothing left over
301   result <- stream-empty? expected-stream
302   return result
303 }
304 
305 fn test-gap-buffer-equal-from-end {
306   var _g: gap-buffer
307   var g/esi: (addr gap-buffer) <- address _g
308   initialize-gap-buffer g, 0x10
309   #
310   var c/eax: grapheme <- copy 0x61/a
311   add-grapheme-at-gap g, c
312   add-grapheme-at-gap g, c
313   add-grapheme-at-gap g, c
314   # gap is at end (right is empty)
315   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
316   check result, "F - test-gap-buffer-equal-from-end"
317 }
318 
319 fn test-gap-buffer-equal-from-middle {
320   var _g: gap-buffer
321   var g/esi: (addr gap-buffer) <- address _g
322   initialize-gap-buffer g, 0x10
323   #
324   var c/eax: grapheme <- copy 0x61/a
325   add-grapheme-at-gap g, c
326   add-grapheme-at-gap g, c
327   add-grapheme-at-gap g, c
328   var dummy/eax: grapheme <- gap-left g
329   # gap is in the middle
330   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
331   check result, "F - test-gap-buffer-equal-from-middle"
332 }
333 
334 fn test-gap-buffer-equal-from-start {
335   var _g: gap-buffer
336   var g/esi: (addr gap-buffer) <- address _g
337   initialize-gap-buffer g, 0x10
338   #
339   var c/eax: grapheme <- copy 0x61/a
340   add-grapheme-at-gap g, c
341   add-grapheme-at-gap g, c
342   add-grapheme-at-gap g, c
343   var dummy/eax: grapheme <- gap-left g
344   dummy <- gap-left g
345   dummy <- gap-left g
346   # gap is at the start
347   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
348   check result, "F - test-gap-buffer-equal-from-start"
349 }
350 
351 fn test-gap-buffer-equal-fails {
352   # g = "aaa"
353   var _g: gap-buffer
354   var g/esi: (addr gap-buffer) <- address _g
355   initialize-gap-buffer g, 0x10
356   var c/eax: grapheme <- copy 0x61/a
357   add-grapheme-at-gap g, c
358   add-grapheme-at-gap g, c
359   add-grapheme-at-gap g, c
360   #
361   var result/eax: boolean <- gap-buffer-equal? g, "aa"
362   check-not result, "F - test-gap-buffer-equal-fails"
363 }
364 
365 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
366   var tmp/eax: int <- gap-buffer-length self
367   var len/ecx: int <- copy tmp
368   var leng/eax: int <- gap-buffer-length g
369   compare len, leng
370   {
371     break-if-=
372     return 0/false
373   }
374   var i/edx: int <- copy 0
375   {
376     compare i, len
377     break-if->=
378     {
379       var tmp/eax: grapheme <- gap-index self, i
380       var curr/ecx: grapheme <- copy tmp
381       var currg/eax: grapheme <- gap-index g, i
382       compare curr, currg
383       break-if-=
384       return 0/false
385     }
386     i <- increment
387     loop
388   }
389   return 1/true
390 }
391 
392 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
393   var self/esi: (addr gap-buffer) <- copy _self
394   var n/ebx: int <- copy _n
395   # if n < left->length, index into left
396   var left/edi: (addr grapheme-stack) <- get self, left
397   var left-len-a/edx: (addr int) <- get left, top
398   compare n, *left-len-a
399   {
400     break-if->=
401     var data-ah/eax: (addr handle array grapheme) <- get left, data
402     var data/eax: (addr array grapheme) <- lookup *data-ah
403     var result/eax: (addr grapheme) <- index data, n
404     return *result
405   }
406   # shrink n
407   n <- subtract *left-len-a
408   # if n < right->length, index into right
409   var right/edi: (addr grapheme-stack) <- get self, right
410   var right-len-a/edx: (addr int) <- get right, top
411   compare n, *right-len-a
412   {
413     break-if->=
414     var data-ah/eax: (addr handle array grapheme) <- get right, data
415     var data/eax: (addr array grapheme) <- lookup *data-ah
416     # idx = right->len - n - 1
417     var idx/ebx: int <- copy n
418     idx <- subtract *right-len-a
419     idx <- negate
420     idx <- subtract 1
421     var result/eax: (addr grapheme) <- index data, idx
422     return *result
423   }
424   # error
425   abort "gap-index: out of bounds"
426   return 0
427 }
428 
429 fn test-gap-buffers-equal? {
430   var _a: gap-buffer
431   var a/esi: (addr gap-buffer) <- address _a
432   initialize-gap-buffer-with a, "abc"
433   var _b: gap-buffer
434   var b/edi: (addr gap-buffer) <- address _b
435   initialize-gap-buffer-with b, "abc"
436   var _c: gap-buffer
437   var c/ebx: (addr gap-buffer) <- address _c
438   initialize-gap-buffer-with c, "ab"
439   var _d: gap-buffer
440   var d/edx: (addr gap-buffer) <- address _d
441   initialize-gap-buffer-with d, "abd"
442   #
443   var result/eax: boolean <- gap-buffers-equal? a, a
444   check result, "F - test-gap-buffers-equal? - reflexive"
445   result <- gap-buffers-equal? a, b
446   check result, "F - test-gap-buffers-equal? - equal"
447   # length not equal
448   result <- gap-buffers-equal? a, c
449   check-not result, "F - test-gap-buffers-equal? - not equal"
450   # contents not equal
451   result <- gap-buffers-equal? a, d
452   check-not result, "F - test-gap-buffers-equal? - not equal 2"
453   result <- gap-buffers-equal? d, a
454   check-not result, "F - test-gap-buffers-equal? - not equal 3"
455 }
456 
457 fn test-gap-buffer-index {
458   var gap-storage: gap-buffer
459   var gap/esi: (addr gap-buffer) <- address gap-storage
460   initialize-gap-buffer-with gap, "abc"
461   # gap is at end, all contents are in left
462   var g/eax: grapheme <- gap-index gap, 0
463   var x/ecx: int <- copy g
464   check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
465   var g/eax: grapheme <- gap-index gap, 1
466   var x/ecx: int <- copy g
467   check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
468   var g/eax: grapheme <- gap-index gap, 2
469   var x/ecx: int <- copy g
470   check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
471   # now check when everything is to the right
472   gap-to-start gap
473   rewind-gap-buffer gap
474   var g/eax: grapheme <- gap-index gap, 0
475   var x/ecx: int <- copy g
476   check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
477   var g/eax: grapheme <- gap-index gap, 1
478   var x/ecx: int <- copy g
479   check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
480   var g/eax: grapheme <- gap-index gap, 2
481   var x/ecx: int <- copy g
482   check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
483 }
484 
485 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
486   # obtain src-a, dest-a
487   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
488   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
489   var src-a/esi: (addr gap-buffer) <- copy _src-a
490   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
491   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
492   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
493   # copy left grapheme-stack
494   var src/ecx: (addr grapheme-stack) <- get src-a, left
495   var dest/edx: (addr grapheme-stack) <- get dest-a, left
496   copy-grapheme-stack src, dest
497   # copy right grapheme-stack
498   src <- get src-a, right
499   dest <- get dest-a, right
500   copy-grapheme-stack src, dest
501 }
502 
503 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
504   var self/esi: (addr gap-buffer) <- copy _self
505   var curr/ecx: (addr grapheme-stack) <- get self, left
506   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
507   {
508     compare result, 0/false
509     break-if-=
510     curr <- get self, right
511     result <- grapheme-stack-is-decimal-integer? curr
512   }
513   return result
514 }
515 
516 fn test-render-gap-buffer-without-cursor {
517   # setup
518   var gap-storage: gap-buffer
519   var gap/esi: (addr gap-buffer) <- address gap-storage
520   initialize-gap-buffer-with gap, "abc"
521   # setup: screen
522   var screen-on-stack: screen
523   var screen/edi: (addr screen) <- address screen-on-stack
524   initialize-screen screen, 5, 4
525   #
526   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor
527   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
528   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
529                                                                 # abc
530   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
531 }
532 
533 fn test-render-gap-buffer-with-cursor-at-end {
534   # setup
535   var gap-storage: gap-buffer
536   var gap/esi: (addr gap-buffer) <- address gap-storage
537   initialize-gap-buffer-with gap, "abc"
538   gap-to-end gap
539   # setup: screen
540   var screen-on-stack: screen
541   var screen/edi: (addr screen) <- address screen-on-stack
542   initialize-screen screen, 5, 4
543   #
544   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
545   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
546   # we've drawn one extra grapheme for the cursor
547   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
548                                                                 # abc
549   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
550 }
551 
552 fn test-render-gap-buffer-with-cursor-in-middle {
553   # setup
554   var gap-storage: gap-buffer
555   var gap/esi: (addr gap-buffer) <- address gap-storage
556   initialize-gap-buffer-with gap, "abc"
557   gap-to-end gap
558   var dummy/eax: grapheme <- gap-left gap
559   # setup: screen
560   var screen-on-stack: screen
561   var screen/edi: (addr screen) <- address screen-on-stack
562   initialize-screen screen, 5, 4
563   #
564   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
565   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
566   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
567                                                                 # abc
568   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
569 }
570 
571 fn test-render-gap-buffer-with-cursor-at-start {
572   var gap-storage: gap-buffer
573   var gap/esi: (addr gap-buffer) <- address gap-storage
574   initialize-gap-buffer-with gap, "abc"
575   gap-to-start gap
576   # setup: screen
577   var screen-on-stack: screen
578   var screen/edi: (addr screen) <- address screen-on-stack
579   initialize-screen screen, 5, 4
580   #
581   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
582   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
583   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
584                                                                 # abc
585   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
586 }
587 
588 fn test-render-gap-buffer-highlight-matching-close-paren {
589   var gap-storage: gap-buffer
590   var gap/esi: (addr gap-buffer) <- address gap-storage
591   initialize-gap-buffer-with gap, "(a)"
592   gap-to-start gap
593   # setup: screen
594   var screen-on-stack: screen
595   var screen/edi: (addr screen) <- address screen-on-stack
596   initialize-screen screen, 5, 4
597   #
598   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
599   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-close-paren"
600   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-close-paren: result"
601   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "|   ", "F - test-render-gap-buffer-highlight-matching-close-paren: cursor"
602   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "  ) ", "F - test-render-gap-buffer-highlight-matching-close-paren: matching paren"
603 }
604 
605 fn test-render-gap-buffer-highlight-matching-open-paren {
606   var gap-storage: gap-buffer
607   var gap/esi: (addr gap-buffer) <- address gap-storage
608   initialize-gap-buffer-with gap, "(a)"
609   gap-to-end gap
610   var dummy/eax: grapheme <- gap-left gap
611   # setup: screen
612   var screen-on-stack: screen
613   var screen/edi: (addr screen) <- address screen-on-stack
614   initialize-screen screen, 5, 4
615   #
616   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
617   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren"
618   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren: result"
619   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "  | ", "F - test-render-gap-buffer-highlight-matching-open-paren: cursor"
620   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren: matching paren"
621 }
622 
623 fn test-render-gap-buffer-highlight-matching-open-paren-of-end {
624   var gap-storage: gap-buffer
625   var gap/esi: (addr gap-buffer) <- address gap-storage
626   initialize-gap-buffer-with gap, "(a)"
627   gap-to-end gap
628   # setup: screen
629   var screen-on-stack: screen
630   var screen/edi: (addr screen) <- address screen-on-stack
631   initialize-screen screen, 5, 4
632   #
633   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
634   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end"
635   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: result"
636   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "   |", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: cursor"
637   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: matching paren"
638 }
639 
640 # should I highlight a matching open paren? And if so, at what depth from top of left?
641 # basically there are two cases to disambiguate here:
642 #   Usually the cursor is at top of right. Highlight first '(' at depth 0 from top of left.
643 #   If right is empty, match the ')' _before_ cursor. Highlight first '(' at depth _1_ from top of left.
644 fn highlight-matching-open-paren? _gap: (addr gap-buffer), render-cursor?: boolean -> _/ebx: boolean, _/edi: int {
645   # if not rendering cursor, return
646   compare render-cursor?, 0/false
647   {
648     break-if-!=
649     return 0/false, 0
650   }
651   var gap/esi: (addr gap-buffer) <- copy _gap
652   var stack/edi: (addr grapheme-stack) <- get gap, right
653   var top-addr/eax: (addr int) <- get stack, top
654   var top-index/ecx: int <- copy *top-addr
655   compare top-index, 0
656   {
657     break-if->
658     # if cursor at end, return (char before cursor == ')', 1)
659     stack <- get gap, left
660     top-addr <- get stack, top
661     top-index <- copy *top-addr
662     compare top-index, 0
663     {
664       break-if->
665       return 0/false, 0
666     }
667     top-index <- decrement
668     var data-ah/eax: (addr handle array grapheme) <- get stack, data
669     var data/eax: (addr array grapheme) <- lookup *data-ah
670     var g/eax: (addr grapheme) <- index data, top-index
671     compare *g, 0x29/close-paren
672     {
673       break-if-=
674       return 0/false, 0
675     }
676     return 1/true, 1
677   }
678   # cursor is not at end; return (char at cursor == ')')
679   top-index <- decrement
680   var data-ah/eax: (addr handle array grapheme) <- get stack, data
681   var data/eax: (addr array grapheme) <- lookup *data-ah
682   var g/eax: (addr grapheme) <- index data, top-index
683   compare *g, 0x29/close-paren
684   {
685     break-if-=
686     return 0/false, 0
687   }
688   return 1/true, 0
689 }
690 
691 fn test-highlight-matching-open-paren {
692   var gap-storage: gap-buffer
693   var gap/esi: (addr gap-buffer) <- address gap-storage
694   initialize-gap-buffer-with gap, "(a)"
695   gap-to-end gap
696   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
697   var open-paren-depth/edi: int <- copy 0
698   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 0/no-cursor
699   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: no cursor"
700   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
701   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: at end immediately after ')'"
702   check-ints-equal open-paren-depth, 1, "F - test-highlight-matching-open-paren: depth at end immediately after ')'"
703   var dummy/eax: grapheme <- gap-left gap
704   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
705   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: on ')'"
706   dummy <- gap-left gap
707   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
708   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: not on ')'"
709 }
710 
711 ## some primitives for scanning through a gap buffer
712 # don't modify the gap buffer while scanning
713 # this includes moving the cursor around
714 
715 # restart scan without affecting gap-buffer contents
716 fn rewind-gap-buffer _self: (addr gap-buffer) {
717   var self/esi: (addr gap-buffer) <- copy _self
718   var dest/eax: (addr int) <- get self, left-read-index
719   copy-to *dest, 0
720   dest <- get self, right-read-index
721   copy-to *dest, 0
722 }
723 
724 fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
725   var self/esi: (addr gap-buffer) <- copy _self
726   # more in left?
727   var left/eax: (addr grapheme-stack) <- get self, left
728   var left-size/eax: int <- grapheme-stack-length left
729   var left-read-index/ecx: (addr int) <- get self, left-read-index
730   compare *left-read-index, left-size
731   {
732     break-if->=
733     return 0/false
734   }
735   # more in right?
736   var right/eax: (addr grapheme-stack) <- get self, right
737   var right-size/eax: int <- grapheme-stack-length right
738   var right-read-index/ecx: (addr int) <- get self, right-read-index
739   compare *right-read-index, right-size
740   {
741     break-if->=
742     return 0/false
743   }
744   #
745   return 1/true
746 }
747 
748 fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
749   var self/esi: (addr gap-buffer) <- copy _self
750   # more in left?
751   var left/ecx: (addr grapheme-stack) <- get self, left
752   var left-size/eax: int <- grapheme-stack-length left
753   var left-read-index-a/edx: (addr int) <- get self, left-read-index
754   compare *left-read-index-a, left-size
755   {
756     break-if->=
757     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
758     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
759     var left-read-index/ecx: int <- copy *left-read-index-a
760     var result/eax: (addr grapheme) <- index left-data, left-read-index
761     return *result
762   }
763   # more in right?
764   var right/ecx: (addr grapheme-stack) <- get self, right
765   var _right-size/eax: int <- grapheme-stack-length right
766   var right-size/ebx: int <- copy _right-size
767   var right-read-index-a/edx: (addr int) <- get self, right-read-index
768   compare *right-read-index-a, right-size
769   {
770     break-if->=
771     # read the right from reverse
772     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
773     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
774     var right-read-index/ebx: int <- copy right-size
775     right-read-index <- subtract *right-read-index-a
776     right-read-index <- subtract 1
777     var result/eax: (addr grapheme) <- index right-data, right-read-index
778     return *result
779   }
780   # if we get here there's nothing left
781   return 0/nul
782 }
783 
784 fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
785   var self/esi: (addr gap-buffer) <- copy _self
786   # more in left?
787   var left/ecx: (addr grapheme-stack) <- get self, left
788   var left-size/eax: int <- grapheme-stack-length left
789   var left-read-index-a/edx: (addr int) <- get self, left-read-index
790   compare *left-read-index-a, left-size
791   {
792     break-if->=
793     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
794     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
795     var left-read-index/ecx: int <- copy *left-read-index-a
796     var result/eax: (addr grapheme) <- index left-data, left-read-index
797     increment *left-read-index-a
798     return *result
799   }
800   # more in right?
801   var right/ecx: (addr grapheme-stack) <- get self, right
802   var _right-size/eax: int <- grapheme-stack-length right
803   var right-size/ebx: int <- copy _right-size
804   var right-read-index-a/edx: (addr int) <- get self, right-read-index
805   compare *right-read-index-a, right-size
806   {
807     break-if->=
808     # read the right from reverse
809     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
810     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
811     var right-read-index/ebx: int <- copy right-size
812     right-read-index <- subtract *right-read-index-a
813     right-read-index <- subtract 1
814     var result/eax: (addr grapheme) <- index right-data, right-read-index
815     increment *right-read-index-a
816     return *result
817   }
818   # if we get here there's nothing left
819   return 0/nul
820 }
821 
822 fn test-read-from-gap-buffer {
823   var gap-storage: gap-buffer
824   var gap/esi: (addr gap-buffer) <- address gap-storage
825   initialize-gap-buffer-with gap, "abc"
826   # gap is at end, all contents are in left
827   var done?/eax: boolean <- gap-buffer-scan-done? gap
828   check-not done?, "F - test-read-from-gap-buffer/left-1/done"
829   var g/eax: grapheme <- read-from-gap-buffer gap
830   var x/ecx: int <- copy g
831   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
832   var done?/eax: boolean <- gap-buffer-scan-done? gap
833   check-not done?, "F - test-read-from-gap-buffer/left-2/done"
834   var g/eax: grapheme <- read-from-gap-buffer gap
835   var x/ecx: int <- copy g
836   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
837   var done?/eax: boolean <- gap-buffer-scan-done? gap
838   check-not done?, "F - test-read-from-gap-buffer/left-3/done"
839   var g/eax: grapheme <- read-from-gap-buffer gap
840   var x/ecx: int <- copy g
841   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
842   var done?/eax: boolean <- gap-buffer-scan-done? gap
843   check done?, "F - test-read-from-gap-buffer/left-4/done"
844   var g/eax: grapheme <- read-from-gap-buffer gap
845   var x/ecx: int <- copy g
846   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
847   # now check when everything is to the right
848   gap-to-start gap
849   rewind-gap-buffer gap
850   var done?/eax: boolean <- gap-buffer-scan-done? gap
851   check-not done?, "F - test-read-from-gap-buffer/right-1/done"
852   var g/eax: grapheme <- read-from-gap-buffer gap
853   var x/ecx: int <- copy g
854   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
855   var done?/eax: boolean <- gap-buffer-scan-done? gap
856   check-not done?, "F - test-read-from-gap-buffer/right-2/done"
857   var g/eax: grapheme <- read-from-gap-buffer gap
858   var x/ecx: int <- copy g
859   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
860   var done?/eax: boolean <- gap-buffer-scan-done? gap
861   check-not done?, "F - test-read-from-gap-buffer/right-3/done"
862   var g/eax: grapheme <- read-from-gap-buffer gap
863   var x/ecx: int <- copy g
864   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
865   var done?/eax: boolean <- gap-buffer-scan-done? gap
866   check done?, "F - test-read-from-gap-buffer/right-4/done"
867   var g/eax: grapheme <- read-from-gap-buffer gap
868   var x/ecx: int <- copy g
869   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
870 }
871 
872 fn skip-whitespace-from-gap-buffer self: (addr gap-buffer) {
873   var done?/eax: boolean <- gap-buffer-scan-done? self
874   compare done?, 0/false
875   break-if-!=
876   var g/eax: grapheme <- peek-from-gap-buffer self
877   {
878     compare g, 0x20/space
879     break-if-=
880     compare g, 0xa/newline
881     break-if-=
882     return
883   }
884   g <- read-from-gap-buffer self
885   loop
886 }
887 
888 fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
889   var g/edx: grapheme <- copy key
890   {
891     compare g, 8/backspace
892     break-if-!=
893     delete-before-gap self
894     return
895   }
896   {
897     compare g, 0x80/left-arrow
898     break-if-!=
899     var dummy/eax: grapheme <- gap-left self
900     return
901   }
902   {
903     compare g, 0x83/right-arrow
904     break-if-!=
905     var dummy/eax: grapheme <- gap-right self
906     return
907   }
908   {
909     compare g, 1/ctrl-a
910     break-if-!=
911     gap-to-start self
912     return
913   }
914   {
915     compare g, 5/ctrl-e
916     break-if-!=
917     gap-to-end self
918     return
919   }
920   {
921     compare g, 0x15/ctrl-u
922     break-if-!=
923     clear-gap-buffer self
924     return
925   }
926   # default: insert character
927   add-grapheme-at-gap self, g
928 }