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