Patch title: Release 96 bulk changes
Abstract:
File: /pliant/graphic/ui/sample/editor.pli
Key:
    Removed line
    Added line
module "/pliant/language/compiler.pli"
module "/pliant/language/stream.pli"
module "/pliant/language/data/id.pli"
module "/pliant/graphic/ui/server/context.pli"
module "/pliant/graphic/ui/server/api.pli"
module "/pliant/language/type/text/str32.pli"
module "/pliant/util/encoding/utf8.pli"
module "/pliant/util/pml/io.pli"

constant new_clipboard true


ui_type FileEditorContext
  field Pointer:UIServerContext context
  field Array:Str lines
  field Array:Str ids
  field (Dictionary Str Int) ys
  field CBool modified <- false
  field Int cx cy <- 0 # cursor
  field Int ax ay bx by <- undefined # selection
  field Link:Str search_pattern
  field Link:Str replace_pattern
  if not new_clipboard
    field List:Str copy


method e move x y
  arg_rw FileEditorContext e ; arg Int x y
  var Pointer:UIServerContext c :> e context
  implicit e c
    if y>=0 and y<ids:size
      focus_set "line "+ids:y (min x (shunt y>=0 and y<lines:size lines:y:len 0))
    section_overwrite "status"
      para stick cursor edit
        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:UIServerContext c :> e context
  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
    section_after (shunt y>0 "line "+(ids y-1) "line top")
      section "line "+id
        text ""
  # console "insert line " y eol
    
method e delete_line y
  arg_rw FileEditorContext e ; arg Int y
  var Pointer:UIServerContext c :> e context
  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
    section_delete "line "+id
  # console "delete line " y eol
    
method e get_line y -> l
  arg_rw FileEditorContext e ; arg Int y ; arg Str32 l
  l := utf8_decode e:lines:y

method e set_line y l
  arg_rw FileEditorContext e ; arg Int y ; arg Str l
  var Pointer:UIServerContext c :> e context
  implicit c e
    lines y := l
    section_overwrite "line "+ids:y
      para stick cursor edit
        text (shunt l:len>0 l " ")
    e modified := true
  # console "set line " y " " l eol

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 Link:FileEditorContext e ; arg Str action
  var Pointer:UIServerContext c :> e context
  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"
        if new_clipboard
          clipboard_write
            var Str cb
            if by=ay
              cb := utf8_encode (get_line:ay ax bx+1-ax)
            else
              cb := utf8_encode:(get_line:ay ax lines:ay:len)+"[lf]"
              for (var Int i) ay+1 by-1
                cb += lines:i+"[lf]"
              cb += utf8_encode (get_line:by 0 bx+1)
            clipboard oraw open (cast "text" Ident) body cb close
        else
          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 (get_line:ay 0 ax)+(get_line:by bx+1 lines:by:len)
        for (var Int i) by ay+1 step -1
          delete_line i
        move ax ay
        # FIXME window_refresh main
        ax := undefined ; ay := undefined ; bx := undefined ; by := undefined
      if action="paste"
        if new_clipboard
          clipboard_read
            while (clipboard ipick open)
              if (clipboard iraw open (cast "text" Ident) body (var Str cb) close)
                var Int start := 0 ; var Int x := cx ; var Int y := cy
                while start<cb:len
                  var Int stop := ((cb start cb:len) search "[lf]" cb:len-start)+start
                  var Str32 v := utf8_decode (cb start stop-start)
                  var Str32 l := get_line y
                  set_line y (l 0 x)+v+(l x l:len)
                  x += v len
                  start := stop
                  if start<cb:len and cb:start="[lf]"
                    var Str32 l := get_line y
                    insert_line y+1
                    set_line y+1 (l x l:len)
                    set_line y (l 0 x)
                    y += 1 ; x := 0
                    start += 1
              else
                clipboard iskip
        else
          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
          # FIXME window_refresh main

ui_function text_editor filename
  arg Str filename
  ovar FileEditorContext e
  e context :> context
  implicit e
    style_copy "" "text"
    style_set "text" "standard/text/font" "Bitstream Vera Sans Mono" # "Free Monospaced Bold"
    style_set "text" "standard/para/wrap" false
    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
      if true # FIXME center
        if true # FIXME 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<>"" and cx>=0 and cy>=0
                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 search_pattern<>""
                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
      hook
        style use "text"
          section "line top"
            void
          for (var Int i) 0 ids:size-1
            section "line "+ids:i
              para stick cursor edit
                text (shunt i<lines:size and lines:i:len>0 lines:i " ")
      event
        # console "editor event " event " key " key " at " focus_section eol
        if event="press" and key="button1" and (pointer_section parse word:"line" any:(var Str id))
          var Int x := pointer_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 (pointer_section parse word:"line" any:(var Str id))
          var Int x := pointer_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 := get_line cy
          if event="character"
            if true
              lines cy := utf8_encode (l 0 cx)+utf8_decode:key+(l cx l:len)
              modified := true
              cx += 1
              section_overwrite "status"
                para stick cursor edit
                  text "line "+(string cy+1)+" column "+(string cx+1)
            else
              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
                set_line cy (l 0 cx-1)+(l cx l:len)
                move cx-1 cy
              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 false # FIXME target_x1-target_x0>hook_x1-hook_x0 
          window_refresh main
    window bottom
      section "status"
        void