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-grapheme 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 g/ebx: grapheme <- read-json-grapheme stream
 584     compare g, 0xffffffff/end-of-file
 585     break-if-=
 586     $draw-json-stream-wrapping-right-then-down:render-grapheme: {
 587       compare g, 0x5c/backslash
 588       {
 589         break-if-!=
 590         xcurr, ycurr <- render-json-escaped-grapheme 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-grapheme screen, g, 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-grapheme stream: (addr stream byte) -> _/ebx: grapheme {
 603   var result/eax: grapheme <- read-grapheme stream
 604   return result
 605 }
 606 
 607 # '\' encountered
 608 # https://www.json.org/json-en.html
 609 fn render-json-escaped-grapheme 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 {
 610   var g/ebx: grapheme <- read-json-grapheme stream
 611   compare g, 0xffffffff/end-of-file
 612   {
 613     break-if-!=
 614     return xcurr, ycurr
 615   }
 616   # \n = newline
 617   compare g, 0x6e/n
 618   var x/eax: int <- copy xcurr
 619   {
 620     break-if-!=
 621     increment ycurr
 622     return xmin, ycurr
 623   }
 624   # ignore \t \r \f \b
 625   {
 626     compare g, 0x74/t
 627     break-if-!=
 628     return xcurr, ycurr
 629   }
 630   {
 631     compare g, 0x72/r
 632     break-if-!=
 633     return xcurr, ycurr
 634   }
 635   {
 636     compare g, 0x66/f
 637     break-if-!=
 638     return xcurr, ycurr
 639   }
 640   {
 641     compare g, 0x62/b
 642     break-if-!=
 643     return xcurr, ycurr
 644   }
 645   var y/ecx: int <- copy 0
 646   # \u = Unicode
 647   {
 648     compare g, 0x75/u
 649     break-if-!=
 650     x, y <- render-json-escaped-unicode-grapheme screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 651     return x, y
 652   }
 653   # most characters escape to themselves
 654   x, y <- render-grapheme screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 655   return x, y
 656 }
 657 
 658 # '\u' encountered
 659 fn render-json-escaped-unicode-grapheme 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 {
 660   var ustream-storage: (stream byte 4)
 661   var ustream/esi: (addr stream byte) <- address ustream-storage
 662   # slurp 4 bytes exactly
 663   var b/eax: byte <- read-byte stream
 664   var b-int/eax: int <- copy b
 665   append-byte ustream, b-int
 666   var b/eax: byte <- read-byte stream
 667   var b-int/eax: int <- copy b
 668   append-byte ustream, b-int
 669   var b/eax: byte <- read-byte stream
 670   var b-int/eax: int <- copy b
 671   append-byte ustream, b-int
 672   var b/eax: byte <- read-byte stream
 673   var b-int/eax: int <- copy b
 674   append-byte ustream, b-int
 675   # \u2013 = -
 676   {
 677     var endash?/eax: boolean <- stream-data-equal? ustream, "2013"
 678     compare endash?, 0/false
 679     break-if-=
 680     var x/eax: int <- copy 0
 681     var y/ecx: int <- copy 0
 682     x, y <- render-grapheme screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 683     return x, y
 684   }
 685   # \u2014 = -
 686   {
 687     var emdash?/eax: boolean <- stream-data-equal? ustream, "2014"
 688     compare emdash?, 0/false
 689     break-if-=
 690     var x/eax: int <- copy 0
 691     var y/ecx: int <- copy 0
 692     x, y <- render-grapheme screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 693     return x, y
 694   }
 695   # \u2018 = '
 696   {
 697     var left-quote?/eax: boolean <- stream-data-equal? ustream, "2018"
 698     compare left-quote?, 0/false
 699     break-if-=
 700     var x/eax: int <- copy 0
 701     var y/ecx: int <- copy 0
 702     x, y <- render-grapheme screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 703     return x, y
 704   }
 705   # \u2019 = '
 706   {
 707     var right-quote?/eax: boolean <- stream-data-equal? ustream, "2019"
 708     compare right-quote?, 0/false
 709     break-if-=
 710     var x/eax: int <- copy 0
 711     var y/ecx: int <- copy 0
 712     x, y <- render-grapheme screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 713     return x, y
 714   }
 715   # \u201c = "
 716   {
 717     var left-dquote?/eax: boolean <- stream-data-equal? ustream, "201c"
 718     compare left-dquote?, 0/false
 719     break-if-=
 720     var x/eax: int <- copy 0
 721     var y/ecx: int <- copy 0
 722     x, y <- render-grapheme screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 723     return x, y
 724   }
 725   # \u201d = "
 726   {
 727     var right-dquote?/eax: boolean <- stream-data-equal? ustream, "201d"
 728     compare right-dquote?, 0/false
 729     break-if-=
 730     var x/eax: int <- copy 0
 731     var y/ecx: int <- copy 0
 732     x, y <- render-grapheme screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 733     return x, y
 734   }
 735   # \u2022 = *
 736   {
 737     var bullet?/eax: boolean <- stream-data-equal? ustream, "2022"
 738     compare bullet?, 0/false
 739     break-if-=
 740     var x/eax: int <- copy 0
 741     var y/ecx: int <- copy 0
 742     x, y <- render-grapheme screen, 0x2a/asterisk, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 743     return x, y
 744   }
 745   # \u2026 = ...
 746   {
 747     var ellipses?/eax: boolean <- stream-data-equal? ustream, "2026"
 748     compare ellipses?, 0/false
 749     break-if-=
 750     var x/eax: int <- copy 0
 751     var y/ecx: int <- copy 0
 752     x, y <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 753     return x, y
 754   }
 755   # TODO: rest of Unicode
 756   var x/eax: int <- copy 0
 757   var y/ecx: int <- copy 0
 758   x, y <- draw-stream-wrapping-right-then-down screen, ustream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
 759   return x, y
 760 }
 761 
 762 ### Edit
 763 
 764 fn update-environment _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 765   var env/edi: (addr environment) <- copy _env
 766   # first dispatch to search mode if necessary
 767   {
 768     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 769     compare *cursor-in-search?, 0/false
 770     break-if-=
 771     update-search env, key, users, channels, items
 772     return
 773   }
 774   {
 775     compare key, 0x2f/slash
 776     break-if-!=
 777     # enter search mode
 778     var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 779     copy-to *cursor-in-search?, 1/true
 780     # do one more repaint
 781     var dirty?/eax: (addr boolean) <- get env, dirty?
 782     copy-to *dirty?, 1/true
 783     return
 784   }
 785   {
 786     compare key, 0x1b/esc
 787     break-if-!=
 788     # back in history
 789     previous-tab env
 790     return
 791   }
 792   var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 793   {
 794     compare key, 9/tab
 795     break-if-!=
 796     # toggle cursor between main panel and channels nav
 797     not *cursor-in-channels?  # bitwise NOT; only works if you never assign 1/true to this variable
 798     # do one more repaint
 799     var dirty?/eax: (addr boolean) <- get env, dirty?
 800     copy-to *dirty?, 1/true
 801     return
 802   }
 803   {
 804     compare *cursor-in-channels?, 0/false
 805     break-if-!=
 806     update-main-panel env, key, users, channels, items
 807     return
 808   }
 809   {
 810     compare *cursor-in-channels?, 0/false
 811     break-if-=
 812     update-channels-nav env, key, users, channels, items
 813     return
 814   }
 815 }
 816 
 817 fn update-main-panel env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 818   {
 819     compare key, 0xa/newline
 820     break-if-!=
 821     new-thread-tab env, users, channels, items
 822     return
 823   }
 824   {
 825     compare key, 0x81/down-arrow
 826     break-if-!=
 827     next-item env, users, channels, items
 828     return
 829   }
 830   {
 831     compare key, 0x82/up-arrow
 832     break-if-!=
 833     previous-item env, users, channels, items
 834     return
 835   }
 836   {
 837     compare key, 6/ctrl-f
 838     break-if-!=
 839     page-down env, users, channels, items
 840     return
 841   }
 842   {
 843     compare key, 2/ctrl-b
 844     break-if-!=
 845     page-up env, users, channels, items
 846     return
 847   }
 848 }
 849 
 850 # TODO: clamp cursor within bounds
 851 fn update-channels-nav _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 852   var env/edi: (addr environment) <- copy _env
 853   var channel-cursor-index/eax: (addr int) <- get env, channel-cursor-index
 854   {
 855     compare key, 0x81/down-arrow
 856     break-if-!=
 857     increment *channel-cursor-index
 858     return
 859   }
 860   {
 861     compare key, 0x82/up-arrow
 862     break-if-!=
 863     decrement *channel-cursor-index
 864     return
 865   }
 866   {
 867     compare key, 0xa/newline
 868     break-if-!=
 869     new-channel-tab env, *channel-cursor-index, channels
 870     var cursor-in-channels?/eax: (addr boolean) <- get env, cursor-in-channels?
 871     copy-to *cursor-in-channels?, 0/false
 872     return
 873   }
 874 }
 875 
 876 fn update-search _env: (addr environment), key: byte, users: (addr array user), channels: (addr array channel), items: (addr item-list) {
 877   var env/edi: (addr environment) <- copy _env
 878   var cursor-in-search?/eax: (addr boolean) <- get env, cursor-in-search?
 879   {
 880     compare key 0x1b/esc
 881     break-if-!=
 882     # get out of search mode
 883     copy-to *cursor-in-search?, 0/false
 884     return
 885   }
 886   {
 887     compare key, 0xa/newline
 888     break-if-!=
 889     # perform a search, then get out of search mode
 890     new-search-tab env, items
 891     copy-to *cursor-in-search?, 0/false
 892     return
 893   }
 894   # otherwise delegate
 895   var search-terms-ah/eax: (addr handle gap-buffer) <- get env, search-terms
 896   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
 897   var g/ecx: grapheme <- copy key
 898   edit-gap-buffer search-terms, g
 899 }
 900 
 901 fn new-thread-tab _env: (addr environment), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
 902   var env/edi: (addr environment) <- copy _env
 903   var current-tab-index-a/ecx: (addr int) <- get env, current-tab-index
 904   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
 905   var tabs/eax: (addr array tab) <- lookup *tabs-ah
 906   var current-tab-index/ecx: int <- copy *current-tab-index-a
 907   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
 908   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
 909   var item-index/esi: int <- item-index current-tab, channels
 910   var post-index/ecx: int <- post-index _items, item-index
 911   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
 912   increment *current-tab-index-addr
 913   var current-tab-index/edx: int <- copy *current-tab-index-addr
 914   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
 915   var tabs/eax: (addr array tab) <- lookup *tabs-ah
 916   var max-tabs/ebx: int <- length tabs
 917   compare current-tab-index, max-tabs
 918   {
 919     compare current-tab-index, max-tabs
 920     break-if-<
 921     abort "history overflow; grow max-history (we should probably improve this)"
 922   }
 923   var current-tab-offset/edi: (offset tab) <- compute-offset tabs, current-tab-index
 924   var current-tab/edi: (addr tab) <- index tabs, current-tab-offset
 925   clear-object current-tab
 926   var current-tab-type/eax: (addr int) <- get current-tab, type
 927   copy-to *current-tab, 3/thread
 928   var current-tab-root-index/eax: (addr int) <- get current-tab, root-index
 929   copy-to *current-tab-root-index, post-index
 930   var items/eax: (addr item-list) <- copy _items
 931   var items-data-ah/eax: (addr handle array item) <- get items, data
 932   var items-data/eax: (addr array item) <- lookup *items-data-ah
 933   var offset/ecx: (offset item) <- compute-offset items-data, post-index
 934   var post/eax: (addr item) <- index items-data, offset
 935   var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
 936   # terminology:
 937   #   post-comment-index = index of a comment in a post's comment array
 938   #   comment-index = index of a comment in the global item list
 939   var final-post-comment-index/ecx: int <- copy *post-comments-first-free-addr
 940   final-post-comment-index <- decrement
 941   var post-comments-ah/eax: (addr handle array int) <- get post, comments
 942   var post-comments/eax: (addr array int) <- lookup *post-comments-ah
 943   # look for item-index in post-comments[0..final-post-comment-index]
 944   var curr-post-comment-index/edx: int <- copy final-post-comment-index
 945   {
 946     compare curr-post-comment-index, 0
 947     {
 948       break-if->=
 949       # if we didn't find the current item in a post's comments, it must be
 950       # the parent post itself which isn't in the comment list but hackily
 951       # rendered at the bottom. Just render the whole comment list.
 952       var tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
 953       copy-to *tab-item-index-addr, curr-post-comment-index
 954       return
 955     }
 956     var curr-comment-index/ecx: (addr int) <- index post-comments, curr-post-comment-index
 957     compare *curr-comment-index, item-index
 958     {
 959       break-if-!=
 960       # item-index found
 961       var tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
 962       copy-to *tab-item-index-addr, curr-post-comment-index
 963       return
 964     }
 965     curr-post-comment-index <- decrement
 966     loop
 967   }
 968   abort "new-thread-tab: should never leave previous loop without returning"
 969 }
 970 
 971 fn item-index _tab: (addr tab), _channels: (addr array channel) -> _/esi: int {
 972   var tab/esi: (addr tab) <- copy _tab
 973   var tab-type/eax: (addr int) <- get tab, type
 974   {
 975     compare *tab-type, 0/all-items
 976     break-if-!=
 977     var tab-item-index/eax: (addr int) <- get tab, item-index
 978     return *tab-item-index
 979   }
 980   {
 981     compare *tab-type, 1/channel
 982     break-if-!=
 983     var channel-index-addr/eax: (addr int) <- get tab, channel-index
 984     var channel-index/eax: int <- copy *channel-index-addr
 985     var channels/ecx: (addr array channel) <- copy _channels
 986     var channel-offset/eax: (offset channel) <- compute-offset channels, channel-index
 987     var current-channel/eax: (addr channel) <- index channels, channel-offset
 988     var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
 989     var current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
 990     var channel-item-index-addr/ecx: (addr int) <- get tab, item-index
 991     var channel-item-index/ecx: int <- copy *channel-item-index-addr
 992     var channel-item-index/eax: (addr int) <- index current-channel-posts, channel-item-index
 993     return *channel-item-index
 994   }
 995   {
 996     compare *tab-type, 2/search
 997     break-if-!=
 998     var tab-search-items-ah/eax: (addr handle array int) <- get tab, search-items
 999     var tab-search-items/eax: (addr array int) <- lookup *tab-search-items-ah
1000     var tab-search-items-index-addr/ecx: (addr int) <- get tab, item-index
1001     var tab-search-items-index/ecx: int <- copy *tab-search-items-index-addr
1002     var src/eax: (addr int) <- index tab-search-items, tab-search-items-index
1003     return *src
1004   }
1005   abort "item-index: unknown tab type"
1006   return -1
1007 }
1008 
1009 fn post-index _items: (addr item-list), item-index: int -> _/ecx: int {
1010   var items/eax: (addr item-list) <- copy _items
1011   var items-data-ah/eax: (addr handle array item) <- get items, data
1012   var items-data/eax: (addr array item) <- lookup *items-data-ah
1013   var index/ecx: int <- copy item-index
1014   var offset/ecx: (offset item) <- compute-offset items-data, index
1015   var item/eax: (addr item) <- index items-data, offset
1016   var parent/eax: (addr int) <- get item, parent
1017   compare *parent, 0
1018   {
1019     break-if-=
1020     return *parent
1021   }
1022   return item-index
1023 }
1024 
1025 fn new-channel-tab _env: (addr environment), channel-index: int, _channels: (addr array channel) {
1026   var env/edi: (addr environment) <- copy _env
1027   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
1028   increment *current-tab-index-addr
1029   var current-tab-index/ecx: int <- copy *current-tab-index-addr
1030   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1031   var tabs/eax: (addr array tab) <- lookup *tabs-ah
1032   var max-tabs/edx: int <- length tabs
1033   compare current-tab-index, max-tabs
1034   {
1035     compare current-tab-index, max-tabs
1036     break-if-<
1037     abort "history overflow; grow max-history (we should probably improve this)"
1038   }
1039   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1040   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1041   clear-object current-tab
1042   var current-tab-type/eax: (addr int) <- get current-tab, type
1043   copy-to *current-tab, 1/channel
1044   var current-tab-channel-index/eax: (addr int) <- get current-tab, channel-index
1045   var curr-channel-index/edx: int <- copy channel-index
1046   copy-to *current-tab-channel-index, curr-channel-index
1047   var channels/esi: (addr array channel) <- copy _channels
1048   var curr-channel-offset/eax: (offset channel) <- compute-offset channels, curr-channel-index
1049   var curr-channel/eax: (addr channel) <- index channels, curr-channel-offset
1050   var curr-channel-posts-first-free-addr/eax: (addr int) <- get curr-channel, posts-first-free
1051   var curr-channel-final-post-index/eax: int <- copy *curr-channel-posts-first-free-addr
1052   curr-channel-final-post-index <- decrement
1053   var dest/edi: (addr int) <- get current-tab, item-index
1054   copy-to *dest, curr-channel-final-post-index
1055 }
1056 
1057 fn new-search-tab _env: (addr environment), items: (addr item-list) {
1058   var env/edi: (addr environment) <- copy _env
1059   var current-tab-index-addr/eax: (addr int) <- get env, current-tab-index
1060   increment *current-tab-index-addr
1061   var current-tab-index/ecx: int <- copy *current-tab-index-addr
1062   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1063   var tabs/eax: (addr array tab) <- lookup *tabs-ah
1064   var max-tabs/edx: int <- length tabs
1065   compare current-tab-index, max-tabs
1066   {
1067     compare current-tab-index, max-tabs
1068     break-if-<
1069     abort "history overflow; grow max-history (we should probably improve this)"
1070   }
1071   var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1072   var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1073   clear-object current-tab
1074   var current-tab-type/eax: (addr int) <- get current-tab, type
1075   copy-to *current-tab, 2/search
1076   var current-tab-search-terms-ah/edx: (addr handle gap-buffer) <- get current-tab, search-terms
1077   allocate current-tab-search-terms-ah
1078   var current-tab-search-terms/eax: (addr gap-buffer) <- lookup *current-tab-search-terms-ah
1079   initialize-gap-buffer current-tab-search-terms, 0x30/search-capacity
1080   var search-terms-ah/ebx: (addr handle gap-buffer) <- get env, search-terms
1081   copy-gap-buffer search-terms-ah, current-tab-search-terms-ah
1082   var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
1083   search-items current-tab, items, search-terms
1084 }
1085 
1086 fn search-items _tab: (addr tab), _items: (addr item-list), search-terms: (addr gap-buffer) {
1087   var tab/edi: (addr tab) <- copy _tab
1088   var tab-items-first-free-addr/esi: (addr int) <- get tab, search-items-first-free
1089   var tab-items-ah/eax: (addr handle array int) <- get tab, search-items
1090   populate tab-items-ah, 0x100/max-search-results
1091   var _tab-items/eax: (addr array int) <- lookup *tab-items-ah
1092   var tab-items/edi: (addr array int) <- copy _tab-items
1093   # preprocess search-terms
1094   var search-terms-stream-storage: (stream byte 0x100)
1095   var search-terms-stream-addr/ecx: (addr stream byte) <- address search-terms-stream-storage
1096   emit-gap-buffer search-terms, search-terms-stream-addr
1097   var search-terms-text-h: (handle array byte)
1098   var search-terms-text-ah/eax: (addr handle array byte) <- address search-terms-text-h
1099   stream-to-array search-terms-stream-addr, search-terms-text-ah
1100   var tmp/eax: (addr array byte) <- lookup *search-terms-text-ah
1101   var search-terms-text: (addr array byte)
1102   copy-to search-terms-text, tmp
1103   #
1104   var items/ecx: (addr item-list) <- copy _items
1105   var items-data-ah/eax: (addr handle array item) <- get items, data
1106   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1107   var items-data/ebx: (addr array item) <- copy _items-data
1108   var items-data-first-free-a/edx: (addr int) <- get items, data-first-free
1109   var i/ecx: int <- copy 0
1110   {
1111     compare i, *items-data-first-free-a
1112     break-if->=
1113     var curr-offset/eax: (offset item) <- compute-offset items-data, i
1114     var curr-item/eax: (addr item) <- index items-data, curr-offset
1115     var found?/eax: boolean <- search-terms-match? curr-item, search-terms-text
1116     compare found?, 0/false
1117     {
1118       break-if-=
1119       var tab-items-first-free/eax: int <- copy *tab-items-first-free-addr
1120       compare tab-items-first-free, 0x100/max-search-results
1121       break-if->=
1122       var dest/eax: (addr int) <- index tab-items, tab-items-first-free
1123       copy-to *dest, i
1124       increment *tab-items-first-free-addr
1125     }
1126     i <- increment
1127     loop
1128   }
1129   var tab/edi: (addr tab) <- copy _tab
1130   var tab-item-index-addr/edi: (addr int) <- get tab, item-index
1131   var tab-items-first-free/eax: int <- copy *tab-items-first-free-addr
1132   tab-items-first-free <- decrement
1133   copy-to *tab-item-index-addr, tab-items-first-free
1134 }
1135 
1136 fn search-terms-match? _item: (addr item), search-terms: (addr array byte) -> _/eax: boolean {
1137   var item/esi: (addr item) <- copy _item
1138   var item-text-ah/eax: (addr handle array byte) <- get item, text
1139   var item-text/eax: (addr array byte) <- lookup *item-text-ah
1140   var i/ecx: int <- copy 0
1141   var max/edx: int <- length item-text
1142   var search-terms2/ebx: (addr array byte) <- copy search-terms
1143   var slen/ebx: int <- length search-terms2
1144   max <- subtract slen
1145   {
1146     compare i, max
1147     break-if->
1148     var found?/eax: boolean <- substring-match? item-text, search-terms, i
1149     compare found?, 0/false
1150     {
1151       break-if-=
1152       return 1/true
1153     }
1154     i <- increment
1155     loop
1156   }
1157   return 0/false
1158 }
1159 
1160 fn substring-match? _s: (addr array byte), _pat: (addr array byte), start: int -> _/eax: boolean {
1161   var s/esi: (addr array byte) <- copy _s
1162   var pat/edi: (addr array byte) <- copy _pat
1163   var s-idx/edx: int <- copy start
1164   var pat-idx/ebx: int <- copy 0
1165   var pat-len: int
1166   var tmp/eax: int <- length pat
1167   copy-to pat-len, tmp
1168   {
1169     compare pat-idx, pat-len
1170     break-if->=
1171     var s-ab/eax: (addr byte) <- index s, s-idx
1172     var s-b/eax: byte <- copy-byte *s-ab
1173     var pat-ab/ecx: (addr byte) <- index pat, pat-idx
1174     var pat-b/ecx: byte <- copy-byte *pat-ab
1175     compare s-b, pat-b
1176     {
1177       break-if-=
1178       return 0/false
1179     }
1180     s-idx <- increment
1181     pat-idx <- increment
1182     loop
1183   }
1184   return 1/true
1185 }
1186 
1187 fn previous-tab _env: (addr environment) {
1188   var env/edi: (addr environment) <- copy _env
1189   var current-tab-index-addr/ecx: (addr int) <- get env, current-tab-index
1190   compare *current-tab-index-addr, 0
1191   {
1192     break-if-<=
1193     decrement *current-tab-index-addr
1194     # if necessary restore search state
1195     var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1196     var tabs/eax: (addr array tab) <- lookup *tabs-ah
1197     var current-tab-index/ecx: int <- copy *current-tab-index-addr
1198     var current-tab-offset/ecx: (offset tab) <- compute-offset tabs, current-tab-index
1199     var current-tab/ecx: (addr tab) <- index tabs, current-tab-offset
1200     var current-tab-type/eax: (addr int) <- get current-tab, type
1201     compare *current-tab-type, 2/search
1202     break-if-!=
1203     var current-tab-search-terms-ah/ecx: (addr handle gap-buffer) <- get current-tab, search-terms
1204     var search-terms-ah/edx: (addr handle gap-buffer) <- get env, search-terms
1205     var search-terms/eax: (addr gap-buffer) <- lookup *search-terms-ah
1206     clear-gap-buffer search-terms
1207     copy-gap-buffer current-tab-search-terms-ah, search-terms-ah
1208   }
1209 }
1210 
1211 fn next-item _env: (addr environment), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1212   var env/edi: (addr environment) <- copy _env
1213   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1214   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1215   var tabs/edx: (addr array tab) <- copy _tabs
1216   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1217   var current-tab-index/eax: int <- copy *current-tab-index-a
1218   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1219   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1220   var dest/eax: (addr int) <- get current-tab, item-index
1221   compare *dest, 0
1222   break-if-<=
1223   decrement *dest
1224 }
1225 
1226 fn previous-item _env: (addr environment), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1227   var env/edi: (addr environment) <- copy _env
1228   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1229   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1230   var tabs/edx: (addr array tab) <- copy _tabs
1231   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1232   var current-tab-index/eax: int <- copy *current-tab-index-a
1233   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1234   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1235   var current-tab-type/eax: (addr int) <- get current-tab, type
1236   compare *current-tab-type, 0/all-items
1237   {
1238     break-if-!=
1239     var items/esi: (addr item-list) <- copy _items
1240     var items-data-first-free-a/ecx: (addr int) <- get items, data-first-free
1241     var final-item-index/ecx: int <- copy *items-data-first-free-a
1242     final-item-index <- decrement
1243     var dest/eax: (addr int) <- get current-tab, item-index
1244     compare *dest, final-item-index
1245     break-if->=
1246     increment *dest
1247     return
1248   }
1249   compare *current-tab-type, 1/channel
1250   {
1251     break-if-!=
1252     var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1253     var current-channel-index/eax: int <- copy *current-channel-index-addr
1254     var channels/esi: (addr array channel) <- copy _channels
1255     var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1256     var current-channel/eax: (addr channel) <- index channels, current-channel-offset
1257     var current-channel-posts-first-free-addr/eax: (addr int) <- get current-channel, posts-first-free
1258     var final-item-index/ecx: int <- copy *current-channel-posts-first-free-addr
1259     final-item-index <- decrement
1260     var dest/eax: (addr int) <- get current-tab, item-index
1261     compare *dest, final-item-index
1262     break-if->=
1263     increment *dest
1264     return
1265   }
1266   compare *current-tab-type, 2/search
1267   {
1268     break-if-!=
1269     var current-tab-search-items-first-free-addr/eax: (addr int) <- get current-tab, search-items-first-free
1270     var final-item-index/ecx: int <- copy *current-tab-search-items-first-free-addr
1271     final-item-index <- decrement
1272     var dest/eax: (addr int) <- get current-tab, item-index
1273     compare *dest, final-item-index
1274     break-if->=
1275     increment *dest
1276     return
1277   }
1278   compare *current-tab-type, 3/thread
1279   {
1280     break-if-!=
1281     var items/eax: (addr item-list) <- copy _items
1282     var items-data-ah/eax: (addr handle array item) <- get items, data
1283     var _items-data/eax: (addr array item) <- lookup *items-data-ah
1284     var items-data/esi: (addr array item) <- copy _items-data
1285     var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1286     var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1287     var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1288     var post/eax: (addr item) <- index items-data, current-tab-root-offset
1289     var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
1290     var final-item-index/ecx: int <- copy *post-comments-first-free-addr
1291     final-item-index <- decrement
1292     var dest/eax: (addr int) <- get current-tab, item-index
1293     compare *dest, final-item-index
1294     break-if->=
1295     increment *dest
1296     return
1297   }
1298 }
1299 
1300 fn page-down _env: (addr environment), users: (addr array user), channels: (addr array channel), items: (addr item-list) {
1301   var env/edi: (addr environment) <- copy _env
1302   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1303   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1304   var tabs/ecx: (addr array tab) <- copy _tabs
1305   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1306   var current-tab-index/eax: int <- copy *current-tab-index-a
1307   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1308   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1309   var current-tab-type/eax: (addr int) <- get current-tab, type
1310   compare *current-tab-type, 0/all-items
1311   {
1312     break-if-!=
1313     all-items-page-down current-tab, users, channels, items
1314     return
1315   }
1316   compare *current-tab-type, 1/channel
1317   {
1318     break-if-!=
1319     channel-page-down current-tab, users, channels, items
1320     return
1321   }
1322   compare *current-tab-type, 2/search
1323   {
1324     break-if-!=
1325     search-page-down current-tab, users, channels, items
1326     return
1327   }
1328   compare *current-tab-type, 3/thread
1329   {
1330     break-if-!=
1331     thread-page-down current-tab, users, channels, items
1332     return
1333   }
1334 }
1335 
1336 fn all-items-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1337   var items/esi: (addr item-list) <- copy _items
1338   var items-data-ah/eax: (addr handle array item) <- get items, data
1339   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1340   var items-data/ebx: (addr array item) <- copy _items-data
1341   var current-tab/eax: (addr tab) <- copy _current-tab
1342   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1343   var new-item-index/ecx: int <- copy *current-tab-item-index-addr
1344   var y/edx: int <- copy 2
1345   {
1346     compare new-item-index, 0
1347     break-if-<
1348     compare y, 0x28/screen-height-minus-menu
1349     break-if->=
1350     var offset/eax: (offset item) <- compute-offset items-data, new-item-index
1351     var item/eax: (addr item) <- index items-data, offset
1352     var item-text-ah/eax: (addr handle array byte) <- get item, text
1353     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1354     var h/eax: int <- estimate-height item-text
1355     y <- add h
1356     new-item-index <- decrement
1357     loop
1358   }
1359   new-item-index <- increment
1360   {
1361     # HACK: make sure we make forward progress even if a single post takes up
1362     # the whole screen.
1363     # We can't see the rest of that single post at the moment. But at least we
1364     # can go past it.
1365     compare new-item-index, *current-tab-item-index-addr
1366     break-if-!=
1367     # Don't make "forward progress" past post 0.
1368     compare new-item-index, 0
1369     break-if-=
1370     new-item-index <- decrement
1371   }
1372   copy-to *current-tab-item-index-addr, new-item-index
1373 }
1374 
1375 fn channel-page-down _current-tab: (addr tab), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1376   var current-tab/edi: (addr tab) <- copy _current-tab
1377   var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1378   var current-channel-index/eax: int <- copy *current-channel-index-addr
1379   var channels/esi: (addr array channel) <- copy _channels
1380   var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1381   var current-channel/esi: (addr channel) <- index channels, current-channel-offset
1382   var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
1383   var _current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
1384   var current-channel-posts/esi: (addr array int) <- copy _current-channel-posts
1385   var items/eax: (addr item-list) <- copy _items
1386   var items-data-ah/eax: (addr handle array item) <- get items, data
1387   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1388   var items-data/ebx: (addr array item) <- copy _items-data
1389   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1390   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1391   var y/edx: int <- copy 2
1392   {
1393     compare new-tab-item-index, 0
1394     break-if-<
1395     compare y, 0x28/screen-height-minus-menu
1396     break-if->=
1397     var current-item-index-addr/eax: (addr int) <- index current-channel-posts, new-tab-item-index
1398     var current-item-index/eax: int <- copy *current-item-index-addr
1399     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1400     var item/eax: (addr item) <- index items-data, offset
1401     var item-text-ah/eax: (addr handle array byte) <- get item, text
1402     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1403     var h/eax: int <- estimate-height item-text
1404     y <- add h
1405     new-tab-item-index <- decrement
1406     loop
1407   }
1408   new-tab-item-index <- increment
1409   {
1410     # HACK: make sure we make forward progress even if a single post takes up
1411     # the whole screen.
1412     # We can't see the rest of that single post at the moment. But at least we
1413     # can go past it.
1414     compare new-tab-item-index, *current-tab-item-index-addr
1415     break-if-!=
1416     # Don't make "forward progress" past post 0.
1417     compare new-tab-item-index, 0
1418     break-if-=
1419     new-tab-item-index <- decrement
1420   }
1421   copy-to *current-tab-item-index-addr, new-tab-item-index
1422 }
1423 
1424 fn search-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1425   var current-tab/edi: (addr tab) <- copy _current-tab
1426   var current-tab-search-items-ah/eax: (addr handle array int) <- get current-tab, search-items
1427   var _current-tab-search-items/eax: (addr array int) <- lookup *current-tab-search-items-ah
1428   var current-tab-search-items/esi: (addr array int) <- copy _current-tab-search-items
1429   var items/eax: (addr item-list) <- copy _items
1430   var items-data-ah/eax: (addr handle array item) <- get items, data
1431   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1432   var items-data/ebx: (addr array item) <- copy _items-data
1433   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1434   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1435   var y/edx: int <- copy 2
1436   {
1437     compare new-tab-item-index, 0
1438     break-if-<
1439     compare y, 0x28/screen-height-minus-menu
1440     break-if->=
1441     var current-item-index-addr/eax: (addr int) <- index current-tab-search-items, new-tab-item-index
1442     var current-item-index/eax: int <- copy *current-item-index-addr
1443     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1444     var item/eax: (addr item) <- index items-data, offset
1445     var item-text-ah/eax: (addr handle array byte) <- get item, text
1446     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1447     var h/eax: int <- estimate-height item-text
1448     y <- add h
1449     new-tab-item-index <- decrement
1450     loop
1451   }
1452   new-tab-item-index <- increment
1453   {
1454     # HACK: make sure we make forward progress even if a single post takes up
1455     # the whole screen.
1456     # We can't see the rest of that single post at the moment. But at least we
1457     # can go past it.
1458     compare new-tab-item-index, *current-tab-item-index-addr
1459     break-if-!=
1460     # Don't make "forward progress" past post 0.
1461     compare new-tab-item-index, 0
1462     break-if-=
1463     new-tab-item-index <- decrement
1464   }
1465   copy-to *current-tab-item-index-addr, new-tab-item-index
1466 }
1467 
1468 fn thread-page-down _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1469   var current-tab/edi: (addr tab) <- copy _current-tab
1470   var items/eax: (addr item-list) <- copy _items
1471   var items-data-ah/eax: (addr handle array item) <- get items, data
1472   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1473   var items-data/esi: (addr array item) <- copy _items-data
1474   var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1475   var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1476   var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1477   var post/eax: (addr item) <- index items-data, current-tab-root-offset
1478   var post-comments-ah/eax: (addr handle array int) <- get post, comments
1479   var _post-comments/eax: (addr array int) <- lookup *post-comments-ah
1480   var post-comments/ebx: (addr array int) <- copy _post-comments
1481   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1482   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1483   var y/edx: int <- copy 2
1484   {
1485     compare new-tab-item-index, 0
1486     break-if-<
1487     compare y, 0x28/screen-height-minus-menu
1488     break-if->=
1489     var current-item-index-addr/eax: (addr int) <- index post-comments, new-tab-item-index
1490     var current-item-index/eax: int <- copy *current-item-index-addr
1491     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1492     var item/eax: (addr item) <- index items-data, offset
1493     var item-text-ah/eax: (addr handle array byte) <- get item, text
1494     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1495     var h/eax: int <- estimate-height item-text
1496     y <- add h
1497     new-tab-item-index <- decrement
1498     loop
1499   }
1500   new-tab-item-index <- increment
1501   {
1502     # HACK: make sure we make forward progress even if a single post takes up
1503     # the whole screen.
1504     # We can't see the rest of that single post at the moment. But at least we
1505     # can go past it.
1506     compare new-tab-item-index, *current-tab-item-index-addr
1507     break-if-!=
1508     # Don't make "forward progress" past post 0.
1509     compare new-tab-item-index, 0
1510     break-if-=
1511     new-tab-item-index <- decrement
1512   }
1513   copy-to *current-tab-item-index-addr, new-tab-item-index
1514 }
1515 
1516 fn page-up _env: (addr environment), users: (addr array user), channels: (addr array channel), items: (addr item-list) {
1517   var env/edi: (addr environment) <- copy _env
1518   var tabs-ah/eax: (addr handle array tab) <- get env, tabs
1519   var _tabs/eax: (addr array tab) <- lookup *tabs-ah
1520   var tabs/ecx: (addr array tab) <- copy _tabs
1521   var current-tab-index-a/eax: (addr int) <- get env, current-tab-index
1522   var current-tab-index/eax: int <- copy *current-tab-index-a
1523   var current-tab-offset/eax: (offset tab) <- compute-offset tabs, current-tab-index
1524   var current-tab/edx: (addr tab) <- index tabs, current-tab-offset
1525   var current-tab-type/eax: (addr int) <- get current-tab, type
1526   compare *current-tab-type, 0/all-items
1527   {
1528     break-if-!=
1529     all-items-page-up current-tab, users, channels, items
1530     return
1531   }
1532   compare *current-tab-type, 1/channel
1533   {
1534     break-if-!=
1535     channel-page-up current-tab, users, channels, items
1536     return
1537   }
1538   compare *current-tab-type, 2/search
1539   {
1540     break-if-!=
1541     search-page-up current-tab, users, channels, items
1542     return
1543   }
1544   compare *current-tab-type, 3/thread
1545   {
1546     break-if-!=
1547     thread-page-up current-tab, users, channels, items
1548     return
1549   }
1550 }
1551 
1552 fn all-items-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1553   var items/esi: (addr item-list) <- copy _items
1554   var items-data-ah/eax: (addr handle array item) <- get items, data
1555   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1556   var items-data/ebx: (addr array item) <- copy _items-data
1557   var items-data-first-free-a/eax: (addr int) <- get items, data-first-free
1558   var final-item-index/esi: int <- copy *items-data-first-free-a
1559   final-item-index <- decrement
1560   var current-tab/eax: (addr tab) <- copy _current-tab
1561   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1562   var new-item-index/ecx: int <- copy *current-tab-item-index-addr
1563   var y/edx: int <- copy 2
1564   {
1565     compare new-item-index, final-item-index
1566     break-if->
1567     compare y, 0x28/screen-height-minus-menu
1568     break-if->=
1569     var offset/eax: (offset item) <- compute-offset items-data, new-item-index
1570     var item/eax: (addr item) <- index items-data, offset
1571     var item-text-ah/eax: (addr handle array byte) <- get item, text
1572     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1573     var h/eax: int <- estimate-height item-text
1574     y <- add h
1575     new-item-index <- increment
1576     loop
1577   }
1578   new-item-index <- decrement
1579   copy-to *current-tab-item-index-addr, new-item-index
1580 }
1581 
1582 fn channel-page-up _current-tab: (addr tab), users: (addr array user), _channels: (addr array channel), _items: (addr item-list) {
1583   var current-tab/edi: (addr tab) <- copy _current-tab
1584   var current-channel-index-addr/eax: (addr int) <- get current-tab, channel-index
1585   var current-channel-index/eax: int <- copy *current-channel-index-addr
1586   var channels/esi: (addr array channel) <- copy _channels
1587   var current-channel-offset/eax: (offset channel) <- compute-offset channels, current-channel-index
1588   var current-channel/esi: (addr channel) <- index channels, current-channel-offset
1589   var current-channel-posts-first-free-addr/eax: (addr int) <- get current-channel, posts-first-free
1590   var tmp/eax: int <- copy *current-channel-posts-first-free-addr
1591   var final-tab-post-index: int
1592   copy-to final-tab-post-index, tmp
1593   decrement final-tab-post-index
1594   var current-channel-posts-ah/eax: (addr handle array int) <- get current-channel, posts
1595   var _current-channel-posts/eax: (addr array int) <- lookup *current-channel-posts-ah
1596   var current-channel-posts/esi: (addr array int) <- copy _current-channel-posts
1597   var items/esi: (addr item-list) <- copy _items
1598   var items-data-ah/eax: (addr handle array item) <- get items, data
1599   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1600   var items-data/ebx: (addr array item) <- copy _items-data
1601   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1602   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1603   var y/edx: int <- copy 2
1604   {
1605     compare new-tab-item-index, final-tab-post-index
1606     break-if->
1607     compare y, 0x28/screen-height-minus-menu
1608     break-if->=
1609     var offset/eax: (offset item) <- compute-offset items-data, new-tab-item-index
1610     var item/eax: (addr item) <- index items-data, offset
1611     var item-text-ah/eax: (addr handle array byte) <- get item, text
1612     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1613     var h/eax: int <- estimate-height item-text
1614     y <- add h
1615     new-tab-item-index <- increment
1616     loop
1617   }
1618   new-tab-item-index <- decrement
1619   copy-to *current-tab-item-index-addr, new-tab-item-index
1620 }
1621 
1622 fn search-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1623   var current-tab/edi: (addr tab) <- copy _current-tab
1624   var current-tab-search-items-first-free-addr/eax: (addr int) <- get current-tab, search-items-first-free
1625   var final-tab-post-index: int
1626   var tmp/eax: int <- copy *current-tab-search-items-first-free-addr
1627   copy-to final-tab-post-index, tmp
1628   decrement final-tab-post-index
1629   var current-tab-search-items-ah/eax: (addr handle array int) <- get current-tab, search-items
1630   var _current-tab-search-items/eax: (addr array int) <- lookup *current-tab-search-items-ah
1631   var current-tab-search-items/esi: (addr array int) <- copy _current-tab-search-items
1632   var items/eax: (addr item-list) <- copy _items
1633   var items-data-ah/eax: (addr handle array item) <- get items, data
1634   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1635   var items-data/ebx: (addr array item) <- copy _items-data
1636   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1637   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1638   var y/edx: int <- copy 2
1639   {
1640     compare new-tab-item-index, final-tab-post-index
1641     break-if->
1642     compare y, 0x28/screen-height-minus-menu
1643     break-if->=
1644     var current-item-index-addr/eax: (addr int) <- index current-tab-search-items, new-tab-item-index
1645     var current-item-index/eax: int <- copy *current-item-index-addr
1646     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1647     var item/eax: (addr item) <- index items-data, offset
1648     var item-text-ah/eax: (addr handle array byte) <- get item, text
1649     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1650     var h/eax: int <- estimate-height item-text
1651     y <- add h
1652     new-tab-item-index <- increment
1653     loop
1654   }
1655   new-tab-item-index <- decrement
1656   copy-to *current-tab-item-index-addr, new-tab-item-index
1657 }
1658 
1659 fn thread-page-up _current-tab: (addr tab), users: (addr array user), channels: (addr array channel), _items: (addr item-list) {
1660   var current-tab/edi: (addr tab) <- copy _current-tab
1661   var items/eax: (addr item-list) <- copy _items
1662   var items-data-ah/eax: (addr handle array item) <- get items, data
1663   var _items-data/eax: (addr array item) <- lookup *items-data-ah
1664   var items-data/esi: (addr array item) <- copy _items-data
1665   var current-tab-root-index-addr/eax: (addr int) <- get current-tab, root-index
1666   var current-tab-root-index/eax: int <- copy *current-tab-root-index-addr
1667   var current-tab-root-offset/eax: (offset item) <- compute-offset items-data, current-tab-root-index
1668   var post/eax: (addr item) <- index items-data, current-tab-root-offset
1669   var post-comments-first-free-addr/ecx: (addr int) <- get post, comments-first-free
1670   var post-comments-ah/eax: (addr handle array int) <- get post, comments
1671   var _post-comments/eax: (addr array int) <- lookup *post-comments-ah
1672   var post-comments/ebx: (addr array int) <- copy _post-comments
1673   var final-tab-comment-index: int
1674   {
1675     var tmp/eax: int <- copy *post-comments-first-free-addr
1676     tmp <- decrement
1677     copy-to final-tab-comment-index, tmp
1678   }
1679   var current-tab-item-index-addr/edi: (addr int) <- get current-tab, item-index
1680   var new-tab-item-index/ecx: int <- copy *current-tab-item-index-addr
1681   var y/edx: int <- copy 2
1682   {
1683     compare new-tab-item-index, final-tab-comment-index
1684     break-if->
1685     compare y, 0x28/screen-height-minus-menu
1686     break-if->=
1687     var current-item-index-addr/eax: (addr int) <- index post-comments, new-tab-item-index
1688     var current-item-index/eax: int <- copy *current-item-index-addr
1689     var offset/eax: (offset item) <- compute-offset items-data, current-item-index
1690     var item/eax: (addr item) <- index items-data, offset
1691     var item-text-ah/eax: (addr handle array byte) <- get item, text
1692     var item-text/eax: (addr array byte) <- lookup *item-text-ah
1693     var h/eax: int <- estimate-height item-text
1694     y <- add h
1695     new-tab-item-index <- increment
1696     loop
1697   }
1698   new-tab-item-index <- decrement
1699   copy-to *current-tab-item-index-addr, new-tab-item-index
1700 }
1701 
1702 # keep sync'd with render-item
1703 fn estimate-height _message-text: (addr array byte) -> _/eax: int {
1704   var message-text/esi: (addr array byte) <- copy _message-text
1705   var result/eax: int <- length message-text
1706   var remainder/edx: int <- copy 0
1707   result, remainder <- integer-divide result, 0x40/post-width
1708   compare remainder, 0
1709   {
1710     break-if-=
1711     result <- increment
1712   }
1713   result <- add 2/item-padding-ver
1714   compare result, 6/avatar-space-ver
1715   {
1716     break-if->
1717     return 6/avatar-space-ver
1718   }
1719   return result
1720 }