Patch title: Release 94 bulk changes
Abstract:
File: /pliant/graphic/browser/naive/sample/editor.pli
Key:
    Removed line
    Added line
module "/pliant/language/unsafe.pli"
submodule "/pliant/graphic/browser/naive/server.pli"
module "/pliant/graphic/browser/naive/lib/all.pli"
module "/pliant/language/stream.pli"
module "/pliant/language/type/text/str32.pli"
module "/pliant/util/encoding/utf8.pli"


type FileEditorContext
  field Pointer:BrowserConnection connection
  field Array:Str lines
  field Array:Str ids
  field (Dictionary Str Int) ys
  field CBool modified <- false
  field Int cx cy # cursor
  field Int ax ay bx by <- undefined # selection
  field Link:Str search_pattern
  field Link:Str replace_pattern
  field List:Str copy

method e move x y
  arg_rw FileEditorContext e ; arg Int x y
  var Pointer:BrowserConnection c :> e connection
  implicit e c
    focus_set "hook" (shunt y>=0 and y<ids:size "line "+ids:y "no node") (min x (shunt y>=0 and y<lines:size lines:y:len 0)) ""
    section_overwrite "status"
      text "line "+(string y+1)+" column "+(string x+1)
    cx := x ; cy := y

method e insert_line y
  arg_rw FileEditorContext e ; arg Int y
  var Pointer:BrowserConnection c :> e connection
  implicit c e
    e:lines size += 1
    for (var Int i) lines:size-2 y step -1
      lines i+1 := lines i
    lines y := ""
    var Str id := "line "+generate_id
    e:ids size += 1
    for (var Int i) ids:size-2 y step -1
      ids i+1 := ids i
    ids y := id    
    each p ys
      if p>=y
        p += 1
    ys insert id y
    node_tag "line "+id "text"
    node_attribute "line "+id "" "[lf]"
    if y=0
      node_stick "line "+id before "line "+ids:0
    else
      node_stick "line "+id after "line "+(ids y-1)
    
method e delete_line y
  arg_rw FileEditorContext e ; arg Int y
  var Pointer:BrowserConnection c :> e connection
  implicit c e
    var Str id := ids y
    for (var Int i) y lines:size-2
      lines i := lines i+1
    e:lines size -= 1
    for (var Int i) y ids:size-2
      ids i := ids i+1
    e:ids size -= 1
    ys remove (ys first id)
    each p ys
      if p>=y
        p -= 1
    node_drop "line "+id
    
method e set_line y l
  arg_rw FileEditorContext e ; arg Int y ; arg Str l
  var Pointer:BrowserConnection c :> e connection
  implicit c e
    lines y := l
    node_attribute "line "+ids:y "" lines:y+"[lf]"
    node_refresh "line "+ids:y 1e6 1e6 "localdraw"
    e modified := true

method e set_line y l
  arg_rw FileEditorContext e ; arg Int y ; arg Str32 l
  e set_line y utf8_encode:l

method e cut_paste action
  arg_rw FileEditorContext e ; arg Str action
  var Pointer:BrowserConnection c :> e connection
  implicit c e
    if ay>=0 and by>=ay and by<lines:size and ax>=0 and (by>ay or bx>=ax)
      if by>ay and bx=0
        bx -= 1
      if action="cut" or action="copy"
        copy := var List:Str empty_list
        if by=ay
          copy += lines:ay ax bx+1-ax
        else
          copy += lines:ay ax lines:ay:len
          for (var Int i) ay+1 by-1
            copy += lines i
          copy += lines:by 0 bx+1
      if action="cut"
        set_line ay (lines:ay 0 ax)+(lines:by bx+1 lines:by:len)
        for (var Int i) by ay+1 step -1
          delete_line i
        move ax ay
        window_refresh main
        ax := undefined ; ay := undefined ; bx := undefined ; by := undefined
      if action="paste"
        if not (exists copy:first)
          void
        eif not (exists (copy next copy:first))
          set_line cy (lines:cy 0 cx)+copy:first+(lines:cy cx lines:cy:len)
        else
          insert_line cy+1
          set_line cy+1 copy:last+(lines:cy cx lines:cy:len)
          set_line cy (lines:cy 0 cx)+copy:first
          var Int y := cy
          var Pointer:Str pl :> copy next copy:first
          while addressof:pl<>(addressof copy:last)
            y += 1
            insert_line y
            set_line y pl
            pl :> copy next pl
        window_refresh main

