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, 0x40/capacity
  39   var stream-storage: (stream byte 0x40/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 }