https://github.com/akkartik/mu/blob/main/browse-slack/environment.mu
   1 type environment {
   2   search-terms: (handle gap-buffer)
   3   tabs: (handle array tab)
   4   current-tab-index: int  # index into tabs
   5   dirty?: boolean
   6   # search mode
   7   cursor-in-search?: boolean
   8   # channel mode
   9   cursor-in-channels?: boolean
  10   channel-cursor-index: int
  11 }
  12 
  13 type tab {
  14   type: int
  15       # type 0: everything
  16       # type 1: items in a channel
  17       # type 2: search for a term
  18       # type 3: comments in a single thread
  19   item-index: int  # what item in the corresponding list we start rendering
  20                    # the current page at
  21   # only for type 1
  22   channel-index: int
  23   # only for type 2
  24   search-terms: (handle gap-buffer)
  25   search-items: (handle array int)
  26   search-items-first-free: int
  27   # only for type 3
  28   root-index: int
  29 }
  30 
  31 # static buffer sizes in this file:
  32 #   main-panel-hor            # in characters
  33 #   item-padding-hor          # in pixels
  34 #   item-padding-ver          # in characters
  35 #   avatar-side               # in pixels
  36 #   avatar-space-hor          # in characters
  37 #   avatar-space-ver          # in characters
  38 #   search-position-x         # in characters
  39 #   search-space-ver          # in characters
  40 #   author-name-padding-ver   # in characters
  41 #   post-right-coord          # in characters
  42 #   channel-offset-x          # in characters
  43 #   menu-space-ver            # in characters
  44 #   max-search-results
  45 
  46 fn initialize-environment _self: (addr environment), _items: (addr item-list) {
  47   var self/esi: (addr environment) <- copy _self
  48   var search-terms-ah/eax: (addr handle gap-buffer) <- get self, search-terms
  49   allocate search-terms-ah
  50   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
  51   initialize-gap-buffer search-terms, 0x30/search-capacity
  52   var items/eax: (addr item-list) <- copy _items
  53   var items-data-first-free-a/eax: (addr int) <- get items, data-first-free
  54   var final-item/edx: int <- copy *items-data-first-free-a
  55   final-item <- decrement
  56   var tabs-ah/ecx: (addr handle array tab) <- get self, tabs
  57   populate tabs-ah, 0x10/max-history
  58   # current-tab-index implicitly set to 0
  59   var tabs/eax: (addr array tab) <- lookup *tabs-ah
  60   var first-tab/eax: (addr tab) <- index tabs, 0/current-tab-index
  61   var dest/edi: (addr int) <- get first-tab, item-index
  62   copy-to *dest, final-item
  63 }
  64 
  65 ### Render
  66 
  67 fn render-environment screen: (addr screen), _env: (addr environment), users: (addr array user), channels: (addr array channel), items: (addr item-list) {
  68   var env/esi: (addr environment) <- copy _env
  69   {
  70     var dirty?/eax: (addr boolean) <- get env, dirty?
  71     compare *dirty?, 0/false
  72     break-if-!=
  73     # minimize repaints when typing into the search bar
  74     {
  75       var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
  76       compare *cursor-in-search?, 0/false
  77       break-if-=
  78       render-search-input screen, env
  79       clear-rect screen, 0/x 0x2f/y, 0x80/x 0x30/y, 0/bg
  80       render-search-menu screen, env
  81       return
  82     }
  83     # minimize repaints when focus in channel nav
  84     {
  85       var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
  86       compare *cursor-in-channels?, 0/false
  87       break-if-=
  88       render-channels screen, env, channels
  89       clear-rect screen, 0/x 0x2f/y, 0x80/x 0x30/y, 0/bg
  90       render-channels-menu screen, env
  91       return
  92     }
  93   }
  94   # full repaint
  95   clear-screen screen
  96   render-search-input screen, env
  97   render-channels screen, env, channels
  98   render-item-list screen, env, items, channels, users
  99   render-menu screen, env
 100   var dirty?/eax: (addr boolean) <- get env, dirty?
 101   copy-to *dirty?, 0/false
 102 }
 103 
 104 fn render-channels screen: (addr screen), _env: (addr environment), _channels: (addr array channel) {
 105   var env/esi: (addr environment) <- copy _env
 106   var cursor-index/edi: int <- copy -1
 107   {
 108     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 109     compare *cursor-in-search?, 0/false
 110     break-if-!=
 111     var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 112     compare *cursor-in-channels?, 0/false
 113     break-if-=
 114     var cursor-index-addr/eax: (addr int) <- get env, channel-cursor-index
 115     cursor-index <- copy *cursor-index-addr
 116   }
 117   var channels/esi: (addr array channel) <- copy _channels
 118   var y/ebx: int <- copy 2/search-space-ver
 119   y <- add 1/item-padding-ver
 120   var i/ecx: int <- copy 0
 121   var max/edx: int <- length channels
 122   {
 123     compare i, max
 124     break-if->=
 125     var offset/eax: (offset channel) <- compute-offset channels, i
 126     var curr/eax: (addr channel) <- index channels, offset
 127     var name-ah/eax: (addr handle array byte) <- get curr, name
 128     var name/eax: (addr array byte) <- lookup *name-ah
 129     compare name, 0
 130     break-if-=
 131     set-cursor-position screen, 2/x y
 132     {
 133       compare cursor-index, i
 134       break-if-=
 135       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "#", 7/grey 0/black
 136       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, name, 7/grey 0/black
 137     }
 138     {
 139       compare cursor-index, i
 140       break-if-!=
 141       # cursor; reverse video
 142       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "#", 0/black 0xf/white
 143       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, name, 0/black 0xf/white
 144     }
 145     y <- add 2/channel-padding
 146     i <- increment
 147     loop
 148   }
 149 }
 150 
 151 fn render-item-list screen: (addr screen), _env: (addr environment), items: (addr item-list), channels: (addr array channel), users: (addr array user) {
 152   var env/esi: (addr environment) <- copy _env
 153   var tmp-width/eax: int <- copy 0
 154   var tmp-height/ecx: int <- copy 0
 155   tmp-width, tmp-height <- screen-size screen
 156   var screen-width: int
 157   copy-to screen-width, tmp-width
 158   var screen-height: int
 159   copy-to screen-height, tmp-height
 160   #
 161   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
 162   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
 163   var tabs/edx: (addr array tab) <- copy _tabs
 164   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
 165   var current-tab-index/eax: int <- copy *current-tab-index-a
 166   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
 167   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
 168   var show-cursor?: boolean
 169   {
 170     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 171     compare *cursor-in-search?, 0/false
 172     break-if-!=
 173     var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 174     compare *cursor-in-channels?, 0/false
 175     break-if-!=
 176     copy-to show-cursor?, 1/true
 177   }
 178   render-tab screen, current-tab, show-cursor?, items, channels, users, screen-height
 179   var top/eax: int <- copy screen-height
 180   top <- subtract 2/menu-space-ver
 181   clear-rect screen, 0 top, screen-width screen-height, 0/bg
 182 }
 183 
 184 fn render-tab screen: (addr screen), _current-tab: (addr tab), show-cursor?: boolean, items: (addr item-list), channels: (addr array channel), users: (addr array user), screen-height: int {
 185   var current-tab/esi: (addr tab) <- copy _current-tab
 186   var current-tab-type/eax: (addr int) <- get current-tab, type
 187   compare *current-tab-type, 0/all-items
 188   {
 189     break-if-!=
 190     render-all-items screen, current-tab, show-cursor?, items, users, screen-height
 191     return
 192   }
 193   compare *current-tab-type, 1/channel
 194   {
 195     break-if-!=
 196     render-channel-tab screen, current-tab, show-cursor?, items, channels, users, screen-height
 197     return
 198   }
 199   compare *current-tab-type, 2/search
 200   {
 201     break-if-!=
 202     render-search-tab screen, current-tab, show-cursor?, items, channels, users, screen-height
 203     return
 204   }
 205   compare *current-tab-type, 3/thread
 206   {
 207     break-if-!=
 208     render-thread-tab screen, current-tab, show-cursor?, items, channels, users, screen-height
 209     return
 210   }
 211 }
 212 
 213 fn render-all-items screen: (addr screen), _current-tab: (addr tab), show-cursor?: boolean, _items: (addr item-list), users: (addr array user), screen-height: int {
 214   var current-tab/esi: (addr tab) <- copy _current-tab
 215   var items/edi: (addr item-list) <- copy _items
 216   var newest-item/eax: (addr int) <- get current-tab, item-index
 217   var i/ebx: int <- copy *newest-item
 218   var items-data-first-free-addr/eax: (addr int) <- get items, data-first-free
 219   render-progress screen, i, *items-data-first-free-addr
 220   var items-data-ah/eax: (addr handle array item) <- get items, data
 221   var _items-data/eax: (addr array item) <- lookup *items-data-ah
 222   var items-data/edi: (addr array item) <- copy _items-data
 223   var y/ecx: int <- copy 2/search-space-ver
 224   y <- add 1/item-padding-ver
 225   {
 226     compare i, 0
 227     break-if-<
 228     compare y, screen-height
 229     break-if->=
 230     var offset/eax: (offset item) <- compute-offset items-data, i
 231     var curr-item/eax: (addr item) <- index items-data, offset
 232     y <- render-item screen, curr-item, users, show-cursor?, y, screen-height
 233     # cursor always at top item
 234     copy-to show-cursor?, 0/false
 235     i <- decrement
 236     loop
 237   }
 238 }
 239 
 240 fn render-channel-tab screen: (addr screen), _current-tab: (addr tab), show-cursor?: boolean, _items: (addr item-list), _channels: (addr array channel), users: (addr array user), screen-height: int {
 241   var current-tab/esi: (addr tab) <- copy _current-tab
 242   var items/edi: (addr item-list) <- copy _items
 243   var channels/ebx: (addr array channel) <- copy _channels
 244   var channel-index-addr/eax: (addr int) <- get current-tab, channel-index
 245   var channel-index/eax: int <- copy *channel-index-addr
 246   var channel-offset/eax: (offset channel) <- compute-offset channels, channel-index
 247   var current-channel/ecx: (addr channel) <- index channels, channel-offset
 248   var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
 249   var _current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
 250   var current-channel-posts/edx: (addr array int) <- copy _current-channel-posts
 251   var current-channel-first-channel-item-addr/eax: (addr int) <- get current-tab, item-index
 252   var i/ebx: int <- copy *current-channel-first-channel-item-addr
 253   var current-channel-posts-first-free-addr/eax: (addr int) <- get current-channel, posts-first-free
 254   set-cursor-position 0/screen, 0x68/x 0/y
 255   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "channel", 7/fg 0/bg
 256   render-progress screen, i, *current-channel-posts-first-free-addr
 257   var items-data-ah/eax: (addr handle array item) <- get items, data
 258   var _items-data/eax: (addr array item) <- lookup *items-data-ah
 259   var items-data/edi: (addr array item) <- copy _items-data
 260   var y/ecx: int <- copy 2/search-space-ver
 261   y <- add 1/item-padding-ver
 262   {
 263     compare i, 0
 264     break-if-<
 265     compare y, screen-height
 266     break-if->=
 267     var item-index-addr/eax: (addr int) <- index current-channel-posts, i
 268     var item-index/eax: int <- copy *item-index-addr
 269     var item-offset/eax: (offset item) <- compute-offset items-data, item-index
 270     var curr-item/eax: (addr item) <- index items-data, item-offset
 271     y <- render-item screen, curr-item, users, show-cursor?, y, screen-height
 272     # cursor always at top item
 273     copy-to show-cursor?, 0/false
 274     i <- decrement
 275     loop
 276   }
 277 }
 278 
 279 fn render-search-tab screen: (addr screen), _current-tab: (addr tab), show-cursor?: boolean, _items: (addr item-list), channels: (addr array channel), users: (addr array user), screen-height: int {
 280   var current-tab/esi: (addr tab) <- copy _current-tab
 281   var items/edi: (addr item-list) <- copy _items
 282   var current-tab-search-items-ah/eax: (addr handle array int) <- get current-tab, search-items
 283   var _current-tab-search-items/eax: (addr array int) <- lookup *current-tab-search-items-ah
 284   var current-tab-search-items/ebx: (addr array int) <- copy _current-tab-search-items
 285   var current-tab-top-item-addr/eax: (addr int) <- get current-tab, item-index
 286   var i/edx: int <- copy *current-tab-top-item-addr
 287   var current-tab-search-items-first-free-addr/eax: (addr int) <- get current-tab, search-items-first-free
 288   set-cursor-position 0/screen, 0x68/x 0/y
 289   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "search", 7/fg 0/bg
 290   render-progress screen, i, *current-tab-search-items-first-free-addr
 291   {
 292     compare *current-tab-search-items-first-free-addr, 0x100/max-search-results
 293     break-if-<
 294     set-cursor-position 0/screen, 0x68/x 1/y
 295     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "too many results", 4/fg 0/bg
 296   }
 297   var items-data-ah/eax: (addr handle array item) <- get items, data
 298   var _items-data/eax: (addr array item) <- lookup *items-data-ah
 299   var items-data/edi: (addr array item) <- copy _items-data
 300   var y/ecx: int <- copy 2/search-space-ver
 301   y <- add 1/item-padding-ver
 302   {
 303     compare i, 0
 304     break-if-<
 305     compare y, screen-height
 306     break-if->=
 307     var item-index-addr/eax: (addr int) <- index current-tab-search-items, i
 308     var item-index/eax: int <- copy *item-index-addr
 309     var item-offset/eax: (offset item) <- compute-offset items-data, item-index
 310     var curr-item/eax: (addr item) <- index items-data, item-offset
 311     y <- render-item screen, curr-item, users, show-cursor?, y, screen-height
 312     # cursor always at top item
 313     copy-to show-cursor?, 0/false
 314     i <- decrement
 315     loop
 316   }
 317 }
 318 
 319 fn render-thread-tab screen: (addr screen), _current-tab: (addr tab), show-cursor?: boolean, _items: (addr item-list), channels: (addr array channel), users: (addr array user), screen-height: int {
 320   var current-tab/esi: (addr tab) <- copy _current-tab
 321   var items/eax: (addr item-list) <- copy _items
 322   var items-data-ah/eax: (addr handle array item) <- get items, data
 323   var _items-data/eax: (addr array item) <- lookup *items-data-ah
 324   var items-data/edi: (addr array item) <- copy _items-data
 325   var post-index-addr/eax: (addr int) <- get current-tab, root-index
 326   var post-index/eax: int <- copy *post-index-addr
 327   var post-offset/eax: (offset item) <- compute-offset items-data, post-index
 328   var post/ebx: (addr item) <- index items-data, post-offset
 329   var current-tab-top-item-addr/eax: (addr int) <- get current-tab, item-index
 330   var i/edx: int <- copy *current-tab-top-item-addr
 331   var post-comments-first-free-addr/eax: (addr int) <- get post, comments-first-free
 332   set-cursor-position 0/screen, 0x68/x 0/y
 333   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "thread", 7/fg 0/bg
 334   render-progress screen, i, *post-comments-first-free-addr
 335   var post-comments-ah/eax: (addr handle array int) <- get post, comments
 336   var post-comments/eax: (addr array int) <- lookup *post-comments-ah
 337   var y/ecx: int <- copy 2/search-space-ver
 338   y <- add 1/item-padding-ver
 339   {
 340     compare i, 0
 341     break-if-<
 342     compare y, screen-height
 343     break-if->=
 344     var item-index-addr/eax: (addr int) <- index post-comments, i
 345     var item-index/eax: int <- copy *item-index-addr
 346     var item-offset/eax: (offset item) <- compute-offset items-data, item-index
 347     var curr-item/eax: (addr item) <- index items-data, item-offset
 348     y <- render-item screen, curr-item, users, show-cursor?, y, screen-height
 349     # cursor always at top item
 350     copy-to show-cursor?, 0/false
 351     i <- decrement
 352     loop
 353   }
 354   # finally render the parent -- though we'll never focus on it
 355   y <- render-item screen, post, users, 0/no-cursor, y, screen-height
 356 }
 357 
 358 # side-effect: mutates cursor position
 359 fn render-progress screen: (addr screen), curr: int, max: int {
 360   set-cursor-position 0/screen, 0x70/x 0/y
 361   var top-index/eax: int <- copy max
 362   top-index <- subtract curr  # happy accident: 1-based
 363   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, top-index, 7/fg 0/bg
 364   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "/", 7/fg 0/bg
 365   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, max, 7/fg 0/bg
 366 }
 367 
 368 fn render-search-input screen: (addr screen), _env: (addr environment) {
 369   var env/esi: (addr environment) <- copy _env
 370   var cursor-in-search?/ecx: (addr boolean) <- get env, cursor-in-search?
 371   set-cursor-position 0/screen, 0x22/x=search-position-x 1/y
 372   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "search ", 7/fg 0/bg
 373   var search-terms-ah/eax: (addr handle gap-buffer) <- get env, search-terms
 374   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
 375   rewind-gap-buffer search-terms
 376   var x/eax: int <- render-gap-buffer screen, search-terms, 0x2a/x 1/y, *cursor-in-search?, 0xf/fg 0/bg
 377   {
 378     compare x, 0x4a/end-search
 379     break-if->
 380     var y/ecx: int <- copy 0
 381     x, y <- render-code-point screen, 0x5f/underscore, 0/xmin 1/ymin, 0x80/xmax, 1/ymax, x, 1/y, 0xf/fg 0/bg
 382     loop
 383   }
 384 }
 385 
 386 # not used in search mode
 387 fn render-menu screen: (addr screen), _env: (addr environment) {
 388   var env/edi: (addr environment) <- copy _env
 389   {
 390     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 391     compare *cursor-in-search?, 0/false
 392     break-if-=
 393     render-search-menu screen, env
 394     return
 395   }
 396   var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 397   compare *cursor-in-channels?, 0/false
 398   {
 399     break-if-=
 400     render-channels-menu screen, env
 401     return
 402   }
 403   render-main-menu screen, env
 404 }
 405 
 406 fn render-main-menu screen: (addr screen), _env: (addr environment) {
 407   var width/eax: int <- copy 0
 408   var y/ecx: int <- copy 0
 409   width, y <- screen-size screen
 410   y <- decrement
 411   set-cursor-position screen, 2/x, y
 412   {
 413     var env/edi: (addr environment) <- copy _env
 414     var num-tabs/edi: (addr int) <- get env, current-tab-index
 415     compare *num-tabs, 0
 416     break-if-<=
 417     draw-text-rightward-from-cursor screen, " Esc ", width, 0/fg 0xf/bg
 418     draw-text-rightward-from-cursor screen, " go back  ", width, 0xf/fg, 0/bg
 419   }
 420   draw-text-rightward-from-cursor screen, " Enter ", width, 0/fg 0xf/bg
 421   draw-text-rightward-from-cursor screen, " go to thread  ", width, 0xf/fg, 0/bg
 422   draw-text-rightward-from-cursor screen, " / ", width, 0/fg 0xf/bg
 423   draw-text-rightward-from-cursor screen, " search  ", width, 0xf/fg, 0/bg
 424   draw-text-rightward-from-cursor screen, " Tab ", width, 0/fg 0xf/bg
 425   draw-text-rightward-from-cursor screen, " go to channels  ", width, 0xf/fg, 0/bg
 426   draw-text-rightward-from-cursor screen, " ^b ", width, 0/fg 0xf/bg
 427   draw-text-rightward-from-cursor screen, " << page  ", width, 0xf/fg, 0/bg
 428   draw-text-rightward-from-cursor screen, " ^f ", width, 0/fg 0xf/bg
 429   draw-text-rightward-from-cursor screen, " page >>  ", width, 0xf/fg, 0/bg
 430 }
 431 
 432 fn render-channels-menu screen: (addr screen), _env: (addr environment) {
 433   var width/eax: int <- copy 0
 434   var y/ecx: int <- copy 0
 435   width, y <- screen-size screen
 436   y <- decrement
 437   set-cursor-position screen, 2/x, y
 438   {
 439     var env/edi: (addr environment) <- copy _env
 440     var num-tabs/edi: (addr int) <- get env, current-tab-index
 441     compare *num-tabs, 0
 442     break-if-<=
 443     draw-text-rightward-from-cursor screen, " Esc ", width, 0/fg 0xf/bg
 444     draw-text-rightward-from-cursor screen, " go back  ", width, 0xf/fg, 0/bg
 445   }
 446   draw-text-rightward-from-cursor screen, " / ", width, 0/fg 0xf/bg
 447   draw-text-rightward-from-cursor screen, " search  ", width, 0xf/fg, 0/bg
 448   draw-text-rightward-from-cursor screen, " Tab ", width, 0/fg 0xf/bg
 449   draw-text-rightward-from-cursor screen, " go to items  ", width, 0xf/fg, 0/bg
 450   draw-text-rightward-from-cursor screen, " Enter ", width, 0/fg 0xf/bg
 451   draw-text-rightward-from-cursor screen, " select  ", width, 0xf/fg, 0/bg
 452 }
 453 
 454 fn render-search-menu screen: (addr screen), _env: (addr environment) {
 455   var width/eax: int <- copy 0
 456   var y/ecx: int <- copy 0
 457   width, y <- screen-size screen
 458   y <- decrement
 459   set-cursor-position screen, 2/x, y
 460   draw-text-rightward-from-cursor screen, " Esc ", width, 0/fg 0xf/bg
 461   draw-text-rightward-from-cursor screen, " cancel  ", width, 0xf/fg, 0/bg
 462   draw-text-rightward-from-cursor screen, " Enter ", width, 0/fg 0xf/bg
 463   draw-text-rightward-from-cursor screen, " select  ", width, 0xf/fg, 0/bg
 464   draw-text-rightward-from-cursor screen, " ^a ", width, 0/fg, 0xf/bg
 465   draw-text-rightward-from-cursor screen, " <<  ", width, 0xf/fg, 0/bg
 466   draw-text-rightward-from-cursor screen, " ^b ", width, 0/fg, 0xf/bg
 467   draw-text-rightward-from-cursor screen, " <word  ", width, 0xf/fg, 0/bg
 468   draw-text-rightward-from-cursor screen, " ^f ", width, 0/fg, 0xf/bg
 469   draw-text-rightward-from-cursor screen, " word>  ", width, 0xf/fg, 0/bg
 470   draw-text-rightward-from-cursor screen, " ^e ", width, 0/fg, 0xf/bg
 471   draw-text-rightward-from-cursor screen, " >>  ", width, 0xf/fg, 0/bg
 472   draw-text-rightward-from-cursor screen, " ^u ", width, 0/fg, 0xf/bg
 473   draw-text-rightward-from-cursor screen, " clear  ", width, 0xf/fg, 0/bg
 474 }
 475 
 476 fn render-item screen: (addr screen), _item: (addr item), _users: (addr array user), show-cursor?: boolean, y: int, screen-height: int -> _/ecx: int {
 477   var item/esi: (addr item) <- copy _item
 478   var users/edi: (addr array user) <- copy _users
 479   var author-index-addr/ecx: (addr int) <- get item, by
 480   var author-index/ecx: int <- copy *author-index-addr
 481   var author-offset/ecx: (offset user) <- compute-offset users, author-index
 482   var author/ecx: (addr user) <- index users, author-offset
 483   # author avatar
 484   var author-avatar-ah/eax: (addr handle image) <- get author, avatar
 485   var _author-avatar/eax: (addr image) <- lookup *author-avatar-ah
 486   var author-avatar/ebx: (addr image) <- copy _author-avatar
 487   {
 488     compare author-avatar, 0
 489     break-if-=
 490     var y/edx: int <- copy y
 491     y <- shift-left 4/log2font-height
 492     var x/eax: int <- copy 0x20/main-panel-hor
 493     x <- shift-left 3/log2font-width
 494     x <- add 0x18/item-padding-hor
 495     render-image screen, author-avatar, x, y, 0x50/avatar-side, 0x50/avatar-side
 496   }
 497   # channel
 498   var channel-name-ah/eax: (addr handle array byte) <- get item, channel
 499   var channel-name/eax: (addr array byte) <- lookup *channel-name-ah
 500   {
 501     var x/eax: int <- copy 0x20/main-panel-hor
 502     x <- add 0x40/channel-offset-x
 503     set-cursor-position screen, x y
 504   }
 505   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "#", 7/grey 0/black
 506   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, channel-name, 7/grey 0/black
 507   # author name
 508   {
 509     var author-real-name-ah/eax: (addr handle array byte) <- get author, real-name
 510     var author-real-name/eax: (addr array byte) <- lookup *author-real-name-ah
 511     var x/ecx: int <- copy 0x20/main-panel-hor
 512     x <- add 0x10/avatar-space-hor
 513     set-cursor-position screen, x y
 514     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, author-real-name, 0xf/white 0/black
 515   }
 516   increment y
 517   # text
 518   var text-ah/eax: (addr handle array byte) <- get item, text
 519   var _text/eax: (addr array byte) <- lookup *text-ah
 520   var text/edx: (addr array byte) <- copy _text
 521   var text-y/eax: int <- render-slack-message screen, text, show-cursor?, y, screen-height
 522   # flush
 523   add-to y, 6/avatar-space-ver
 524   compare y, text-y
 525   {
 526     break-if-<
 527     return y
 528   }
 529   return text-y
 530 }
 531 
 532 fn render-slack-message screen: (addr screen), text: (addr array byte), highlight?: boolean, ymin: int, ymax: int -> _/eax: int {
 533   var x/eax: int <- copy 0x20/main-panel-hor
 534   x <- add 0x10/avatar-space-hor
 535   var y/ecx: int <- copy ymin
 536   y <- add 1/author-name-padding-ver
 537   $render-slack-message:draw: {
 538     compare highlight?, 0/false
 539     {
 540       break-if-=
 541       x, y <- draw-json-text-wrapping-right-then-down screen, text, x y, 0x70/xmax=post-right-coord ymax, x y, 0/fg 7/bg
 542       break $render-slack-message:draw
 543     }
 544     x, y <- draw-json-text-wrapping-right-then-down screen, text, x y, 0x70/xmax=post-right-coord ymax, x y, 7/fg 0/bg
 545   }
 546   y <- add 2/item-padding-ver
 547   return y
 548 }
 549 
 550 # draw text in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
 551 # return the next (x, y) coordinate in raster order where drawing stopped
 552 # that way the caller can draw more if given the same min and max bounding-box.
 553 # if there isn't enough space, truncate
 554 fn draw-json-text-wrapping-right-then-down screen: (addr screen), _text: (addr array byte), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
 555   var stream-storage: (stream byte 0x4000/print-buffer-size)
 556   var stream/edi: (addr stream byte) <- address stream-storage
 557   var text/esi: (addr array byte) <- copy _text
 558   var len/eax: int <- length text
 559   compare len, 0x4000/print-buffer-size
 560   {
 561     break-if-<
 562     write stream, "ERROR: stream too small in draw-text-wrapping-right-then-down"
 563   }
 564   compare len, 0x4000/print-buffer-size
 565   {
 566     break-if->=
 567     write stream, text
 568   }
 569   var x/eax: int <- copy _x
 570   var y/ecx: int <- copy _y
 571   x, y <- draw-json-stream-wrapping-right-then-down screen, stream, xmin, ymin, xmax, ymax, x, y, color, background-color
 572   return x, y
 573 }
 574 
 575 # draw a stream in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
 576 # return the next (x, y) coordinate in raster order where drawing stopped
 577 # that way the caller can draw more if given the same min and max bounding-box.
 578 # if there isn't enough space, truncate
 579 fn draw-json-stream-wrapping-right-then-down screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
 580   var xcurr/eax: int <- copy x
 581   var ycurr/ecx: int <- copy y
 582   {
 583     var c/ebx: code-point <- read-json-code-point stream
 584     compare c, 0xffffffff/end-of-file
 585     break-if-=
 586     $draw-json-stream-wrapping-right-then-down:render-grapheme: {
 587       compare c, 0x5c/backslash
 588       {
 589         break-if-!=
 590         xcurr, ycurr <- render-json-escaped-code-point screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 591         break $draw-json-stream-wrapping-right-then-down:render-grapheme
 592       }
 593       xcurr, ycurr <- render-code-point screen, c, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 594     }
 595     loop
 596   }
 597   set-cursor-position screen, xcurr, ycurr
 598   return xcurr, ycurr
 599 }
 600 
 601 # just return a different register
 602 fn read-json-code-point stream: (addr stream byte) -> _/ebx: code-point {
 603   var g/eax: grapheme <- read-grapheme stream
 604   var result/eax: code-point <- to-code-point g
 605   return result
 606 }
 607 
 608 # '\' encountered
 609 # https://www.json.org/json-en.html
 610 fn render-json-escaped-code-point screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
 611   var g/ebx: code-point <- read-json-code-point stream
 612   compare g, 0xffffffff/end-of-file
 613   {
 614     break-if-!=
 615     return xcurr, ycurr
 616   }
 617   # \n = newline
 618   compare g, 0x6e/n
 619   var x/eax: int <- copy xcurr
 620   {
 621     break-if-!=
 622     increment ycurr
 623     return xmin, ycurr
 624   }
 625   # ignore \t \r \f \b
 626   {
 627     compare g, 0x74/t
 628     break-if-!=
 629     return xcurr, ycurr
 630   }
 631   {
 632     compare g, 0x72/r
 633     break-if-!=
 634     return xcurr, ycurr
 635   }
 636   {
 637     compare g, 0x66/f
 638     break-if-!=
 639     return xcurr, ycurr
 640   }
 641   {
 642     compare g, 0x62/b
 643     break-if-!=
 644     return xcurr, ycurr
 645   }
 646   var y/ecx: int <- copy 0
 647   # \u = Unicode
 648   {
 649     compare g, 0x75/u
 650     break-if-!=
 651     x, y <- render-json-escaped-unicode-code-point screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 652     return x, y
 653   }
 654   # most characters escape to themselves
 655   x, y <- render-code-point screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 656   return x, y
 657 }
 658 
 659 # '\u' encountered
 660 fn render-json-escaped-unicode-code-point screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
 661   var hex-digits-storage: (array byte 4)
 662   var hex-digits/esi: (addr array byte) <- address hex-digits-storage
 663   # slurp 4 bytes exactly
 664   var src/eax: byte <- read-byte stream
 665   var dest/ecx: (addr byte) <- index hex-digits, 0
 666   copy-byte-to *dest, src
 667   src <- read-byte stream
 668   dest <- index hex-digits, 1
 669   copy-byte-to *dest, src
 670   src <- read-byte stream
 671   dest <- index hex-digits, 2
 672   copy-byte-to *dest, src
 673   src <- read-byte stream
 674   dest <- index hex-digits, 3
 675   copy-byte-to *dest, src
 676   # \u2013 = -
 677   {
 678     var endash?/eax: boolean <- string-equal? hex-digits, "2013"
 679     compare endash?, 0/false
 680     break-if-=
 681     var x/eax: int <- copy 0
 682     var y/ecx: int <- copy 0
 683     x, y <- render-code-point screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 684     return x, y
 685   }
 686   # \u2014 = -
 687   {
 688     var emdash?/eax: boolean <- string-equal? hex-digits, "2014"
 689     compare emdash?, 0/false
 690     break-if-=
 691     var x/eax: int <- copy 0
 692     var y/ecx: int <- copy 0
 693     x, y <- render-code-point screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 694     return x, y
 695   }
 696   # \u2018 = '
 697   {
 698     var left-quote?/eax: boolean <- string-equal? hex-digits, "2018"
 699     compare left-quote?, 0/false
 700     break-if-=
 701     var x/eax: int <- copy 0
 702     var y/ecx: int <- copy 0
 703     x, y <- render-code-point screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 704     return x, y
 705   }
 706   # \u2019 = '
 707   {
 708     var right-quote?/eax: boolean <- string-equal? hex-digits, "2019"
 709     compare right-quote?, 0/false
 710     break-if-=
 711     var x/eax: int <- copy 0
 712     var y/ecx: int <- copy 0
 713     x, y <- render-code-point screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 714     return x, y
 715   }
 716   # \u201c = "
 717   {
 718     var left-dquote?/eax: boolean <- string-equal? hex-digits, "201c"
 719     compare left-dquote?, 0/false
 720     break-if-=
 721     var x/eax: int <- copy 0
 722     var y/ecx: int <- copy 0
 723     x, y <- render-code-point screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 724     return x, y
 725   }
 726   # \u201d = "
 727   {
 728     var right-dquote?/eax: boolean <- string-equal? hex-digits, "201d"
 729     compare right-dquote?, 0/false
 730     break-if-=
 731     var x/eax: int <- copy 0
 732     var y/ecx: int <- copy 0
 733     x, y <- render-code-point screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 734     return x, y
 735   }
 736   # \u2022 = *
 737   {
 738     var bullet?/eax: boolean <- string-equal? hex-digits, "2022"
 739     compare bullet?, 0/false
 740     break-if-=
 741     var x/eax: int <- copy 0
 742     var y/ecx: int <- copy 0
 743     x, y <- render-code-point screen, 0x2a/asterisk, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 744     return x, y
 745   }
 746   # \u2026 = ...
 747   {
 748     var ellipses?/eax: boolean <- string-equal? hex-digits, "2026"
 749     compare ellipses?, 0/false
 750     break-if-=
 751     var x/eax: int <- copy 0
 752     var y/ecx: int <- copy 0
 753     x, y <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 754     return x, y
 755   }
 756   var n/eax: int <- parse-hex-int hex-digits
 757   var c/edx: code-point <- copy n
 758   var x/eax: int <- copy 0
 759   var y/ecx: int <- copy 0
 760   x, y <- render-code-point screen, c, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 761   return x, y
 762 }
 763 
 764 ### Edit
 765 
 766 fn update-environment _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 767   var env/edi: (addr environment) <- copy _env
 768   # first dispatch to search mode if necessary
 769   {
 770     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 771     compare *cursor-in-search?, 0/false
 772     break-if-=
 773     update-search env, key, users, channels, items
 774     return
 775   }
 776   {
 777     compare key, 0x2f/slash
 778     break-if-!=
 779     # enter search mode
 780     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 781     copy-to *cursor-in-search?, 1/true
 782     # do one more repaint
 783     var dirty?/eax: (addr boolean) <- get env, dirty?
 784     copy-to *dirty?, 1/true
 785     return
 786   }
 787   {
 788     compare key, 0x1b/esc
 789     break-if-!=
 790     # back in history
 791     previous-tab env
 792     return
 793   }
 794   var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 795   {
 796     compare key, 9/tab
 797     break-if-!=
 798     # toggle cursor between main panel and channels nav
 799     not *cursor-in-channels?  # bitwise NOT; only works if you never assign 1/true to this variable
 800     # do one more repaint
 801     var dirty?/eax: (addr boolean) <- get env, dirty?
 802     copy-to *dirty?, 1/true
 803     return
 804   }
 805   {
 806     compare *cursor-in-channels?, 0/false
 807     break-if-!=
 808     update-main-panel env, key, users, channels, items
 809     return
 810   }
 811   {
 812     compare *cursor-in-channels?, 0/false
 813     break-if-=
 814     update-channels-nav env, key, users, channels, items
 815     return
 816   }
 817 }
 818 
 819 fn update-main-panel env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 820   {
 821     compare key, 0xa/newline
 822     break-if-!=
 823     new-thread-tab env, users, channels, items
 824     return
 825   }
 826   {
 827     compare key, 0x81/down-arrow
 828     break-if-!=
 829     next-item env, users, channels, items
 830     return
 831   }
 832   {
 833     compare key, 0x82/up-arrow
 834     break-if-!=
 835     previous-item env, users, channels, items
 836     return
 837   }
 838   {
 839     compare key, 6/ctrl-f
 840     break-if-!=
 841     page-down env, users, channels, items
 842     return
 843   }
 844   {
 845     compare key, 2/ctrl-b
 846     break-if-!=
 847     page-up env, users, channels, items
 848     return
 849   }
 850 }
 851 
 852 # TODO: clamp cursor within bounds
 853 fn update-channels-nav _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 854   var env/edi: (addr environment) <- copy _env
 855   var channel-cursor-index/eax: (addr int) <- get env, channel-cursor-index
 856   {
 857     compare key, 0x81/down-arrow
 858     break-if-!=
 859     increment *channel-cursor-index
 860     return
 861   }
 862   {
 863     compare key, 0x82/up-arrow
 864     break-if-!=
 865     decrement *channel-cursor-index
 866     return
 867   }
 868   {
 869     compare key, 0xa/newline
 870     break-if-!=
 871     new-channel-tab env, *channel-cursor-index, channels
 872     var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 873     copy-to *cursor-in-channels?, 0/false
 874     return
 875   }
 876 }
 877 
 878 fn update-search _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 879   var env/edi: (addr environment) <- copy _env
 880   var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 881   {
 882     compare key 0x1b/esc
 883     break-if-!=
 884     # get out of search mode
 885     copy-to *cursor-in-search?, 0/false
 886     return
 887   }
 888   {
 889     compare key, 0xa/newline
 890     break-if-!=
 891     # perform a search, then get out of search mode
 892     new-search-tab env, items
 893     copy-to *cursor-in-search?, 0/false
 894     return
 895   }
 896   # otherwise delegate
 897   var search-terms-ah/eax: (addr handle gap-buffer) <- get env, search-terms
 898   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
 899   var g/ecx: grapheme <- copy key
 900   edit-gap-buffer search-terms, g
 901 }
 902 
 903 fn new-thread-tab _env: (addr environment), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
 904   var env/edi: (addr environment) <- copy _env
 905   var current-tab-index-a/ecx: (addr int) <- get env, current-tab-index
 906   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
 907   var tabs/eax: (addr array tab) <- lookup *tabs-ah
 908   var current-tab-index/ecx: int <- copy *current-tab-index-a
 909   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
 910   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
 911   var item-index/esi: int <- item-index current-tab, channels
 912   var post-index/ecx: int <- post-index _items, item-index
 913   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
 914   increment *current-tab-index-addr
 915   var current-tab-index/edx: int <- copy *current-tab-index-addr
 916   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
 917   var tabs/eax: (addr array tab) <- lookup *tabs-ah
 918   var max-tabs/ebx: int <- length tabs
 919   compare current-tab-index, max-tabs
 920   {
 921     compare current-tab-index, max-tabs
 922     break-if-<
 923     abort "history overflow; grow max-history (we should probably improve this)"
 924   }
 925   var current-tab-offset/edi: (offset tab) <- compute-offset tabs, current-tab-index
 926   var current-tab/edi: (addr tab) <- index tabs, current-tab-offset
 927   clear-object current-tab
 928   var current-tab-type/eax: (addr int) <- get current-tab, type
 929   copy-to *current-tab, 3/thread
 930   var current-tab-root-index/eax: (addr int) <- get current-tab, root-index
 931   copy-to *current-tab-root-index, post-index
 932   var items/eax: (addr item-list) <- copy _items
 933   var items-data-ah/eax: (addr handle array item) <- get items, data
 934   var items-data/eax: (addr array item) <- lookup *items-data-ah
 935   var offset/ecx: (offset item) <- compute-offset items-data, post-index
 936   var post/eax: (addr item) <- index items-data, offset
 937   var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
 938   # terminology:
 939   #   post-comment-index = index of a comment in a post's comment array
 940   #   comment-index = index of a comment in the global item list
 941   var final-post-comment-index/ecx: int <- copy *post-comments-first-free-addr
 942   final-post-comment-index <- decrement
 943   var post-comments-ah/eax: (addr handle array int) <- get post, comments
 944   var post-comments/eax: (addr array int) <- lookup *post-comments-ah
 945   # look for item-index in post-comments[0..final-post-comment-index]
 946   var curr-post-comment-index/edx: int <- copy final-post-comment-index
 947   {
 948     compare curr-post-comment-index, 0
 949     {
 950       break-if->=
 951       # if we didn't find the current item in a post's comments, it must be
 952       # the parent post itself which isn't in the comment list but hackily
 953       # rendered at the bottom. Just render the whole comment list.
 954       var tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
 955       copy-to *tab-item-index-addr, curr-post-comment-index
 956       return
 957     }
 958     var curr-comment-index/ecx: (addr int) <- index post-comments, curr-post-comment-index
 959     compare *curr-comment-index, item-index
 960     {
 961       break-if-!=
 962       # item-index found
 963       var tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
 964       copy-to *tab-item-index-addr, curr-post-comment-index
 965       return
 966     }
 967     curr-post-comment-index <- decrement
 968     loop
 969   }
 970   abort "new-thread-tab: should never leave previous loop without returning"
 971 }
 972 
 973 fn item-index _tab: (addr tab), _channels: (addr array channel) -> _/esi: int {
 974   var tab/esi: (addr tab) <- copy _tab
 975   var tab-type/eax: (addr int) <- get tab, type
 976   {
 977     compare *tab-type, 0/all-items
 978     break-if-!=
 979     var tab-item-index/eax: (addr int) <- get tab, item-index
 980     return *tab-item-index
 981   }
 982   {
 983     compare *tab-type, 1/channel
 984     break-if-!=
 985     var channel-index-addr/eax: (addr int) <- get tab, channel-index
 986     var channel-index/eax: int <- copy *channel-index-addr
 987     var channels/ecx: (addr array channel) <- copy _channels
 988     var channel-offset/eax: (offset channel) <- compute-offset channels, channel-index
 989     var current-channel/eax: (addr channel) <- index channels, channel-offset
 990     var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
 991     var current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
 992     var channel-item-index-addr/ecx: (addr int) <- get tab, item-index
 993     var channel-item-index/ecx: int <- copy *channel-item-index-addr
 994     var channel-item-index/eax: (addr int) <- index current-channel-posts, channel-item-index
 995     return *channel-item-index
 996   }
 997   {
 998     compare *tab-type, 2/search
 999     break-if-!=
1000     var tab-search-items-ah/eax: (addr handle array int) <- get tab, search-items
1001     var tab-search-items/eax: (addr array int) <- lookup *tab-search-items-ah
1002     var tab-search-items-index-addr/ecx: (addr int) <- get tab, item-index
1003     var tab-search-items-index/ecx: int <- copy *tab-search-items-index-addr
1004     var src/eax: (addr int) <- index tab-search-items, tab-search-items-index
1005     return *src
1006   }
1007   abort "item-index: unknown tab type"
1008   return -1
1009 }
1010 
1011 fn post-index _items: (addr item-list), item-index: int -> _/ecx: int {
1012   var items/eax: (addr item-list) <- copy _items
1013   var items-data-ah/eax: (addr handle array item) <- get items, data
1014   var items-data/eax: (addr array item) <- lookup *items-data-ah
1015   var index/ecx: int <- copy item-index
1016   var offset/ecx: (offset item) <- compute-offset items-data, index
1017   var item/eax: (addr item) <- index items-data, offset
1018   var parent/eax: (addr int) <- get item, parent
1019   compare *parent, 0
1020   {
1021     break-if-=
1022     return *parent
1023   }
1024   return item-index
1025 }
1026 
1027 fn new-channel-tab _env: (addr environment), channel-index: int, _channels: (addr array channel) {
1028   var env/edi: (addr environment) <- copy _env
1029   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
1030   increment *current-tab-index-addr
1031   var current-tab-index/ecx: int <- copy *current-tab-index-addr
1032   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1033   var tabs/eax: (addr array tab) <- lookup *tabs-ah
1034   var max-tabs/edx: int <- length tabs
1035   compare current-tab-index, max-tabs
1036   {
1037     compare current-tab-index, max-tabs
1038     break-if-<
1039     abort "history overflow; grow max-history (we should probably improve this)"
1040   }
1041   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1042   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1043   clear-object current-tab
1044   var current-tab-type/eax: (addr int) <- get current-tab, type
1045   copy-to *current-tab, 1/channel
1046   var current-tab-channel-index/eax: (addr int) <- get current-tab, channel-index
1047   var curr-channel-index/edx: int <- copy channel-index
1048   copy-to *current-tab-channel-index, curr-channel-index
1049   var channels/esi: (addr array channel) <- copy _channels
1050   var curr-channel-offset/eax: (offset channel) <- compute-offset channels, curr-channel-index
1051   var curr-channel/eax: (addr channel) <- index channels, curr-channel-offset
1052   var curr-channel-posts-first-free-addr/eax: (addr int) <- get curr-channel, posts-first-free
1053   var curr-channel-final-post-index/eax: int <- copy *curr-channel-posts-first-free-addr
1054   curr-channel-final-post-index <- decrement
1055   var dest/edi: (addr int) <- get current-tab, item-index
1056   copy-to *dest, curr-channel-final-post-index
1057 }
1058 
1059 fn new-search-tab _env: (addr environment), items: (addr item-list) {
1060   var env/edi: (addr environment) <- copy _env
1061   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
1062   increment *current-tab-index-addr
1063   var current-tab-index/ecx: int <- copy *current-tab-index-addr
1064   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1065   var tabs/eax: (addr array tab) <- lookup *tabs-ah
1066   var max-tabs/edx: int <- length tabs
1067   compare current-tab-index, max-tabs
1068   {
1069     compare current-tab-index, max-tabs
1070     break-if-<
1071     abort "history overflow; grow max-history (we should probably improve this)"
1072   }
1073   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1074   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1075   clear-object current-tab
1076   var current-tab-type/eax: (addr int) <- get current-tab, type
1077   copy-to *current-tab, 2/search
1078   var current-tab-search-terms-ah/edx: (addr handle gap-buffer) <- get current-tab, search-terms
1079   allocate current-tab-search-terms-ah
1080   var current-tab-search-terms/eax: (addr gap-buffer) <- lookup *current-tab-search-terms-ah
1081   initialize-gap-buffer current-tab-search-terms, 0x30/search-capacity
1082   var search-terms-ah/ebx: (addr handle gap-buffer) <- get env, search-terms
1083   copy-gap-buffer search-terms-ah, current-tab-search-terms-ah
1084   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
1085   search-items current-tab, items, search-terms
1086 }
1087 
1088 fn search-items _tab: (addr tab), _items: (addr item-list), search-terms: (addr gap-buffer) {
1089   var tab/edi: (addr tab) <- copy _tab
1090   var tab-items-first-free-addr/esi: (addr int) <- get tab, search-items-first-free
1091   var tab-items-ah/eax: (addr handle array int) <- get tab, search-items
1092   populate tab-items-ah, 0x100/max-search-results
1093   var _tab-items/eax: (addr array int) <- lookup *tab-items-ah
1094   var tab-items/edi: (addr array int) <- copy _tab-items
1095   # preprocess search-terms
1096   var search-terms-stream-storage: (stream byte 0x100)
1097   var search-terms-stream-addr/ecx: (addr stream byte) <- address search-terms-stream-storage
1098   emit-gap-buffer search-terms, search-terms-stream-addr
1099   var search-terms-text-h: (handle array byte)
1100   var search-terms-text-ah/eax: (addr handle array byte) <- address search-terms-text-h
1101   stream-to-array search-terms-stream-addr, search-terms-text-ah
1102   var tmp/eax: (addr array byte) <- lookup *search-terms-text-ah
1103   var search-terms-text: (addr array byte)
1104   copy-to search-terms-text, tmp
1105   #
1106   var items/ecx: (addr item-list) <- copy _items
1107   var items-data-ah/eax: (addr handle array item) <- get items, data
1108   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1109   var items-data/ebx: (addr array item) <- copy _items-data
1110   var items-data-first-free-a/edx: (addr int) <- get items, data-first-free
1111   var i/ecx: int <- copy 0
1112   {
1113     compare i, *items-data-first-free-a
1114     break-if->=
1115     var curr-offset/eax: (offset item) <- compute-offset items-data, i
1116     var curr-item/eax: (addr item) <- index items-data, curr-offset
1117     var found?/eax: boolean <- search-terms-match? curr-item, search-terms-text
1118     compare found?, 0/false
1119     {
1120       break-if-=
1121       var tab-items-first-free/eax: int <- copy *tab-items-first-free-addr
1122       compare tab-items-first-free, 0x100/max-search-results
1123       break-if->=
1124       var dest/eax: (addr int) <- index tab-items, tab-items-first-free
1125       copy-to *dest, i
1126       increment *tab-items-first-free-addr
1127     }
1128     i <- increment
1129     loop
1130   }
1131   var tab/edi: (addr tab) <- copy _tab
1132   var tab-item-index-addr/edi: (addr int) <- get tab, item-index
1133   var tab-items-first-free/eax: int <- copy *tab-items-first-free-addr
1134   tab-items-first-free <- decrement
1135   copy-to *tab-item-index-addr, tab-items-first-free
1136 }
1137 
1138 fn search-terms-match? _item: (addr item), search-terms: (addr array byte) -> _/eax: boolean {
1139   var item/esi: (addr item) <- copy _item
1140   var item-text-ah/eax: (addr handle array byte) <- get item, text
1141   var item-text/eax: (addr array byte) <- lookup *item-text-ah
1142   var i/ecx: int <- copy 0
1143   var max/edx: int <- length item-text
1144   var search-terms2/ebx: (addr array byte) <- copy search-terms
1145   var slen/ebx: int <- length search-terms2
1146   max <- subtract slen
1147   {
1148     compare i, max
1149     break-if->
1150     var found?/eax: boolean <- substring-match? item-text, search-terms, i
1151     compare found?, 0/false
1152     {
1153       break-if-=
1154       return 1/true
1155     }
1156     i <- increment
1157     loop
1158   }
1159   return 0/false
1160 }
1161 
1162 fn substring-match? _s: (addr array byte), _pat: (addr array byte), start: int -> _/eax: boolean {
1163   var s/esi: (addr array byte) <- copy _s
1164   var pat/edi: (addr array byte) <- copy _pat
1165   var s-idx/edx: int <- copy start
1166   var pat-idx/ebx: int <- copy 0
1167   var pat-len: int
1168   var tmp/eax: int <- length pat
1169   copy-to pat-len, tmp
1170   {
1171     compare pat-idx, pat-len
1172     break-if->=
1173     var s-ab/eax: (addr byte) <- index s, s-idx
1174     var s-b/eax: byte <- copy-byte *s-ab
1175     var pat-ab/ecx: (addr byte) <- index pat, pat-idx
1176     var pat-b/ecx: byte <- copy-byte *pat-ab
1177     compare s-b, pat-b
1178     {
1179       break-if-=
1180       return 0/false
1181     }
1182     s-idx <- increment
1183     pat-idx <- increment
1184     loop
1185   }
1186   return 1/true
1187 }
1188 
1189 fn previous-tab _env: (addr environment) {
1190   var env/edi: (addr environment) <- copy _env
1191   var current-tab-index-addr/ecx: (addr int) <- get env, current-tab-index
1192   compare *current-tab-index-addr, 0
1193   {
1194     break-if-<=
1195     decrement *current-tab-index-addr
1196     # if necessary restore search state
1197     var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1198     var tabs/eax: (addr array tab) <- lookup *tabs-ah
1199     var current-tab-index/ecx: int <- copy *current-tab-index-addr
1200     var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1201     var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1202     var current-tab-type/eax: (addr int) <- get current-tab, type
1203     compare *current-tab-type, 2/search
1204     break-if-!=
1205     var current-tab-search-terms-ah/ecx: (addr handle gap-buffer) <- get current-tab, search-terms
1206     var search-terms-ah/edx: (addr handle gap-buffer) <- get env, search-terms
1207     var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
1208     clear-gap-buffer search-terms
1209     copy-gap-buffer current-tab-search-terms-ah, search-terms-ah
1210   }
1211 }
1212 
1213 fn next-item _env: (addr environment), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1214   var env/edi: (addr environment) <- copy _env
1215   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1216   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1217   var tabs/edx: (addr array tab) <- copy _tabs
1218   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1219   var current-tab-index/eax: int <- copy *current-tab-index-a
1220   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1221   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1222   var dest/eax: (addr int) <- get current-tab, item-index
1223   compare *dest, 0
1224   break-if-<=
1225   decrement *dest
1226 }
1227 
1228 fn previous-item _env: (addr environment), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1229   var env/edi: (addr environment) <- copy _env
1230   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1231   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1232   var tabs/edx: (addr array tab) <- copy _tabs
1233   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1234   var current-tab-index/eax: int <- copy *current-tab-index-a
1235   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1236   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1237   var current-tab-type/eax: (addr int) <- get current-tab, type
1238   compare *current-tab-type, 0/all-items
1239   {
1240     break-if-!=
1241     var items/esi: (addr item-list) <- copy _items
1242     var items-data-first-free-a/ecx: (addr int) <- get items, data-first-free
1243     var final-item-index/ecx: int <- copy *items-data-first-free-a
1244     final-item-index <- decrement
1245     var dest/eax: (addr int) <- get current-tab, item-index
1246     compare *dest, final-item-index
1247     break-if->=
1248     increment *dest
1249     return
1250   }
1251   compare *current-tab-type, 1/channel
1252   {
1253     break-if-!=
1254     var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1255     var current-channel-index/eax: int <- copy *current-channel-index-addr
1256     var channels/esi: (addr array channel) <- copy _channels
1257     var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1258     var current-channel/eax: (addr channel) <- index channels, current-channel-offset
1259     var current-channel-posts-first-free-addr/eax: (addr int) <- get current-channel, posts-first-free
1260     var final-item-index/ecx: int <- copy *current-channel-posts-first-free-addr
1261     final-item-index <- decrement
1262     var dest/eax: (addr int) <- get current-tab, item-index
1263     compare *dest, final-item-index
1264     break-if->=
1265     increment *dest
1266     return
1267   }
1268   compare *current-tab-type, 2/search
1269   {
1270     break-if-!=
1271     var current-tab-search-items-first-free-addr/eax: (addr int) <- get current-tab, search-items-first-free
1272     var final-item-index/ecx: int <- copy *current-tab-search-items-first-free-addr
1273     final-item-index <- decrement
1274     var dest/eax: (addr int) <- get current-tab, item-index
1275     compare *dest, final-item-index
1276     break-if->=
1277     increment *dest
1278     return
1279   }
1280   compare *current-tab-type, 3/thread
1281   {
1282     break-if-!=
1283     var items/eax: (addr item-list) <- copy _items
1284     var items-data-ah/eax: (addr handle array item) <- get items, data
1285     var _items-data/eax: (addr array item) <- lookup *items-data-ah
1286     var items-data/esi: (addr array item) <- copy _items-data
1287     var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1288     var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1289     var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1290     var post/eax: (addr item) <- index items-data, current-tab-root-offset
1291     var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
1292     var final-item-index/ecx: int <- copy *post-comments-first-free-addr
1293     final-item-index <- decrement
1294     var dest/eax: (addr int) <- get current-tab, item-index
1295     compare *dest, final-item-index
1296     break-if->=
1297     increment *dest
1298     return
1299   }
1300 }
1301 
1302 fn page-down _env: (addr environment), users: (addr array user), channels: (addr array channel), items: (addr item-list) {
1303   var env/edi: (addr environment) <- copy _env
1304   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1305   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1306   var tabs/ecx: (addr array tab) <- copy _tabs
1307   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1308   var current-tab-index/eax: int <- copy *current-tab-index-a
1309   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1310   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1311   var current-tab-type/eax: (addr int) <- get current-tab, type
1312   compare *current-tab-type, 0/all-items
1313   {
1314     break-if-!=
1315     all-items-page-down current-tab, users, channels, items
1316     return
1317   }
1318   compare *current-tab-type, 1/channel
1319   {
1320     break-if-!=
1321     channel-page-down current-tab, users, channels, items
1322     return
1323   }
1324   compare *current-tab-type, 2/search
1325   {
1326     break-if-!=
1327     search-page-down current-tab, users, channels, items
1328     return
1329   }
1330   compare *current-tab-type, 3/thread
1331   {
1332     break-if-!=
1333     thread-page-down current-tab, users, channels, items
1334     return
1335   }
1336 }
1337 
1338 fn all-items-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1339   var items/esi: (addr item-list) <- copy _items
1340   var items-data-ah/eax: (addr handle array item) <- get items, data
1341   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1342   var items-data/ebx: (addr array item) <- copy _items-data
1343   var current-tab/eax: (addr tab) <- copy _current-tab
1344   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1345   var new-item-index/ecx: int <- copy *current-tab-item-index-addr
1346   var y/edx: int <- copy 2
1347   {
1348     compare new-item-index, 0
1349     break-if-<
1350     compare y, 0x28/screen-height-minus-menu
1351     break-if->=
1352     var offset/eax: (offset item) <- compute-offset items-data, new-item-index
1353     var item/eax: (addr item) <- index items-data, offset
1354     var item-text-ah/eax: (addr handle array byte) <- get item, text
1355     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1356     var h/eax: int <- estimate-height item-text
1357     y <- add h
1358     new-item-index <- decrement
1359     loop
1360   }
1361   new-item-index <- increment
1362   {
1363     # HACK: make sure we make forward progress even if a single post takes up
1364     # the whole screen.
1365     # We can't see the rest of that single post at the moment. But at least we
1366     # can go past it.
1367     compare new-item-index, *current-tab-item-index-addr
1368     break-if-!=
1369     # Don't make "forward progress" past post 0.
1370     compare new-item-index, 0
1371     break-if-=
1372     new-item-index <- decrement
1373   }
1374   copy-to *current-tab-item-index-addr, new-item-index
1375 }
1376 
1377 fn channel-page-down _current-tab: (addr tab), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1378   var current-tab/edi: (addr tab) <- copy _current-tab
1379   var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1380   var current-channel-index/eax: int <- copy *current-channel-index-addr
1381   var channels/esi: (addr array channel) <- copy _channels
1382   var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1383   var current-channel/esi: (addr channel) <- index channels, current-channel-offset
1384   var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
1385   var _current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
1386   var current-channel-posts/esi: (addr array int) <- copy _current-channel-posts
1387   var items/eax: (addr item-list) <- copy _items
1388   var items-data-ah/eax: (addr handle array item) <- get items, data
1389   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1390   var items-data/ebx: (addr array item) <- copy _items-data
1391   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1392   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1393   var y/edx: int <- copy 2
1394   {
1395     compare new-tab-item-index, 0
1396     break-if-<
1397     compare y, 0x28/screen-height-minus-menu
1398     break-if->=
1399     var current-item-index-addr/eax: (addr int) <- index current-channel-posts, new-tab-item-index
1400     var current-item-index/eax: int <- copy *current-item-index-addr
1401     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1402     var item/eax: (addr item) <- index items-data, offset
1403     var item-text-ah/eax: (addr handle array byte) <- get item, text
1404     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1405     var h/eax: int <- estimate-height item-text
1406     y <- add h
1407     new-tab-item-index <- decrement
1408     loop
1409   }
1410   new-tab-item-index <- increment
1411   {
1412     # HACK: make sure we make forward progress even if a single post takes up
1413     # the whole screen.
1414     # We can't see the rest of that single post at the moment. But at least we
1415     # can go past it.
1416     compare new-tab-item-index, *current-tab-item-index-addr
1417     break-if-!=
1418     # Don't make "forward progress" past post 0.
1419     compare new-tab-item-index, 0
1420     break-if-=
1421     new-tab-item-index <- decrement
1422   }
1423   copy-to *current-tab-item-index-addr, new-tab-item-index
1424 }
1425 
1426 fn search-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1427   var current-tab/edi: (addr tab) <- copy _current-tab
1428   var current-tab-search-items-ah/eax: (addr handle array int) <- get current-tab, search-items
1429   var _current-tab-search-items/eax: (addr array int) <- lookup *current-tab-search-items-ah
1430   var current-tab-search-items/esi: (addr array int) <- copy _current-tab-search-items
1431   var items/eax: (addr item-list) <- copy _items
1432   var items-data-ah/eax: (addr handle array item) <- get items, data
1433   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1434   var items-data/ebx: (addr array item) <- copy _items-data
1435   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1436   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1437   var y/edx: int <- copy 2
1438   {
1439     compare new-tab-item-index, 0
1440     break-if-<
1441     compare y, 0x28/screen-height-minus-menu
1442     break-if->=
1443     var current-item-index-addr/eax: (addr int) <- index current-tab-search-items, new-tab-item-index
1444     var current-item-index/eax: int <- copy *current-item-index-addr
1445     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1446     var item/eax: (addr item) <- index items-data, offset
1447     var item-text-ah/eax: (addr handle array byte) <- get item, text
1448     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1449     var h/eax: int <- estimate-height item-text
1450     y <- add h
1451     new-tab-item-index <- decrement
1452     loop
1453   }
1454   new-tab-item-index <- increment
1455   {
1456     # HACK: make sure we make forward progress even if a single post takes up
1457     # the whole screen.
1458     # We can't see the rest of that single post at the moment. But at least we
1459     # can go past it.
1460     compare new-tab-item-index, *current-tab-item-index-addr
1461     break-if-!=
1462     # Don't make "forward progress" past post 0.
1463     compare new-tab-item-index, 0
1464     break-if-=
1465     new-tab-item-index <- decrement
1466   }
1467   copy-to *current-tab-item-index-addr, new-tab-item-index
1468 }
1469 
1470 fn thread-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1471   var current-tab/edi: (addr tab) <- copy _current-tab
1472   var items/eax: (addr item-list) <- copy _items
1473   var items-data-ah/eax: (addr handle array item) <- get items, data
1474   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1475   var items-data/esi: (addr array item) <- copy _items-data
1476   var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1477   var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1478   var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1479   var post/eax: (addr item) <- index items-data, current-tab-root-offset
1480   var post-comments-ah/eax: (addr handle array int) <- get post, comments
1481   var _post-comments/eax: (addr array int) <- lookup *post-comments-ah
1482   var post-comments/ebx: (addr array int) <- copy _post-comments
1483   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1484   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1485   var y/edx: int <- copy 2
1486   {
1487     compare new-tab-item-index, 0
1488     break-if-<
1489     compare y, 0x28/screen-height-minus-menu
1490     break-if->=
1491     var current-item-index-addr/eax: (addr int) <- index post-comments, new-tab-item-index
1492     var current-item-index/eax: int <- copy *current-item-index-addr
1493     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1494     var item/eax: (addr item) <- index items-data, offset
1495     var item-text-ah/eax: (addr handle array byte) <- get item, text
1496     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1497     var h/eax: int <- estimate-height item-text
1498     y <- add h
1499     new-tab-item-index <- decrement
1500     loop
1501   }
1502   new-tab-item-index <- increment
1503   {
1504     # HACK: make sure we make forward progress even if a single post takes up
1505     # the whole screen.
1506     # We can't see the rest of that single post at the moment. But at least we
1507     # can go past it.
1508     compare new-tab-item-index, *current-tab-item-index-addr
1509     break-if-!=
1510     # Don't make "forward progress" past post 0.
1511     compare new-tab-item-index, 0
1512     break-if-=
1513     new-tab-item-index <- decrement
1514   }
1515   copy-to *current-tab-item-index-addr, new-tab-item-index
1516 }
1517 
1518 fn page-up _env: (addr environment), users: (addr array user), channels: (addr array channel), items: (addr item-list) {
1519   var env/edi: (addr environment) <- copy _env
1520   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1521   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1522   var tabs/ecx: (addr array tab) <- copy _tabs
1523   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1524   var current-tab-index/eax: int <- copy *current-tab-index-a
1525   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1526   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1527   var current-tab-type/eax: (addr int) <- get current-tab, type
1528   compare *current-tab-type, 0/all-items
1529   {
1530     break-if-!=
1531     all-items-page-up current-tab, users, channels, items
1532     return
1533   }
1534   compare *current-tab-type, 1/channel
1535   {
1536     break-if-!=
1537     channel-page-up current-tab, users, channels, items
1538     return
1539   }
1540   compare *current-tab-type, 2/search
1541   {
1542     break-if-!=
1543     search-page-up current-tab, users, channels, items
1544     return
1545   }
1546   compare *current-tab-type, 3/thread
1547   {
1548     break-if-!=
1549     thread-page-up current-tab, users, channels, items
1550     return
1551   }
1552 }
1553 
1554 fn all-items-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1555   var items/esi: (addr item-list) <- copy _items
1556   var items-data-ah/eax: (addr handle array item) <- get items, data
1557   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1558   var items-data/ebx: (addr array item) <- copy _items-data
1559   var items-data-first-free-a/eax: (addr int) <- get items, data-first-free
1560   var final-item-index/esi: int <- copy *items-data-first-free-a
1561   final-item-index <- decrement
1562   var current-tab/eax: (addr tab) <- copy _current-tab
1563   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1564   var new-item-index/ecx: int <- copy *current-tab-item-index-addr
1565   var y/edx: int <- copy 2
1566   {
1567     compare new-item-index, final-item-index
1568     break-if->
1569     compare y, 0x28/screen-height-minus-menu
1570     break-if->=
1571     var offset/eax: (offset item) <- compute-offset items-data, new-item-index
1572     var item/eax: (addr item) <- index items-data, offset
1573     var item-text-ah/eax: (addr handle array byte) <- get item, text
1574     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1575     var h/eax: int <- estimate-height item-text
1576     y <- add h
1577     new-item-index <- increment
1578     loop
1579   }
1580   new-item-index <- decrement
1581   copy-to *current-tab-item-index-addr, new-item-index
1582 }
1583 
1584 fn channel-page-up _current-tab: (addr tab), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1585   var current-tab/edi: (addr tab) <- copy _current-tab
1586   var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1587   var current-channel-index/eax: int <- copy *current-channel-index-addr
1588   var channels/esi: (addr array channel) <- copy _channels
1589   var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1590   var current-channel/esi: (addr channel) <- index channels, current-channel-offset
1591   var current-channel-posts-first-free-addr/eax: (addr int) <- get current-channel, posts-first-free
1592   var tmp/eax: int <- copy *current-channel-posts-first-free-addr
1593   var final-tab-post-index: int
1594   copy-to final-tab-post-index, tmp
1595   decrement final-tab-post-index
1596   var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
1597   var _current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
1598   var current-channel-posts/esi: (addr array int) <- copy _current-channel-posts
1599   var items/esi: (addr item-list) <- copy _items
1600   var items-data-ah/eax: (addr handle array item) <- get items, data
1601   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1602   var items-data/ebx: (addr array item) <- copy _items-data
1603   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1604   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1605   var y/edx: int <- copy 2
1606   {
1607     compare new-tab-item-index, final-tab-post-index
1608     break-if->
1609     compare y, 0x28/screen-height-minus-menu
1610     break-if->=
1611     var offset/eax: (offset item) <- compute-offset items-data, new-tab-item-index
1612     var item/eax: (addr item) <- index items-data, offset
1613     var item-text-ah/eax: (addr handle array byte) <- get item, text
1614     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1615     var h/eax: int <- estimate-height item-text
1616     y <- add h
1617     new-tab-item-index <- increment
1618     loop
1619   }
1620   new-tab-item-index <- decrement
1621   copy-to *current-tab-item-index-addr, new-tab-item-index
1622 }
1623 
1624 fn search-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1625   var current-tab/edi: (addr tab) <- copy _current-tab
1626   var current-tab-search-items-first-free-addr/eax: (addr int) <- get current-tab, search-items-first-free
1627   var final-tab-post-index: int
1628   var tmp/eax: int <- copy *current-tab-search-items-first-free-addr
1629   copy-to final-tab-post-index, tmp
1630   decrement final-tab-post-index
1631   var current-tab-search-items-ah/eax: (addr handle array int) <- get current-tab, search-items
1632   var _current-tab-search-items/eax: (addr array int) <- lookup *current-tab-search-items-ah
1633   var current-tab-search-items/esi: (addr array int) <- copy _current-tab-search-items
1634   var items/eax: (addr item-list) <- copy _items
1635   var items-data-ah/eax: (addr handle array item) <- get items, data
1636   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1637   var items-data/ebx: (addr array item) <- copy _items-data
1638   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1639   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1640   var y/edx: int <- copy 2
1641   {
1642     compare new-tab-item-index, final-tab-post-index
1643     break-if->
1644     compare y, 0x28/screen-height-minus-menu
1645     break-if->=
1646     var current-item-index-addr/eax: (addr int) <- index current-tab-search-items, new-tab-item-index
1647     var current-item-index/eax: int <- copy *current-item-index-addr
1648     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1649     var item/eax: (addr item) <- index items-data, offset
1650     var item-text-ah/eax: (addr handle array byte) <- get item, text
1651     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1652     var h/eax: int <- estimate-height item-text
1653     y <- add h
1654     new-tab-item-index <- increment
1655     loop
1656   }
1657   new-tab-item-index <- decrement
1658   copy-to *current-tab-item-index-addr, new-tab-item-index
1659 }
1660 
1661 fn thread-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1662   var current-tab/edi: (addr tab) <- copy _current-tab
1663   var items/eax: (addr item-list) <- copy _items
1664   var items-data-ah/eax: (addr handle array item) <- get items, data
1665   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1666   var items-data/esi: (addr array item) <- copy _items-data
1667   var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1668   var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1669   var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1670   var post/eax: (addr item) <- index items-data, current-tab-root-offset
1671   var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
1672   var post-comments-ah/eax: (addr handle array int) <- get post, comments
1673   var _post-comments/eax: (addr array int) <- lookup *post-comments-ah
1674   var post-comments/ebx: (addr array int) <- copy _post-comments
1675   var final-tab-comment-index: int
1676   {
1677     var tmp/eax: int <- copy *post-comments-first-free-addr
1678     tmp <- decrement
1679     copy-to final-tab-comment-index, tmp
1680   }
1681   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1682   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1683   var y/edx: int <- copy 2
1684   {
1685     compare new-tab-item-index, final-tab-comment-index
1686     break-if->
1687     compare y, 0x28/screen-height-minus-menu
1688     break-if->=
1689     var current-item-index-addr/eax: (addr int) <- index post-comments, new-tab-item-index
1690     var current-item-index/eax: int <- copy *current-item-index-addr
1691     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1692     var item/eax: (addr item) <- index items-data, offset
1693     var item-text-ah/eax: (addr handle array byte) <- get item, text
1694     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1695     var h/eax: int <- estimate-height item-text
1696     y <- add h
1697     new-tab-item-index <- increment
1698     loop
1699   }
1700   new-tab-item-index <- decrement
1701   copy-to *current-tab-item-index-addr, new-tab-item-index
1702 }
1703 
1704 # keep sync'd with render-item
1705 fn estimate-height _message-text: (addr array byte) -> _/eax: int {
1706   var message-text/esi: (addr array byte) <- copy _message-text
1707   var result/eax: int <- length message-text
1708   var remainder/edx: int <- copy 0
1709   result, remainder <- integer-divide result, 0x40/post-width
1710   compare remainder, 0
1711   {
1712     break-if-=
1713     result <- increment
1714   }
1715   result <- add 2/item-padding-ver
1716   compare result, 6/avatar-space-ver
1717   {
1718     break-if->
1719     return 6/avatar-space-ver
1720   }
1721   return result
1722 }