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), capacity: 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, capacity
      15   var right/eax: (addr grapheme-stack) <- get self, right
      16   initialize-grapheme-stack right, capacity
      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 fn gap-buffer-capacity _gap: (addr gap-buffer) -> _/ecx: int {
      28   var gap/esi: (addr gap-buffer) <- copy _gap
      29   var left/eax: (addr grapheme-stack) <- get gap, left
      30   var left-data-ah/eax: (addr handle array grapheme) <- get left, data
      31   var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
      32   var result/eax: int <- length left-data
      33   return result
      34 }
      35
      36 # just for tests
      37 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
      38   initialize-gap-buffer self, 0x10/capacity
      39   var stream-storage: (stream byte 0x10/capacity)
      40   var stream/ecx: (addr stream byte) <- address stream-storage
      41   write stream, s
      42   {
      43     var done?/eax: boolean <- stream-empty? stream
      44     compare done?, 0/false
      45     break-if-!=
      46     var g/eax: grapheme <- read-grapheme stream
      47     add-grapheme-at-gap self, g
      48     loop
      49   }
      50 }
      51
      52 fn load-gap-buffer-from-stream self: (addr gap-buffer), in: (addr stream byte) {
      53   rewind-stream in
      54   {
      55     var done?/eax: boolean <- stream-empty? in
      56     compare done?, 0/false
      57     break-if-!=
      58     var key/eax: byte <- read-byte in
      59     compare key, 0/null
      60     break-if-=
      61     var g/eax: grapheme <- copy key
      62     edit-gap-buffer self, g
      63     loop
      64   }
      65 }
      66
      67 fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
      68   var self/esi: (addr gap-buffer) <- copy _self
      69   clear-stream out
      70   var left/eax: (addr grapheme-stack) <- get self, left
      71   emit-stack-from-bottom left, out
      72   var right/eax: (addr grapheme-stack) <- get self, right
      73   emit-stack-from-top right, out
      74 }
      75
      76 fn append-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
      77   var self/esi: (addr gap-buffer) <- copy _self
      78   var left/eax: (addr grapheme-stack) <- get self, left
      79   emit-stack-from-bottom left, out
      80   var right/eax: (addr grapheme-stack) <- get self, right
      81   emit-stack-from-top right, out
      82 }
      83
      84 # dump stack from bottom to top
      85 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
      86   var self/esi: (addr grapheme-stack) <- copy _self
      87   var data-ah/edi: (addr handle array grapheme) <- get self, data
      88   var _data/eax: (addr array grapheme) <- lookup *data-ah
      89   var data/edi: (addr array grapheme) <- copy _data
      90   var top-addr/ecx: (addr int) <- get self, top
      91   var i/eax: int <- copy 0
      92   {
      93     compare i, *top-addr
      94     break-if->=
      95     var g/edx: (addr grapheme) <- index data, i
      96     write-grapheme out, *g
      97     i <- increment
      98     loop
      99   }
     100 }
     101
     102 # dump stack from top to bottom
     103 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
     104   var self/esi: (addr grapheme-stack) <- copy _self
     105   var data-ah/edi: (addr handle array grapheme) <- get self, data
     106   var _data/eax: (addr array grapheme) <- lookup *data-ah
     107   var data/edi: (addr array grapheme) <- copy _data
     108   var top-addr/ecx: (addr int) <- get self, top
     109   var i/eax: int <- copy *top-addr
     110   i <- decrement
     111   {
     112     compare i, 0
     113     break-if-<
     114     var g/edx: (addr grapheme) <- index data, i
     115     write-grapheme out, *g
     116     i <- decrement
     117     loop
     118   }
     119 }
     120
     121 # We implicitly render everything editable in a single color, and assume the
     122 # cursor is a single other color.
     123 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, color: int, background-color: int -> _/eax: int, _/ecx: int {
     124   var gap/esi: (addr gap-buffer) <- copy _gap
     125   var left/edx: (addr grapheme-stack) <- get gap, left
     126   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
     127   var matching-open-paren-depth/edi: int <- copy 0
     128   highlight-matching-open-paren?, matching-open-paren-depth <- highlight-matching-open-paren? gap, render-cursor?
     129   var x2/eax: int <- copy 0
     130   var y2/ecx: int <- copy 0
     131   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, color, background-color
     132   var right/edx: (addr grapheme-stack) <- get gap, right
     133   x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, right, xmin, ymin, xmax, ymax, x2, y2, render-cursor?, color, background-color
     134   # decide whether we still need to print a cursor
     135   var bg/ebx: int <- copy background-color
     136   compare render-cursor?, 0/false
     137   {
     138     break-if-=
     139     # if the right side is empty, grapheme stack didn't print the cursor
     140     var empty?/eax: boolean <- grapheme-stack-empty? right
     141     compare empty?, 0/false
     142     break-if-=
     143     bg <- copy 7/cursor
     144   }
     145   # print a grapheme either way so that cursor position doesn't affect printed width
     146   var space/edx: grapheme <- copy 0x20
     147   x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, color, bg
     148   return x2, y2
     149 }
     150
     151 fn render-gap-buffer screen: (addr screen), gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean, color: int, background-color: int -> _/eax: int {
     152   var _width/eax: int <- copy 0
     153   var _height/ecx: int <- copy 0
     154   _width, _height <- screen-size screen
     155   var width/edx: int <- copy _width
     156   var height/ebx: int <- copy _height
     157   var x2/eax: int <- copy 0
     158   var y2/ecx: int <- copy 0
     159   x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?, color, background-color
     160   return x2  # y2? yolo
     161 }
     162
     163 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
     164   var gap/esi: (addr gap-buffer) <- copy _gap
     165   var left/eax: (addr grapheme-stack) <- get gap, left
     166   var tmp/eax: (addr int) <- get left, top
     167   var left-length/ecx: int <- copy *tmp
     168   var right/esi: (addr grapheme-stack) <- get gap, right
     169   tmp <- get right, top
     170   var result/eax: int <- copy *tmp
     171   result <- add left-length
     172   return result
     173 }
     174
     175 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
     176   var self/esi: (addr gap-buffer) <- copy _self
     177   var left/eax: (addr grapheme-stack) <- get self, left
     178   push-grapheme-stack left, g
     179 }
     180
     181 fn add-code-point-at-gap self: (addr gap-buffer), c: code-point {
     182   var g/eax: grapheme <- copy c
     183   add-grapheme-at-gap self, g
     184 }
     185
     186 fn gap-to-start self: (addr gap-buffer) {
     187   {
     188     var curr/eax: grapheme <- gap-left self
     189     compare curr, -1
     190     loop-if-!=
     191   }
     192 }
     193
     194 fn gap-to-end self: (addr gap-buffer) {
     195   {
     196     var curr/eax: grapheme <- gap-right self
     197     compare curr, -1
     198     loop-if-!=
     199   }
     200 }
     201
     202 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
     203   var self/esi: (addr gap-buffer) <- copy _self
     204   var left/eax: (addr grapheme-stack) <- get self, left
     205   var result/eax: boolean <- grapheme-stack-empty? left
     206   return result
     207 }
     208
     209 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
     210   var self/esi: (addr gap-buffer) <- copy _self
     211   var right/eax: (addr grapheme-stack) <- get self, right
     212   var result/eax: boolean <- grapheme-stack-empty? right
     213   return result
     214 }
     215
     216 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
     217   var self/esi: (addr gap-buffer) <- copy _self
     218   var g/eax: grapheme <- copy 0
     219   var right/ecx: (addr grapheme-stack) <- get self, right
     220   g <- pop-grapheme-stack right
     221   compare g, -1
     222   {
     223     break-if-=
     224     var left/ecx: (addr grapheme-stack) <- get self, left
     225     push-grapheme-stack left, g
     226   }
     227   return g
     228 }
     229
     230 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
     231   var self/esi: (addr gap-buffer) <- copy _self
     232   var g/eax: grapheme <- copy 0
     233   {
     234     var left/ecx: (addr grapheme-stack) <- get self, left
     235     g <- pop-grapheme-stack left
     236   }
     237   compare g, -1
     238   {
     239     break-if-=
     240     var right/ecx: (addr grapheme-stack) <- get self, right
     241     push-grapheme-stack right, g
     242   }
     243   return g
     244 }
     245
     246 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
     247   var self/eax: (addr gap-buffer) <- copy _self
     248   var left/eax: (addr grapheme-stack) <- get self, left
     249   var top-addr/eax: (addr int) <- get left, top
     250   var result/eax: int <- copy *top-addr
     251   return result
     252 }
     253
     254 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     255   var self/esi: (addr gap-buffer) <- copy _self
     256   # try to read from left
     257   var left/eax: (addr grapheme-stack) <- get self, left
     258   var top-addr/ecx: (addr int) <- get left, top
     259   compare *top-addr, 0
     260   {
     261     break-if-<=
     262     var data-ah/eax: (addr handle array grapheme) <- get left, data
     263     var data/eax: (addr array grapheme) <- lookup *data-ah
     264     var result-addr/eax: (addr grapheme) <- index data, 0
     265     return *result-addr
     266   }
     267   # try to read from right
     268   var right/eax: (addr grapheme-stack) <- get self, right
     269   top-addr <- get right, top
     270   compare *top-addr, 0
     271   {
     272     break-if-<=
     273     var data-ah/eax: (addr handle array grapheme) <- get right, data
     274     var data/eax: (addr array grapheme) <- lookup *data-ah
     275     var top/ecx: int <- copy *top-addr
     276     top <- decrement
     277     var result-addr/eax: (addr grapheme) <- index data, top
     278     return *result-addr
     279   }
     280   # give up
     281   return -1
     282 }
     283
     284 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     285   var self/esi: (addr gap-buffer) <- copy _self
     286   # try to read from left
     287   var left/ecx: (addr grapheme-stack) <- get self, left
     288   var top-addr/edx: (addr int) <- get left, top
     289   compare *top-addr, 0
     290   {
     291     break-if-<=
     292     var result/eax: grapheme <- pop-grapheme-stack left
     293     push-grapheme-stack left, result
     294     return result
     295   }
     296   # give up
     297   return -1
     298 }
     299
     300 fn delete-before-gap _self: (addr gap-buffer) {
     301   var self/eax: (addr gap-buffer) <- copy _self
     302   var left/eax: (addr grapheme-stack) <- get self, left
     303   var dummy/eax: grapheme <- pop-grapheme-stack left
     304 }
     305
     306 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
     307   var self/eax: (addr gap-buffer) <- copy _self
     308   var right/eax: (addr grapheme-stack) <- get self, right
     309   var result/eax: grapheme <- pop-grapheme-stack right
     310   return result
     311 }
     312
     313 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
     314   var self/esi: (addr gap-buffer) <- copy _self
     315   # complication: graphemes may be multiple bytes
     316   # so don't rely on length
     317   # instead turn the expected result into a stream and arrange to read from it in order
     318   var stream-storage: (stream byte 0x10/capacity)
     319   var expected-stream/ecx: (addr stream byte) <- address stream-storage
     320   write expected-stream, s
     321   # compare left
     322   var left/edx: (addr grapheme-stack) <- get self, left
     323   var result/eax: boolean <- prefix-match? left, expected-stream
     324   compare result, 0/false
     325   {
     326     break-if-!=
     327     return result
     328   }
     329   # compare right
     330   var right/edx: (addr grapheme-stack) <- get self, right
     331   result <- suffix-match? right, expected-stream
     332   compare result, 0/false
     333   {
     334     break-if-!=
     335     return result
     336   }
     337   # ensure there's nothing left over
     338   result <- stream-empty? expected-stream
     339   return result
     340 }
     341
     342 fn test-gap-buffer-equal-from-end {
     343   var _g: gap-buffer
     344   var g/esi: (addr gap-buffer) <- address _g
     345   initialize-gap-buffer g, 0x10
     346   #
     347   add-code-point-at-gap g, 0x61/a
     348   add-code-point-at-gap g, 0x61/a
     349   add-code-point-at-gap g, 0x61/a
     350   # gap is at end (right is empty)
     351   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     352   check result, "F - test-gap-buffer-equal-from-end"
     353 }
     354
     355 fn test-gap-buffer-equal-from-middle {
     356   var _g: gap-buffer
     357   var g/esi: (addr gap-buffer) <- address _g
     358   initialize-gap-buffer g, 0x10
     359   #
     360   add-code-point-at-gap g, 0x61/a
     361   add-code-point-at-gap g, 0x61/a
     362   add-code-point-at-gap g, 0x61/a
     363   var dummy/eax: grapheme <- gap-left g
     364   # gap is in the middle
     365   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     366   check result, "F - test-gap-buffer-equal-from-middle"
     367 }
     368
     369 fn test-gap-buffer-equal-from-start {
     370   var _g: gap-buffer
     371   var g/esi: (addr gap-buffer) <- address _g
     372   initialize-gap-buffer g, 0x10
     373   #
     374   add-code-point-at-gap g, 0x61/a
     375   add-code-point-at-gap g, 0x61/a
     376   add-code-point-at-gap g, 0x61/a
     377   var dummy/eax: grapheme <- gap-left g
     378   dummy <- gap-left g
     379   dummy <- gap-left g
     380   # gap is at the start
     381   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     382   check result, "F - test-gap-buffer-equal-from-start"
     383 }
     384
     385 fn test-gap-buffer-equal-fails {
     386   # g = "aaa"
     387   var _g: gap-buffer
     388   var g/esi: (addr gap-buffer) <- address _g
     389   initialize-gap-buffer g, 0x10
     390   add-code-point-at-gap g, 0x61/a
     391   add-code-point-at-gap g, 0x61/a
     392   add-code-point-at-gap g, 0x61/a
     393   #
     394   var result/eax: boolean <- gap-buffer-equal? g, "aa"
     395   check-not result, "F - test-gap-buffer-equal-fails"
     396 }
     397
     398 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
     399   var tmp/eax: int <- gap-buffer-length self
     400   var len/ecx: int <- copy tmp
     401   var leng/eax: int <- gap-buffer-length g
     402   compare len, leng
     403   {
     404     break-if-=
     405     return 0/false
     406   }
     407   var i/edx: int <- copy 0
     408   {
     409     compare i, len
     410     break-if->=
     411     {
     412       var tmp/eax: grapheme <- gap-index self, i
     413       var curr/ecx: grapheme <- copy tmp
     414       var currg/eax: grapheme <- gap-index g, i
     415       compare curr, currg
     416       break-if-=
     417       return 0/false
     418     }
     419     i <- increment
     420     loop
     421   }
     422   return 1/true
     423 }
     424
     425 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
     426   var self/esi: (addr gap-buffer) <- copy _self
     427   var n/ebx: int <- copy _n
     428   # if n < left->length, index into left
     429   var left/edi: (addr grapheme-stack) <- get self, left
     430   var left-len-a/edx: (addr int) <- get left, top
     431   compare n, *left-len-a
     432   {
     433     break-if->=
     434     var data-ah/eax: (addr handle array grapheme) <- get left, data
     435     var data/eax: (addr array grapheme) <- lookup *data-ah
     436     var result/eax: (addr grapheme) <- index data, n
     437     return *result
     438   }
     439   # shrink n
     440   n <- subtract *left-len-a
     441   # if n < right->length, index into right
     442   var right/edi: (addr grapheme-stack) <- get self, right
     443   var right-len-a/edx: (addr int) <- get right, top
     444   compare n, *right-len-a
     445   {
     446     break-if->=
     447     var data-ah/eax: (addr handle array grapheme) <- get right, data
     448     var data/eax: (addr array grapheme) <- lookup *data-ah
     449     # idx = right->len - n - 1
     450     var idx/ebx: int <- copy n
     451     idx <- subtract *right-len-a
     452     idx <- negate
     453     idx <- subtract 1
     454     var result/eax: (addr grapheme) <- index data, idx
     455     return *result
     456   }
     457   # error
     458   abort "gap-index: out of bounds"
     459   return 0
     460 }
     461
     462 fn test-gap-buffers-equal? {
     463   var _a: gap-buffer
     464   var a/esi: (addr gap-buffer) <- address _a
     465   initialize-gap-buffer-with a, "abc"
     466   var _b: gap-buffer
     467   var b/edi: (addr gap-buffer) <- address _b
     468   initialize-gap-buffer-with b, "abc"
     469   var _c: gap-buffer
     470   var c/ebx: (addr gap-buffer) <- address _c
     471   initialize-gap-buffer-with c, "ab"
     472   var _d: gap-buffer
     473   var d/edx: (addr gap-buffer) <- address _d
     474   initialize-gap-buffer-with d, "abd"
     475   #
     476   var result/eax: boolean <- gap-buffers-equal? a, a
     477   check result, "F - test-gap-buffers-equal? - reflexive"
     478   result <- gap-buffers-equal? a, b
     479   check result, "F - test-gap-buffers-equal? - equal"
     480   # length not equal
     481   result <- gap-buffers-equal? a, c
     482   check-not result, "F - test-gap-buffers-equal? - not equal"
     483   # contents not equal
     484   result <- gap-buffers-equal? a, d
     485   check-not result, "F - test-gap-buffers-equal? - not equal 2"
     486   result <- gap-buffers-equal? d, a
     487   check-not result, "F - test-gap-buffers-equal? - not equal 3"
     488 }
     489
     490 fn test-gap-buffer-index {
     491   var gap-storage: gap-buffer
     492   var gap/esi: (addr gap-buffer) <- address gap-storage
     493   initialize-gap-buffer-with gap, "abc"
     494   # gap is at end, all contents are in left
     495   var g/eax: grapheme <- gap-index gap, 0
     496   var x/ecx: int <- copy g
     497   check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
     498   var g/eax: grapheme <- gap-index gap, 1
     499   var x/ecx: int <- copy g
     500   check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
     501   var g/eax: grapheme <- gap-index gap, 2
     502   var x/ecx: int <- copy g
     503   check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
     504   # now check when everything is to the right
     505   gap-to-start gap
     506   rewind-gap-buffer gap
     507   var g/eax: grapheme <- gap-index gap, 0
     508   var x/ecx: int <- copy g
     509   check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
     510   var g/eax: grapheme <- gap-index gap, 1
     511   var x/ecx: int <- copy g
     512   check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
     513   var g/eax: grapheme <- gap-index gap, 2
     514   var x/ecx: int <- copy g
     515   check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
     516 }
     517
     518 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
     519   # obtain src-a, dest-a
     520   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
     521   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
     522   var src-a/esi: (addr gap-buffer) <- copy _src-a
     523   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
     524   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
     525   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
     526   # copy left grapheme-stack
     527   var src/ecx: (addr grapheme-stack) <- get src-a, left
     528   var dest/edx: (addr grapheme-stack) <- get dest-a, left
     529   copy-grapheme-stack src, dest
     530   # copy right grapheme-stack
     531   src <- get src-a, right
     532   dest <- get dest-a, right
     533   copy-grapheme-stack src, dest
     534 }
     535
     536 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
     537   var self/esi: (addr gap-buffer) <- copy _self
     538   var curr/ecx: (addr grapheme-stack) <- get self, left
     539   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
     540   {
     541     compare result, 0/false
     542     break-if-=
     543     curr <- get self, right
     544     result <- grapheme-stack-is-decimal-integer? curr
     545   }
     546   return result
     547 }
     548
     549 fn test-render-gap-buffer-without-cursor {
     550   # setup
     551   var gap-storage: gap-buffer
     552   var gap/esi: (addr gap-buffer) <- address gap-storage
     553   initialize-gap-buffer-with gap, "abc"
     554   # setup: screen
     555   var screen-on-stack: screen
     556   var screen/edi: (addr screen) <- address screen-on-stack
     557   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     558   #
     559   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor, 3/fg, 0xc5/bg=blue-bg
     560   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
     561   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
     562                                                                 # abc
     563   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
     564 }
     565
     566 fn test-render-gap-buffer-with-cursor-at-end {
     567   # setup
     568   var gap-storage: gap-buffer
     569   var gap/esi: (addr gap-buffer) <- address gap-storage
     570   initialize-gap-buffer-with gap, "abc"
     571   gap-to-end gap
     572   # setup: screen
     573   var screen-on-stack: screen
     574   var screen/edi: (addr screen) <- address screen-on-stack
     575   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     576   #
     577   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     578   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
     579   # we've drawn one extra grapheme for the cursor
     580   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
     581                                                                 # abc
     582   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
     583 }
     584
     585 fn test-render-gap-buffer-with-cursor-in-middle {
     586   # setup
     587   var gap-storage: gap-buffer
     588   var gap/esi: (addr gap-buffer) <- address gap-storage
     589   initialize-gap-buffer-with gap, "abc"
     590   gap-to-end gap
     591   var dummy/eax: grapheme <- gap-left gap
     592   # setup: screen
     593   var screen-on-stack: screen
     594   var screen/edi: (addr screen) <- address screen-on-stack
     595   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     596   #
     597   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     598   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
     599   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
     600                                                                 # abc
     601   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
     602 }
     603
     604 fn test-render-gap-buffer-with-cursor-at-start {
     605   var gap-storage: gap-buffer
     606   var gap/esi: (addr gap-buffer) <- address gap-storage
     607   initialize-gap-buffer-with gap, "abc"
     608   gap-to-start gap
     609   # setup: screen
     610   var screen-on-stack: screen
     611   var screen/edi: (addr screen) <- address screen-on-stack
     612   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     613   #
     614   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     615   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
     616   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
     617                                                                 # abc
     618   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
     619 }
     620
     621 fn test-render-gap-buffer-highlight-matching-close-paren {
     622   var gap-storage: gap-buffer
     623   var gap/esi: (addr gap-buffer) <- address gap-storage
     624   initialize-gap-buffer-with gap, "(a)"
     625   gap-to-start gap
     626   # setup: screen
     627   var screen-on-stack: screen
     628   var screen/edi: (addr screen) <- address screen-on-stack
     629   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     630   #
     631   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     632   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-close-paren"
     633   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-close-paren: result"
     634   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "|   ", "F - test-render-gap-buffer-highlight-matching-close-paren: cursor"
     635   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "  ) ", "F - test-render-gap-buffer-highlight-matching-close-paren: matching paren"
     636 }
     637
     638 fn test-render-gap-buffer-highlight-matching-open-paren {
     639   var gap-storage: gap-buffer
     640   var gap/esi: (addr gap-buffer) <- address gap-storage
     641   initialize-gap-buffer-with gap, "(a)"
     642   gap-to-end gap
     643   var dummy/eax: grapheme <- gap-left gap
     644   # setup: screen
     645   var screen-on-stack: screen
     646   var screen/edi: (addr screen) <- address screen-on-stack
     647   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     648   #
     649   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     650   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren"
     651   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren: result"
     652   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "  | ", "F - test-render-gap-buffer-highlight-matching-open-paren: cursor"
     653   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren: matching paren"
     654 }
     655
     656 fn test-render-gap-buffer-highlight-matching-open-paren-of-end {
     657   var gap-storage: gap-buffer
     658   var gap/esi: (addr gap-buffer) <- address gap-storage
     659   initialize-gap-buffer-with gap, "(a)"
     660   gap-to-end gap
     661   # setup: screen
     662   var screen-on-stack: screen
     663   var screen/edi: (addr screen) <- address screen-on-stack
     664   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     665   #
     666   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     667   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end"
     668   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: result"
     669   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "   |", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: cursor"
     670   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: matching paren"
     671 }
     672
     673 # should I highlight a matching open paren? And if so, at what depth from top of left?
     674 # basically there are two cases to disambiguate here:
     675 #   Usually the cursor is at top of right. Highlight first '(' at depth 0 from top of left.
     676 #   If right is empty, match the ')' _before_ cursor. Highlight first '(' at depth _1_ from top of left.
     677 fn highlight-matching-open-paren? _gap: (addr gap-buffer), render-cursor?: boolean -> _/ebx: boolean, _/edi: int {
     678   # if not rendering cursor, return
     679   compare render-cursor?, 0/false
     680   {
     681     break-if-!=
     682     return 0/false, 0
     683   }
     684   var gap/esi: (addr gap-buffer) <- copy _gap
     685   var stack/edi: (addr grapheme-stack) <- get gap, right
     686   var top-addr/eax: (addr int) <- get stack, top
     687   var top-index/ecx: int <- copy *top-addr
     688   compare top-index, 0
     689   {
     690     break-if->
     691     # if cursor at end, return (char before cursor == ')', 1)
     692     stack <- get gap, left
     693     top-addr <- get stack, top
     694     top-index <- copy *top-addr
     695     compare top-index, 0
     696     {
     697       break-if->
     698       return 0/false, 0
     699     }
     700     top-index <- decrement
     701     var data-ah/eax: (addr handle array grapheme) <- get stack, data
     702     var data/eax: (addr array grapheme) <- lookup *data-ah
     703     var g/eax: (addr grapheme) <- index data, top-index
     704     compare *g, 0x29/close-paren
     705     {
     706       break-if-=
     707       return 0/false, 0
     708     }
     709     return 1/true, 1
     710   }
     711   # cursor is not at end; return (char at cursor == ')')
     712   top-index <- decrement
     713   var data-ah/eax: (addr handle array grapheme) <- get stack, data
     714   var data/eax: (addr array grapheme) <- lookup *data-ah
     715   var g/eax: (addr grapheme) <- index data, top-index
     716   compare *g, 0x29/close-paren
     717   {
     718     break-if-=
     719     return 0/false, 0
     720   }
     721   return 1/true, 0
     722 }
     723
     724 fn test-highlight-matching-open-paren {
     725   var gap-storage: gap-buffer
     726   var gap/esi: (addr gap-buffer) <- address gap-storage
     727   initialize-gap-buffer-with gap, "(a)"
     728   gap-to-end gap
     729   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
     730   var open-paren-depth/edi: int <- copy 0
     731   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 0/no-cursor
     732   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: no cursor"
     733   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
     734   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: at end immediately after ')'"
     735   check-ints-equal open-paren-depth, 1, "F - test-highlight-matching-open-paren: depth at end immediately after ')'"
     736   var dummy/eax: grapheme <- gap-left gap
     737   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
     738   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: on ')'"
     739   dummy <- gap-left gap
     740   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
     741   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: not on ')'"
     742 }
     743
     744 ## some primitives for scanning through a gap buffer
     745 # don't modify the gap buffer while scanning
     746 # this includes moving the cursor around
     747
     748 # restart scan without affecting gap-buffer contents
     749 fn rewind-gap-buffer _self: (addr gap-buffer) {
     750   var self/esi: (addr gap-buffer) <- copy _self
     751   var dest/eax: (addr int) <- get self, left-read-index
     752   copy-to *dest, 0
     753   dest <- get self, right-read-index
     754   copy-to *dest, 0
     755 }
     756
     757 fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
     758   var self/esi: (addr gap-buffer) <- copy _self
     759   # more in left?
     760   var left/eax: (addr grapheme-stack) <- get self, left
     761   var left-size/eax: int <- grapheme-stack-length left
     762   var left-read-index/ecx: (addr int) <- get self, left-read-index
     763   compare *left-read-index, left-size
     764   {
     765     break-if->=
     766     return 0/false
     767   }
     768   # more in right?
     769   var right/eax: (addr grapheme-stack) <- get self, right
     770   var right-size/eax: int <- grapheme-stack-length right
     771   var right-read-index/ecx: (addr int) <- get self, right-read-index
     772   compare *right-read-index, right-size
     773   {
     774     break-if->=
     775     return 0/false
     776   }
     777   #
     778   return 1/true
     779 }
     780
     781 fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     782   var self/esi: (addr gap-buffer) <- copy _self
     783   # more in left?
     784   var left/ecx: (addr grapheme-stack) <- get self, left
     785   var left-size/eax: int <- grapheme-stack-length left
     786   var left-read-index-a/edx: (addr int) <- get self, left-read-index
     787   compare *left-read-index-a, left-size
     788   {
     789     break-if->=
     790     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
     791     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
     792     var left-read-index/ecx: int <- copy *left-read-index-a
     793     var result/eax: (addr grapheme) <- index left-data, left-read-index
     794     return *result
     795   }
     796   # more in right?
     797   var right/ecx: (addr grapheme-stack) <- get self, right
     798   var _right-size/eax: int <- grapheme-stack-length right
     799   var right-size/ebx: int <- copy _right-size
     800   var right-read-index-a/edx: (addr int) <- get self, right-read-index
     801   compare *right-read-index-a, right-size
     802   {
     803     break-if->=
     804     # read the right from reverse
     805     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
     806     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
     807     var right-read-index/ebx: int <- copy right-size
     808     right-read-index <- subtract *right-read-index-a
     809     right-read-index <- subtract 1
     810     var result/eax: (addr grapheme) <- index right-data, right-read-index
     811     return *result
     812   }
     813   # if we get here there's nothing left
     814   return 0/nul
     815 }
     816
     817 fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     818   var self/esi: (addr gap-buffer) <- copy _self
     819   # more in left?
     820   var left/ecx: (addr grapheme-stack) <- get self, left
     821   var left-size/eax: int <- grapheme-stack-length left
     822   var left-read-index-a/edx: (addr int) <- get self, left-read-index
     823   compare *left-read-index-a, left-size
     824   {
     825     break-if->=
     826     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
     827     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
     828     var left-read-index/ecx: int <- copy *left-read-index-a
     829     var result/eax: (addr grapheme) <- index left-data, left-read-index
     830     increment *left-read-index-a
     831     return *result
     832   }
     833   # more in right?
     834   var right/ecx: (addr grapheme-stack) <- get self, right
     835   var _right-size/eax: int <- grapheme-stack-length right
     836   var right-size/ebx: int <- copy _right-size
     837   var right-read-index-a/edx: (addr int) <- get self, right-read-index
     838   compare *right-read-index-a, right-size
     839   {
     840     break-if->=
     841     # read the right from reverse
     842     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
     843     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
     844     var right-read-index/ebx: int <- copy right-size
     845     right-read-index <- subtract *right-read-index-a
     846     right-read-index <- subtract 1
     847     var result/eax: (addr grapheme) <- index right-data, right-read-index
     848     increment *right-read-index-a
     849     return *result
     850   }
     851   # if we get here there's nothing left
     852   return 0/nul
     853 }
     854
     855 fn test-read-from-gap-buffer {
     856   var gap-storage: gap-buffer
     857   var gap/esi: (addr gap-buffer) <- address gap-storage
     858   initialize-gap-buffer-with gap, "abc"
     859   # gap is at end, all contents are in left
     860   var done?/eax: boolean <- gap-buffer-scan-done? gap
     861   check-not done?, "F - test-read-from-gap-buffer/left-1/done"
     862   var g/eax: grapheme <- read-from-gap-buffer gap
     863   var x/ecx: int <- copy g
     864   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
     865   var done?/eax: boolean <- gap-buffer-scan-done? gap
     866   check-not done?, "F - test-read-from-gap-buffer/left-2/done"
     867   var g/eax: grapheme <- read-from-gap-buffer gap
     868   var x/ecx: int <- copy g
     869   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
     870   var done?/eax: boolean <- gap-buffer-scan-done? gap
     871   check-not done?, "F - test-read-from-gap-buffer/left-3/done"
     872   var g/eax: grapheme <- read-from-gap-buffer gap
     873   var x/ecx: int <- copy g
     874   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
     875   var done?/eax: boolean <- gap-buffer-scan-done? gap
     876   check done?, "F - test-read-from-gap-buffer/left-4/done"
     877   var g/eax: grapheme <- read-from-gap-buffer gap
     878   var x/ecx: int <- copy g
     879   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
     880   # now check when everything is to the right
     881   gap-to-start gap
     882   rewind-gap-buffer gap
     883   var done?/eax: boolean <- gap-buffer-scan-done? gap
     884   check-not done?, "F - test-read-from-gap-buffer/right-1/done"
     885   var g/eax: grapheme <- read-from-gap-buffer gap
     886   var x/ecx: int <- copy g
     887   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
     888   var done?/eax: boolean <- gap-buffer-scan-done? gap
     889   check-not done?, "F - test-read-from-gap-buffer/right-2/done"
     890   var g/eax: grapheme <- read-from-gap-buffer gap
     891   var x/ecx: int <- copy g
     892   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
     893   var done?/eax: boolean <- gap-buffer-scan-done? gap
     894   check-not done?, "F - test-read-from-gap-buffer/right-3/done"
     895   var g/eax: grapheme <- read-from-gap-buffer gap
     896   var x/ecx: int <- copy g
     897   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
     898   var done?/eax: boolean <- gap-buffer-scan-done? gap
     899   check done?, "F - test-read-from-gap-buffer/right-4/done"
     900   var g/eax: grapheme <- read-from-gap-buffer gap
     901   var x/ecx: int <- copy g
     902   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
     903 }
     904
     905 fn skip-whitespace-from-gap-buffer self: (addr gap-buffer) {
     906   var done?/eax: boolean <- gap-buffer-scan-done? self
     907   compare done?, 0/false
     908   break-if-!=
     909   var g/eax: grapheme <- peek-from-gap-buffer self
     910   {
     911     compare g, 0x20/space
     912     break-if-=
     913     compare g, 0xa/newline
     914     break-if-=
     915     return
     916   }
     917   g <- read-from-gap-buffer self
     918   loop
     919 }
     920
     921 fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
     922   var g/edx: grapheme <- copy key
     923   {
     924     compare g, 8/backspace
     925     break-if-!=
     926     delete-before-gap self
     927     return
     928   }
     929   {
     930     compare g, 0x80/left-arrow
     931     break-if-!=
     932     var dummy/eax: grapheme <- gap-left self
     933     return
     934   }
     935   {
     936     compare g, 0x83/right-arrow
     937     break-if-!=
     938     var dummy/eax: grapheme <- gap-right self
     939     return
     940   }
     941   {
     942     compare g, 6/ctrl-f
     943     break-if-!=
     944     gap-to-start-of-next-word self
     945     return
     946   }
     947   {
     948     compare g, 2/ctrl-b
     949     break-if-!=
     950     gap-to-end-of-previous-word self
     951     return
     952   }
     953   {
     954     compare g, 1/ctrl-a
     955     break-if-!=
     956     gap-to-previous-start-of-line self
     957     return
     958   }
     959   {
     960     compare g, 5/ctrl-e
     961     break-if-!=
     962     gap-to-next-end-of-line self
     963     return
     964   }
     965   {
     966     compare g, 0x81/down-arrow
     967     break-if-!=
     968     gap-down self
     969     return
     970   }
     971   {
     972     compare g, 0x82/up-arrow
     973     break-if-!=
     974     gap-up self
     975     return
     976   }
     977   {
     978     compare g, 0x15/ctrl-u
     979     break-if-!=
     980     clear-gap-buffer self
     981     return
     982   }
     983   {
     984     compare g, 9/tab
     985     break-if-!=
     986     # tab = 2 spaces
     987     add-code-point-at-gap self, 0x20/space
     988     add-code-point-at-gap self, 0x20/space
     989     return
     990   }
     991   # default: insert character
     992   add-grapheme-at-gap self, g
     993 }
     994
     995 fn gap-to-start-of-next-word self: (addr gap-buffer) {
     996   var curr/eax: grapheme <- copy 0
     997   # skip to next space
     998   {
     999     curr <- gap-right self
    1000     compare curr, -1
    1001     break-if-=
    1002     compare curr, 0x20/space
    1003     break-if-=
    1004     compare curr, 0xa/newline
    1005     break-if-=
    1006     loop
    1007   }
    1008   # skip past spaces
    1009   {
    1010     curr <- gap-right self
    1011     compare curr, -1
    1012     break-if-=
    1013     compare curr, 0x20/space
    1014     loop-if-=
    1015     compare curr, 0xa/space
    1016     loop-if-=
    1017     curr <- gap-left self
    1018     break
    1019   }
    1020 }
    1021
    1022 fn gap-to-end-of-previous-word self: (addr gap-buffer) {
    1023   var curr/eax: grapheme <- copy 0
    1024   # skip to previous space
    1025   {
    1026     curr <- gap-left self
    1027     compare curr, -1
    1028     break-if-=
    1029     compare curr, 0x20/space
    1030     break-if-=
    1031     compare curr, 0xa/newline
    1032     break-if-=
    1033     loop
    1034   }
    1035   # skip past all spaces but one
    1036   {
    1037     curr <- gap-left self
    1038     compare curr, -1
    1039     break-if-=
    1040     compare curr, 0x20/space
    1041     loop-if-=
    1042     compare curr, 0xa/space
    1043     loop-if-=
    1044     curr <- gap-right self
    1045     break
    1046   }
    1047 }
    1048
    1049 fn gap-to-previous-start-of-line self: (addr gap-buffer) {
    1050   # skip past immediate newline
    1051   var dummy/eax: grapheme <- gap-left self
    1052   # skip to previous newline
    1053   {
    1054     dummy <- gap-left self
    1055     {
    1056       compare dummy, -1
    1057       break-if-!=
    1058       return
    1059     }
    1060     {
    1061       compare dummy, 0xa/newline
    1062       break-if-!=
    1063       dummy <- gap-right self
    1064       return
    1065     }
    1066     loop
    1067   }
    1068 }
    1069
    1070 fn gap-to-next-end-of-line self: (addr gap-buffer) {
    1071   # skip past immediate newline
    1072   var dummy/eax: grapheme <- gap-right self
    1073   # skip to next newline
    1074   {
    1075     dummy <- gap-right self
    1076     {
    1077       compare dummy, -1
    1078       break-if-!=
    1079       return
    1080     }
    1081     {
    1082       compare dummy, 0xa/newline
    1083       break-if-!=
    1084       dummy <- gap-left self
    1085       return
    1086     }
    1087     loop
    1088   }
    1089 }
    1090
    1091 fn gap-up self: (addr gap-buffer) {
    1092   # compute column
    1093   var col/edx: int <- count-columns-to-start-of-line self
    1094   #
    1095   gap-to-previous-start-of-line self
    1096   # skip ahead by up to col on previous line
    1097   var i/ecx: int <- copy 0
    1098   {
    1099     compare i, col
    1100     break-if->=
    1101     var curr/eax: grapheme <- gap-right self
    1102     {
    1103       compare curr, -1
    1104       break-if-!=
    1105       return
    1106     }
    1107     compare curr, 0xa/newline
    1108     {
    1109       break-if-!=
    1110       curr <- gap-left self
    1111       return
    1112     }
    1113     i <- increment
    1114     loop
    1115   }
    1116 }
    1117
    1118 fn gap-down self: (addr gap-buffer) {
    1119   # compute column
    1120   var col/edx: int <- count-columns-to-start-of-line self
    1121   # skip to start of next line
    1122   gap-to-end-of-line self
    1123   var dummy/eax: grapheme <- gap-right self
    1124   # skip ahead by up to col on previous line
    1125   var i/ecx: int <- copy 0
    1126   {
    1127     compare i, col
    1128     break-if->=
    1129     var curr/eax: grapheme <- gap-right self
    1130     {
    1131       compare curr, -1
    1132       break-if-!=
    1133       return
    1134     }
    1135     compare curr, 0xa/newline
    1136     {
    1137       break-if-!=
    1138       curr <- gap-left self
    1139       return
    1140     }
    1141     i <- increment
    1142     loop
    1143   }
    1144 }
    1145
    1146 fn count-columns-to-start-of-line self: (addr gap-buffer) -> _/edx: int {
    1147   var count/edx: int <- copy 0
    1148   var dummy/eax: grapheme <- copy 0
    1149   # skip to previous newline
    1150   {
    1151     dummy <- gap-left self
    1152     {
    1153       compare dummy, -1
    1154       break-if-!=
    1155       return count
    1156     }
    1157     {
    1158       compare dummy, 0xa/newline
    1159       break-if-!=
    1160       dummy <- gap-right self
    1161       return count
    1162     }
    1163     count <- increment
    1164     loop
    1165   }
    1166   return count
    1167 }
    1168
    1169 fn gap-to-end-of-line self: (addr gap-buffer) {
    1170   var dummy/eax: grapheme <- copy 0
    1171   # skip to next newline
    1172   {
    1173     dummy <- gap-right self
    1174     {
    1175       compare dummy, -1
    1176       break-if-!=
    1177       return
    1178     }
    1179     {
    1180       compare dummy, 0xa/newline
    1181       break-if-!=
    1182       dummy <- gap-left self
    1183       return
    1184     }
    1185     loop
    1186   }
    1187 }