method connection text_editor filename
  arg_rw BrowserConnection connection ; arg Str filename
  ovar FileEditorContext e
  e connection :> connection
  implicit connection e
    search_pattern :> new Str
    replace_pattern :> new Str
    (var Stream s) open filename in+safe ; var Int i := 0
    while not s:atend
      var Str id := generate_id ; ids += id ; ys insert id i ; i += 1
      lines += s readline
    if i=0
      var Str id := generate_id ; ids += id ; ys insert id i
      lines += ""
    s close
    window top
      center
        bold
          text filename
        eol
      section "parameters"
        void
    window left
      section "menu" dynamic
        button "file" key "alt f"
          section_overwrite "parameters"
            button "save" key "alt s"
              (var Stream s2) open filename out+safe
              for (var Int i) 0 lines:size-1
                s2 writeline lines:i
              s2 close
              e modified := false
              section_replay "menu"
            button "cancel" key "escape"
              section_replay "menu"
        eol
        button "edit" key "alt e"
          section_overwrite "parameters"
            button "copy" key "alt c"
              cut_paste "copy"
            button "cut" key "alt t"
              cut_paste "cut"
            button "paste" key "alt p"
              cut_paste "paste"
            button "cancel" key "escape"
              section_replay "menu"
            eol
        eol
        button "search" key "alt s"
          focus_save
          section_overwrite "parameters"
            input "Search pattern: " search_pattern focus true
            button "search" key "alt s"
              if search_pattern<>""
                var Int x := cx+1 ; var Int y := cy
                part search
                  if y>=lines:size
                    leave search
                  x += (lines:y x lines:y:len) search search_pattern undefined
                  if x<0
                    y += 1 ; x := 0
                    restart search
                  move x y
            button "cancel" key "escape"
              section_replay "menu"
            eol
            input "Replace with: " replace_pattern
            button "replace" key "alt r"
              if cy<lines:size
                if (lines:cy cx search_pattern:len)=search_pattern
                  set_line cy (lines:cy 0 cx)+replace_pattern+(lines:cy cx+search_pattern:len lines:cy:len)
              if search_pattern<>""
                var Int x := cx+1 ; var Int y := cy
                part search
                  if y>=lines:size
                    leave search
                  x += (lines:y x lines:y:len) search search_pattern undefined
                  if x<0
                    y += 1 ; x := 0
                    restart search
                  move x y
        eol
        button "goto" key "alt g"
          section_overwrite "parameters"
            ovar Int num := undefined
            input "Line number: " num focus true
            button "go to line" key "alt l"
              if num>=0 and num<lines:size
                move 0 num-1
              section_replay "menu"
            button "cancel" key "escape"
              section_replay "menu"
        eol
        button "exit" key "alt x"
          if modified
            section_overwrite "parameters"
              text "File as been modified, but not saved." ; eol
              button "force to leave" key "alt f"
                url_return
              button "cancel" key "escape"
                section_replay "menu"
          else
            url_return
        section_overwrite "parameters"
          void
    window main
      node "hook"
        hook
          para spacing 0 fold false
            fixed
              bold
                for (var Int i) 0 ids:size-1
                  node "line "+ids:i
                    text (shunt i<lines:size lines:i "")+"[lf]"
        event
          # console "editor event " event " key " key " at " target_id eol
          if event="press" and key="button1" and (target_id parse word:"line" any:(var Str id))
            var Int x := target_index
            var Pointer:Int py :> ys first id
            var Int y := shunt exists:py py (cast undefined Int)
            move x y
            ax := cx ; ay := cy
          if event="release" and key="button1" and (target_id parse word:"line" any:(var Str id))
            var Int x := target_index
            var Pointer:Int py :> ys first id
            var Int y := shunt exists:py py (cast undefined Int)
            bx := x ; by := y
          eif event="press" and key="shift"
            ax := cx ; ay := cy
          eif event="release" and key="shift"
            bx := cx ; by := cy
          if cy>=0 and cy<lines:size and cx>=0
            var Str32 l := utf8_decode lines:cy
            if event="character"
              set_line cy (l 0 cx)+utf8_decode:key+(l cx l:len)
              move cx+1 cy
            eif event="press"
              if key="backspace"
                if cx>0
                  move cx-1 cy
                  set_line cy (l 0 cx)+(l cx+1 l:len)
                eif cy>0
                  var Int i := utf8_decode:(lines cy-1):len
                  set_line cy-1 (lines cy-1)+lines:cy
                  delete_line cy
                  window_refresh main
                  display
                  move i cy-1
              eif key="enter"
                insert_line cy+1
                set_line cy+1 (l cx l:len)
                set_line cy (l 0 cx)
                if false # while { event_discard2 ; event="press" and key="enter" }
                  cy += 1
                  insert_line cy
                window_refresh main
                display
                if (lines cy+1):len=0
                  cx := 0
                  while (lines:cy cx 1)=" "
                    cx += 1
                  set_line cy+1 (repeat cx " ")
                  move cx cy+1
                else
                  move 0 cy+1
              eif (key="left" or key="shift left") and cx>0
                move cx-1 cy
              eif (key="right" or key="shift right") and cx+1<=l:len
                move cx+1 cy
              eif key="home" or key="shift home"
                move 0 cy
              eif key="end" or key="shift end"
                move l:len cy
              eif (key="up" or key="shift up") and cy>0
                move cx cy-1
              eif (key="down" or key="shift down") and cy+1<lines:size
                move cx cy+1
              eif key="delete"
                cut_paste "cut"
              eif key="insert"
                cut_paste "paste"
          if target_x1-target_x0>hook_x1-hook_x0 
            window_refresh main
    window bottom
      section "status"
        void
    move 0 0

export '. text_editor'