https://github.com/akkartik/mu/blob/main/shell/gap-buffer.mu

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