PreviousUpNext

15.4.1432  src/lib/x-kit/widget/edit/textmill.pkg

## textmill.pkg                                                 # emacs calls these 'buffers' but 'mill' is shorter and more parellel to 'pane'.
#                                                               # Also, I want to eventually generalize this to various other foopane <-> foomill pairs, and generalize millboss to not know or care about the differences between them.
# A textmill contains (typically) the contents
# of one file open for editing.  It is not a
# GUI widget;  display of the textmill contents
# is handled by
#
#     src/lib/x-kit/widget/edit/textpane.pkg
#
# In "Model/View/Controller" terms, textmill.pkg
# is the Model and textpane.pkg is the View+Controller.
#
# See also:
#     src/lib/x-kit/widget/edit/textpane.pkg
#     src/lib/x-kit/widget/edit/millboss-imp.pkg

# Compiled by:
#     src/lib/x-kit/widget/xkit-widget.sublib


stipulate
    include package   threadkit;                                # threadkit                             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
#   package ap  =  client_to_atom;                              # client_to_atom                        is from   src/lib/x-kit/xclient/src/iccc/client-to-atom.pkg
#   package au  =  authentication;                              # authentication                        is from   src/lib/x-kit/xclient/src/stuff/authentication.pkg
#   package cpm =  cs_pixmap;                                   # cs_pixmap                             is from   src/lib/x-kit/xclient/src/window/cs-pixmap.pkg
#   package cpt =  cs_pixmat;                                   # cs_pixmat                             is from   src/lib/x-kit/xclient/src/window/cs-pixmat.pkg
#   package dy  =  display;                                     # display                               is from   src/lib/x-kit/xclient/src/wire/display.pkg
#   package fil =  file__premicrothread;                        # file__premicrothread                  is from   src/lib/std/src/posix/file--premicrothread.pkg
#   package fti =  font_index;                                  # font_index                            is from   src/lib/x-kit/xclient/src/window/font-index.pkg
#   package r2k =  xevent_router_to_keymap;                     # xevent_router_to_keymap               is from   src/lib/x-kit/xclient/src/window/xevent-router-to-keymap.pkg
#   package mtx =  rw_matrix;                                   # rw_matrix                             is from   src/lib/std/src/rw-matrix.pkg
#   package rop =  ro_pixmap;                                   # ro_pixmap                             is from   src/lib/x-kit/xclient/src/window/ro-pixmap.pkg
#   package rw  =  root_window;                                 # root_window                           is from   src/lib/x-kit/widget/lib/root-window.pkg
#   package rwv =  rw_vector;                                   # rw_vector                             is from   src/lib/std/src/rw-vector.pkg
#   package sep =  client_to_selection;                         # client_to_selection                   is from   src/lib/x-kit/xclient/src/window/client-to-selection.pkg
#   package shp =  shade;                                       # shade                                 is from   src/lib/x-kit/widget/lib/shade.pkg
#   package sj  =  socket_junk;                                 # socket_junk                           is from   src/lib/internet/socket-junk.pkg
#   package x2s =  xclient_to_sequencer;                        # xclient_to_sequencer                  is from   src/lib/x-kit/xclient/src/wire/xclient-to-sequencer.pkg
#   package tr  =  logger;                                      # logger                                is from   src/lib/src/lib/thread-kit/src/lib/logger.pkg
#   package tsr =  thread_scheduler_is_running;                 # thread_scheduler_is_running           is from   src/lib/src/lib/thread-kit/src/core-thread-kit/thread-scheduler-is-running.pkg
#   package u1  =  one_byte_unt;                                # one_byte_unt                          is from   src/lib/std/one-byte-unt.pkg
#   package v1u =  vector_of_one_byte_unts;                     # vector_of_one_byte_unts               is from   src/lib/std/src/vector-of-one-byte-unts.pkg
#   package v2w =  value_to_wire;                               # value_to_wire                         is from   src/lib/x-kit/xclient/src/wire/value-to-wire.pkg
#   package wg  =  widget;                                      # widget                                is from   src/lib/x-kit/widget/old/basic/widget.pkg
#   package wi  =  window;                                      # window                                is from   src/lib/x-kit/xclient/src/window/window.pkg
#   package wme =  window_map_event_sink;                       # window_map_event_sink                 is from   src/lib/x-kit/xclient/src/window/window-map-event-sink.pkg
#   package wpp =  client_to_window_watcher;                    # client_to_window_watcher              is from   src/lib/x-kit/xclient/src/window/client-to-window-watcher.pkg
#   package wy  =  widget_style;                                # widget_style                          is from   src/lib/x-kit/widget/lib/widget-style.pkg
#   package xc  =  xclient;                                     # xclient                               is from   src/lib/x-kit/xclient/xclient.pkg
#   package xj  =  xsession_junk;                               # xsession_junk                         is from   src/lib/x-kit/xclient/src/window/xsession-junk.pkg
#   package xtr =  xlogger;                                     # xlogger                               is from   src/lib/x-kit/xclient/src/stuff/xlogger.pkg
    #

    #
    package evt =  gui_event_types;                             # gui_event_types                       is from   src/lib/x-kit/widget/gui/gui-event-types.pkg
    package gts =  gui_event_to_string;                         # gui_event_to_string                   is from   src/lib/x-kit/widget/gui/gui-event-to-string.pkg
    package gt  =  guiboss_types;                               # guiboss_types                         is from   src/lib/x-kit/widget/gui/guiboss-types.pkg

    package a2r =  windowsystem_to_xevent_router;               # windowsystem_to_xevent_router         is from   src/lib/x-kit/xclient/src/window/windowsystem-to-xevent-router.pkg

    package gd  =  gui_displaylist;                             # gui_displaylist                       is from   src/lib/x-kit/widget/theme/gui-displaylist.pkg

    package pp  =  standard_prettyprinter;                      # standard_prettyprinter                is from   src/lib/prettyprint/big/src/standard-prettyprinter.pkg

    package err =  compiler::error_message;                     # compiler                              is from   src/lib/core/compiler/compiler.pkg
                                                                # error_message                         is from   src/lib/compiler/front/basics/errormsg/error-message.pkg

    package bt  =  gui_to_sprite_theme;                         # gui_to_sprite_theme                   is from   src/lib/x-kit/widget/theme/sprite/gui-to-sprite-theme.pkg
    package ct  =  gui_to_object_theme;                         # gui_to_object_theme                   is from   src/lib/x-kit/widget/theme/object/gui-to-object-theme.pkg
    package wt  =  widget_theme;                                # widget_theme                          is from   src/lib/x-kit/widget/theme/widget/widget-theme.pkg


    package boi =  spritespace_imp;                             # spritespace_imp                       is from   src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg
    package cai =  objectspace_imp;                             # objectspace_imp                       is from   src/lib/x-kit/widget/space/object/objectspace-imp.pkg
    package pai =  widgetspace_imp;                             # widgetspace_imp                       is from   src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg

    #    
    package gtg =  guiboss_to_guishim;                          # guiboss_to_guishim                    is from   src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg

    package b2s =  spritespace_to_sprite;                       # spritespace_to_sprite                 is from   src/lib/x-kit/widget/space/sprite/spritespace-to-sprite.pkg
    package c2o =  objectspace_to_object;                       # objectspace_to_object                 is from   src/lib/x-kit/widget/space/object/objectspace-to-object.pkg

    package s2b =  sprite_to_spritespace;                       # sprite_to_spritespace                 is from   src/lib/x-kit/widget/space/sprite/sprite-to-spritespace.pkg
    package o2c =  object_to_objectspace;                       # object_to_objectspace                 is from   src/lib/x-kit/widget/space/object/object-to-objectspace.pkg

    package g2p =  gadget_to_pixmap;                            # gadget_to_pixmap                      is from   src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg
    package m2d =  mode_to_drawpane;                            # mode_to_drawpane                      is from   src/lib/x-kit/widget/edit/mode-to-drawpane.pkg

    package im  =  int_red_black_map;                           # int_red_black_map                     is from   src/lib/src/int-red-black-map.pkg
#   package is  =  int_red_black_set;                           # int_red_black_set                     is from   src/lib/src/int-red-black-set.pkg
    package sm  =  string_map;                                  # string_map                            is from   src/lib/src/string-map.pkg

    package r8  =  rgb8;                                        # rgb8                                  is from   src/lib/x-kit/xclient/src/color/rgb8.pkg
    package r64 =  rgb;                                         # rgb                                   is from   src/lib/x-kit/xclient/src/color/rgb.pkg
    package g2d =  geometry2d;                                  # geometry2d                            is from   src/lib/std/2d/geometry2d.pkg
    package g2j =  geometry2d_junk;                             # geometry2d_junk                       is from   src/lib/std/2d/geometry2d-junk.pkg

    package e2g =  millboss_to_guiboss;                         # millboss_to_guiboss                   is from   src/lib/x-kit/widget/edit/millboss-to-guiboss.pkg

    package sj  =  string_junk;                                 # string_junk                           is from   src/lib/std/src/string-junk.pkg
    package bq  =  bounded_queue;                               # bounded_queue                         is from   src/lib/src/bounded-queue.pkg
    package nl  =  red_black_numbered_list;                     # red_black_numbered_list               is from   src/lib/src/red-black-numbered-list.pkg
    package tmc =  textmill_crypts;                             # textmill_crypts                       is from   src/lib/x-kit/widget/edit/textmill-crypts.pkg
    package mt  =  millboss_types;                              # millboss_types                        is from   src/lib/x-kit/widget/edit/millboss-types.pkg

    package bo  =  bool_millout;                                # bool_millout                          is from   src/lib/x-kit/widget/edit/bool-millout.pkg
    package bso =  bools_millout;                               # bools_millout                         is from   src/lib/x-kit/widget/edit/bools-millout.pkg
    package fo  =  float_millout;                               # float_millout                         is from   src/lib/x-kit/widget/edit/float-millout.pkg
    package fso =  floats_millout;                              # floats_millout                        is from   src/lib/x-kit/widget/edit/floats-millout.pkg
    package io  =  int_millout;                                 # int_millout                           is from   src/lib/x-kit/widget/edit/int-millout.pkg
    package iso =  ints_millout;                                # ints_millout                          is from   src/lib/x-kit/widget/edit/ints-millout.pkg
    package so  =  string_millout;                              # string_millout                        is from   src/lib/x-kit/widget/edit/string-millout.pkg
    package sso =  strings_millout;                             # strings_millout                       is from   src/lib/x-kit/widget/edit/strings-millout.pkg
    package xso =  boolfloatintstrings_millout;                 # boolfloatintstrings_millout           is from   src/lib/x-kit/widget/edit/boolfloatintstrings-millout.pkg
    package tso =  textmill_statechange_millout;                # textmill_statechange_millout          is from   src/lib/x-kit/widget/edit/textmill-statechange-millout.pkg
    package mmo =  millgraph_millout;                           # millgraph_millout                     is from   src/lib/x-kit/widget/edit/millgraph-millout.pkg
    package mgm =  millgraph_mill;                              # millgraph_mill                        is from   src/lib/x-kit/widget/edit/millgraph-mill.pkg

    tracefile   =  "widget-unit-test.trace.log";

    nb = log::note_on_stderr;                                   # log                           is from   src/lib/std/src/log.pkg

dummy0 = tmc::decrypt__textpane_to_textmill;            # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy2 = so::String_Millout;                            # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy3 = io::Int_Millout;                               # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy4 = fo::Float_Millout;                             # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy5 = bo::Bool_Millout;                              # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy6 = fso::Floats_Millout;                           # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy7 = bso::Bools_Millout;                            # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy8 = iso::Ints_Millout;                             # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy9 = sso::Strings_Millout;                          # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummya = xso::Boolfloatintstrings_Millout;              # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummyb = mmo::Millgraph_Millout;                        # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
dummyc = mgm::millgraph_mill: mt::Textmill_Extension;   # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
herein

    package textmill
    :       Textmill                                                                                                                            # Textmill              is from   src/lib/x-kit/widget/edit/textmill.api
    {
        max_history_length = 4000;                                                                                                              # XXX SUCKO FIXME The 4000 max-history-length should be configurable.
        #
        fun dummy_make_pane_guiplan                                                                                                             # Synthesize guiplan for a pane to display our state.
              {
                textpane_to_textmill:           mt::Textpane_To_Textmill,                                                                       # 
                filepath:                       Null_Or( String ),                                                                              # make_pane_guiplan will (should!) often select the pane mode to use based on the filename.
                textpane_hint:                  Crypt                                                                                           # Current pane mode (e.g. fundamental_mode) etc, wrapped up so textmill can't see the relevant types, in the interest of modularity.
              }
            :                                   gt::Gp_Widget_Type
            =
            {   msg = "dummy_make_pane_guiplan() called?!  --textmill.pkg";
                log::fatal msg;                                                                                                                 # Should never return.
                raise exception DIE msg;                                                                                                        # To keep compiler happy.
            };
        make_pane_guiplan__hack                                                                                                                 # Nassssty hack to break a package dependency cycle.
            =                                                                                                                                   # This is used by App_To_Mill.make_pane_guiplan() below.
            REF dummy_make_pane_guiplan;                                                                                                        # This value will be overwritten by   src/lib/x-kit/widget/edit/make-textpane.pkg


        Textmill_State                  = mt::Textmill_State;                                                                                   # 
        Imports                         = mt::Textmill_Imports;                                                                                 # Ports we use, provided by other imps.
        Textmill_Statechange__Watchers  = mt::Textmill_Statechange__Watchers;                                                                   # Type for tracking the set of clients subscribed to a mill for mt::Textmill_Statechange updates.
        Textmill_Statechange__Watchee   = mt::Textmill_Statechange__Watchee;                                                                    # Type for tracking the client we are subscribed to for mt::Textmill_Statechange updates.
        Runstate                        = mt::Textmill_Runstate;                                                                                # These values will be statically globally visible throughout the code body for the imp.
        Textmill_Q                      = mt::Textmill_Q;

        Me_Slot                                                                                                                                 #
            =                                                                                                                                   #
            Mailslot (                                                                                                                          #
              { imports:                                Imports,                                                                                #
                me:                                     Textmill_State,                                                                         #
                textmill_arg:                           mt::Textmill_Arg,                                                                       #
                run_gun':                               Run_Gun,                                                                                #
                end_gun':                               End_Gun                                                                                 #
              }                                                                                                                                 #
            );                                                                                                                                  #

        Exports = {                                                                                                                             # Ports we provide for use by other imps.
                    textpane_to_textmill:               mt::Textpane_To_Textmill,                                                               # mt::Textpane_To_Textmill includes mt::App_To_Mill.
                    millboss_to_mill:                   mt::Millboss_To_Mill
                  };


        Textmill_Egg =  Void -> (Exports,   (Imports, Run_Gun, End_Gun) -> Void);



        fun run ( textmill_q:                           Textmill_Q,                                                                             # 
                  #
                  runstate as
                  {                                                                                                                             # These values will be statically globally visible throughout the code body for the imp.
                    id:                                 Id,
                    me:                                 Textmill_State,                                                                         # 
                    textmill_arg:                       mt::Textmill_Arg,
                    textpane_to_textmill:               mt::Textpane_To_Textmill,
                    mill_to_millboss:                   mt::Mill_To_Millboss,
                    millins:                            mt::ipm::Map(mt::Millin),                                                               # Millins for this mill indexed by Inport.
                    millouts:                           mt::opm::Map(mt::Millout),                                                              # Output streams available to watch.  Indexed by Outport.
                    make_pane_guiplan':                 mt::Make_Pane_Guiplan_Fn,
                    finalize_textmill_extension:        Void -> Void,                                                                           # Function to be called at textmill shutdown, so textmill extension can do any required shutdown of its own.
                    imports:                            Imports,                                                                                # Imps to which we send requests.
                    to:                                 Replyqueue,                                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                    #                                                                                                                           #
                    textmill_statechange__outport:      mt::Outport,                                                                            #
                    textmill_statechange__millout:      mt::Millout,                                                                            #
                    textmill_statechange__watchers:     Ref( Textmill_Statechange__Watchers ),                                                  # 
                    #                                                                                                                           #
                    textmill_statechange__inport:       mt::Inport,                                                                             #
                    textmill_statechange__millin:       mt::Millin,
                    textmill_statechange__watchee:      Ref( Null_Or( Textmill_Statechange__Watchee ) ),                                        # 
                    #                                                                                                                           #
                    end_gun':                           End_Gun                                                                                 # 
                  }
                )
            =
            {   loop ();
            }
            where
                #
                fun loop ()                                                                                                                     # Outer loop for the imp.
                    =
                    {   do_one_mailop' to [
                            #
                            end_gun'                        ==>  shut_down_textmill',
                            take_from_mailqueue' textmill_q ==>  do_textmill_plea
                        ];

                        loop ();
                    }   
                    where
                        fun do_textmill_plea  thunk
                            =
                            thunk runstate;
                        #
                        fun shut_down_textmill' ()
                            =
                            {   finalize_textmill_extension ();                                                                                 # Let any textmill extension present do any needed shutdown work.
                                #
                                thread_exit { success => TRUE };                                                                                # Will not return.      
                            };
                    end;
            end;        



        #
        fun startup                                                                                                                             # Root fn of imp microthread.  Note currying.
              (                                                                                                                                 #
                id:                     Id,                                                                                                     #
                textmill_extension:     Null_Or( mt::Textmill_Extension ),                                                                      #
                reply_oneshot:          Oneshot_Maildrop( (Me_Slot, Exports) )                                                                  #
              )                                                                                                                                 #
              ()                                                                                                                                # Curried to allow delayed activation of egg.
            =
            {   me_slot  =  make_mailslot  ()   :  Me_Slot;
                #
                textmill_statechange__outport
                  =
                  { mill_id      => id,
                    outport_name => "textmill_statechange"
                  };

                textmill_statechange__inport
                  =
                  { mill_id      => id,
                    inport_name  => "textmill_statechange"
                  };

                textmill_statechange__millin
                    =
                    make__textmill_statechange__millin  textmill_statechange__inport;

                textmill_statechange__millout
                    =
                    make__textmill_statechange__millout  textmill_statechange__outport;

                millins    =  make_millins  (textmill_statechange__inport,  textmill_statechange__millin );
                millouts   =  make_millouts (textmill_statechange__outport, textmill_statechange__millout);
                #
                make_pane_guiplan' = *make_pane_guiplan__hack;                                                                                  # This should be the only read of make_pane_guiplan__hack. Everywhere else we should use runstate.make_pane_guiplan' because that support textmill extension overrides of the value.

                mill_extension_state = *mill_extension_state__global;

                my { millins, millouts, make_pane_guiplan', mill_extension_state, finalize_textmill_extension }                                 # If we have a textmill extension, let it initialize its state and register its millins and millouts, also finalization code and preferred pane.
                    =
                    case textmill_extension
                        #
                        NULL  => { millins, millouts, make_pane_guiplan', mill_extension_state, finalize_textmill_extension => \\ () = () };

                        THE x => x.initialize_textmill_extension { mill_id => id, textmill_q, millins, millouts, make_pane_guiplan' };
                    esac;

                mill_extension_state__global := mill_extension_state;

                app_to_mill                                                                                                                     # Generic interface supported by all mills.
                    =
                    mt::APP_TO_MILL
                      {
                        id,
                        millins,
                        millouts,

                        get_dirty,
                        pass_dirty,

                        get_filepath,
                        set_filepath,
                        pass_filepath,

                        get_name,
                        set_name,
                        pass_name,

                        reload_from_file,
                        save_to_file,

                        get_pane_guiplan,
                        pass_pane_guiplan
                      };

                textpane_to_textmill                                                                                                            # textmill-specific interface.
                    =
                    mt::TEXTPANE_TO_TEXTMILL
                      {
                        id,
                        #
                        get_maxline,
                        pass_maxline,

                        get_line,
                        pass_line,

                        set_lines,
                        get_lines,
                        pass_lines,

                        get_textstate,
                        pass_textstate,

                        get_edit_result,
                        pass_edit_result,

                        get_drawpane_startup_result,
                        get_drawpane_shutdown_result,
                        get_drawpane_initialize_gadget_result,
                        get_drawpane_redraw_request_result,
                        get_drawpane_mouse_click_result,
                        get_drawpane_mouse_drag_result,
                        get_drawpane_mouse_transit_result,

                        undo,

                        set_readonly,
                        get_readonly,
                        pass_readonly,

                        set_textpane_hint,
                        get_textpane_hint,

                        note__textmill_statechange__watcher,
                        drop__textmill_statechange__watcher,

                        textmill_extension,

                        app_to_mill                                                                                                             # Include the generic mill interface in our textmill-specific interface.
                      };

                millboss_to_mill                                                                                                                # 
                  =
                  { id => issue_unique_id (),
                    wakeup
                  };
 
                exports     = { textpane_to_textmill,
                                millboss_to_mill
                              };
                                
                to          =  make_replyqueue();
                #
                put_in_oneshot (reply_oneshot, (me_slot, exports));                                                                             # Return value from textmill_egg'().

                (take_from_mailslot  me_slot)                                                                                                   # Imports from textmill_egg'().
                    ->
                    { me, textmill_arg, imports, run_gun', end_gun' };

                block_until_mailop_fires  run_gun';                                                                                             # Wait for the starting gun.

                textmill_statechange__watchers =  REF mt::ipm::empty;
                textmill_statechange__watchee  =  REF (NULL: Null_Or( Textmill_Statechange__Watchee ));

                runstate =    { id,
                                me,
                                textmill_arg,
                                textpane_to_textmill,
                                mill_to_millboss,
                                millins,
                                millouts, 
                                make_pane_guiplan',
                                finalize_textmill_extension,
                                imports,
                                to,
                                #
                                textmill_statechange__outport,
                                textmill_statechange__millout,
                                textmill_statechange__watchers,
                                #
                                textmill_statechange__inport,
                                textmill_statechange__millin,
                                textmill_statechange__watchee,
                                #
                                end_gun'
                              };

                tell__textmill_statechange__watchers_full_state  runstate;                                                                      # Make sure textmill_statechange__watchers start out with full update.

                run (textmill_q, runstate);                                                                                                     # Will not return.
            }
            where
                textmill_q     =  make_mailqueue (get_current_microthread()):  Textmill_Q;
                #
                (mt::get__mill_to_millboss  "textmill::startup")
                    ->
                    (mill_to_millboss  as  mt::MILL_TO_MILLBOSS m2m);

                mill_extension_state__global
                    =
                    REF { id   => issue_unique_id (),
                          type => "Void",
                          info =>     "dummy value for mill_extension_state -- textmill.pkg",
                          data => DIE "dummy value for mill_extension_state -- textmill.pkg"
                        };

                fun tell__textmill_statechange__watchers
                      (
                        textmill_statechange__watchers: Textmill_Statechange__Watchers,                                                         # 
                        statechange:                    mt::Textmill_Statechange,
                        r:                              Runstate
                      )
                    =
                    mt::ipm::apply   tell_watcher   textmill_statechange__watchers
                    where
                        fun tell_watcher
                              (
                                inport:          mt::Inport,                                                                                    # Unique id identifying this watcher.
                                watcher:        (mt::Outport, mt::Textmill_Statechange) -> Void                                                 # 
                              )
                            =
                            {   outport  =  r.textmill_statechange__outport;
                                #
                                watcher     (outport, statechange);

                                counter  =  r.textmill_statechange__millout.counter;                                                            # Count messages sent through port,
                                counter := *counter + 1;                                                                                        # for debug/display purposes.
                            };
                    end;

                fun tell__textmill_statechange__watcher_full_state
                      (
                        watchfn:                (mt::Outport, mt::Textmill_Statechange) -> Void,
                        r:                      Runstate
                      ) 
                    =
                    {   outport = r.textmill_statechange__outport;
                        #
                        watchfn  (outport, mt::TEXTSTATE_CHANGED  { was => *r.me.state,         now => *r.me.state      });
                        watchfn  (outport, mt::FILEPATH_CHANGED   { was => *r.me.filepath,      now => *r.me.filepath   });
                        watchfn  (outport, mt::NAME_CHANGED       { was => *r.me.name,          now => *r.me.name       });
                        watchfn  (outport, mt::READONLY_CHANGED   { was => *r.me.readonly,      now => *r.me.readonly   });
                        watchfn  (outport, mt::DIRTY_CHANGED      { was => *r.me.dirty,         now => *r.me.dirty      });

                        counter  =  r.textmill_statechange__millout.counter;                                                                    # Count messages sent through port,
                        counter := *counter + 5;                                                                                                # for debug/display purposes.
                    };

                fun tell__textmill_statechange__watchers_full_state
                      (
                        r:                      Runstate
                      )
                    =
                    mt::ipm::apply   tell_watcher   *r.textmill_statechange__watchers
                    where
                        fun tell_watcher
                              (
                                inport:          mt::Inport,                                                                                    # 
                                watchfn:        (mt::Outport, mt::Textmill_Statechange) -> Void                                                 # 
                              )
                            =
                            tell__textmill_statechange__watcher_full_state (watchfn, r);
                    end;

                fun line_range (textlines: mt::Textlines,  firstline: Int,  lastline: Int):  List(String)
                    =
                    lines (lastline, [])
                    where
                        fun lines (i: Int, result: List(String))
                            =
                            if (i < firstline)    result;
                            else
                                result =    case (nl::find (textlines, i))
                                                #
                                                THE line => mt::visible_line(line) ! result;
                                                NULL     =>                          result;
                                            esac;

                                lines  (i - 1,  result);
                            fi;
                    end;


                fun changed_textlines ([]:  List(mt::Editfn_Out_Option))     =>  NULL;
                    changed_textlines (mt::TEXTLINES textlines ! _)          =>  THE textlines;
                    changed_textlines (_ ! rest)                             =>  changed_textlines rest;
                end;

                fun changed_readonly  ([]:  List(mt::Editfn_Out_Option))     =>  NULL;
                    changed_readonly  (mt::READONLY readonly ! _)            =>  THE readonly;
                    changed_readonly  (_ ! rest)                             =>  changed_readonly rest;
                end;

                fun changed_edit_history  ([]:  List(mt::Editfn_Out_Option)) =>  NULL;
                    changed_edit_history  (mt::EDIT_HISTORY history ! _)     =>  THE history;
                    changed_edit_history  (_ ! rest)                         =>  changed_edit_history rest;
                end;


                fun do_editfn_out
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        editfn_out:     mt::Editfn_Out,

                        log_undo_info:  Bool

                      )
                    =
                    {   was = *me.state;
                        #
                        case editfn_out
                            #
                            FAIL _ => ();                                                                                                               # Editfn aborted, no changes to process.

                            WORK editfn_out_options                                                                                                     # Editfn did not abort ...
                                =>
                                {
                                    case (changed_readonly  editfn_out_options)
                                        #
                                        NULL => ();                                                                                                     # 

                                        THE readonly
                                            =>
                                            {   was = *me.readonly;
                                                now =     readonly;

                                                me.readonly :=  readonly;

                                                tell__textmill_statechange__watchers 
                                                  (
                                                    *textmill_statechange__watchers,
                                                    mt::READONLY_CHANGED  { was, now },
                                                    runstate
                                                  );
                                            };
                                    esac;

                                    log_undo_info
                                        =
                                        case (changed_edit_history  editfn_out_options)
                                            #
                                            NULL => log_undo_info;                                                                                      # 

                                            THE edit_history
                                                =>
                                                {   me.edit_history :=  edit_history;
                                                    #
#                                                   tell__textmill_statechange__watchers                                                                # XXX QUERO FIXME Is there any reason to do something like this?
#                                                     (
#                                                       *textmill_statechange__watchers,
#                                                       mt::EDIT_HISTORY_CHANGED  { was, now },
#                                                       runstate
#                                                     );

                                                    FALSE;
                                                };
                                        esac;

                                    case (changed_textlines  editfn_out_options)
                                        #
                                        NULL => ();                                                                                                     # Editfn did NOT change contents of textmill, so we're done.

                                        THE new_textlines                                                                                               # Editfn DID change contents of textmill.
                                            =>
                                            {   now     = { textlines =>  new_textlines,
                                                            editcount =>  was.editcount + 1
                                                          };

                                                was_dirty    =  *me.dirty;

                                                me.state    :=  now;
                                                me.dirty    :=  TRUE;
                                                me.readonly :=  FALSE;

                                                if log_undo_info
                                                    #
                                                    me.edit_history :=  bq::push (*me.edit_history, was);                                               # Add previous state to history.
                                                fi;

                                                tell__textmill_statechange__watchers
                                                  (
                                                    *textmill_statechange__watchers,
                                                    mt::TEXTSTATE_CHANGED  { was, now },
                                                    runstate
                                                  );

                                                if (was_dirty != *me.dirty)
                                                    #
                                                    tell__textmill_statechange__watchers
                                                      (
                                                        *textmill_statechange__watchers,
                                                        #
                                                        mt::DIRTY_CHANGED
                                                          { was => was_dirty,
                                                            now => *me.dirty
                                                          },

                                                        runstate
                                                    );
                                                fi;

                                            };
                                    esac;
                                };
                        esac;

                        editfn_out;                                                                                                                     # Return all updates to caller.
                    };

                fun do_get_or_pass_edit_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg as
                        { keystring:            String,                                                         # User keystroke that invoked this editfn.
                          numeric_prefix:       Null_Or(Int),                                                   # ^U "Universal numeric prefix" value for this editfn if supplied by user, else NULL.
                          prompted_args:        List( mt::Prompted_Arg ),                                       # Args read interactively from user.
                          point_and_mark:       mt::Point_And_Mark,                                             # 'point' is the visible cursor. 'mark' (if set) is the other end of the selected region. (Emacs nomenclature.)
                          lastmark:             Null_Or( g2d::Point ),
                          screen_origin:        g2d::Point,
                          visible_lines:        Int,
                          log_undo_info:        Bool,
                          #
                          pane_tag:             Int,
                          pane_id:              Id,
                          editfn_node:          mt::Editfn_Node,
                          widget_to_guiboss:    gt::Widget_To_Guiboss,                                          # 
                          #
                          mainmill_modestate:   mt::Panemode_State,
                          minimill_modestate:   mt::Panemode_State,
                          #
                          textpane_to_textmill: mt::Textpane_To_Textmill,
                          mode_to_drawpane:     Null_Or( m2d::Mode_To_Drawpane ),
                          valid_completions:    Null_Or( String -> List(String) )                               # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                        }
                      )
                    =
                    {   was = *me.state;
                        #
#                       runstate.mill_to_millboss
#                           ->
#                           mt::MILL_TO_MILLBOSS eb;                                                            # We don't currently use 'eb' here.
 
                        stipulate
                            fun make_pane_guiplan ()                                                            # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                =
                                {   filepath      =  *me.filepath;
                                    textpane_hint =  *me.textpane_hint;
                                    #
                                    make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                };
                        herein
                            editfn_in =   { args                =>  prompted_args,
                                            textlines           =>  was.textlines,
                                            point               =>  point_and_mark.point,
                                            mark                =>  point_and_mark.mark,
                                            readonly            => *me.readonly,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            edit_history        => *me.edit_history,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            to,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            keystring,
                                            numeric_prefix,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions
                                          };
                        end;

                        plain_editfn
                            =
                            case editfn_node
                                #
                                mt::PLAIN_EDITFN  plain_editfn => plain_editfn;

                                mt::FANCY_EDITFN => {   msg = "FANCY_EDITFN not supported -- do_pass_edit_result in textmill.pkg";
                                                        log::fatal msg;
                                                        raise exception DIE msg;
                                                    };
                            esac;

                        editfn_out =    (plain_editfn.editfn editfn_in)
                                        except _ = FAIL "<uncaught exception in editfn>";                                                               # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                        do_editfn_out (runstate, editfn_out, log_undo_info);
                    };


                fun do_get_drawpane_startup_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Startup_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_startup_fn, ... };

                        case drawpane_startup_fn
                            #
                            NULL => WORK [];

                            THE drawpane_startup_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_startup_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            widget_to_guiboss,
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            mill_to_millboss,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_startup_fn  drawpane_startup_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_startup_in>";                                      # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_shutdown_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Shutdown_Arg
                      )
                    =
                    {
                        arg ->    {
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) )                       # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_shutdown_fn, ... };

                        case drawpane_shutdown_fn
                            #
                            NULL => WORK [];

                            THE drawpane_shutdown_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_shutdown_in
                                          =
                                          {
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            mill_to_millboss,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions
                                          };
                                    end;


                                    editfn_out =    (drawpane_shutdown_fn  drawpane_shutdown_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_shutdown_in>";                                     # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_initialize_gadget_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Initialize_Gadget_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    site:                       g2d::Box,                                               # Widget's assigned area in window coordinates.
                                    pass_font:                  List(String) -> Replyqueue
                                                                             -> (evt::Font -> Void) -> Void,            # Nonblocking version of next, for use in imps.
                                     get_font:                  List(String) ->  evt::Font,                             # Accepts a list of font names which are tried in order.
                                    make_rw_pixmap:             g2d::Size -> g2p::Gadget_To_Rw_Pixmap,                  # Make an Xserver-side rw_pixmap for scratch use by widget.  In general there is no need for the widget to explicitly free these -- guiboss_imp will do this automatically when the gui is killed.

                                    #
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    theme:                      wt::Widget_Theme,
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_initialize_gadget_fn, ... };

                        case drawpane_initialize_gadget_fn
                            #
                            NULL => WORK [];

                            THE drawpane_initialize_gadget_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_initialize_gadget_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            site,
                                            pass_font,
                                             get_font,
                                            make_rw_pixmap,
                                            #
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            theme,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_initialize_gadget_fn  drawpane_initialize_gadget_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_initialize_gadget_in>";                                    # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_redraw_request_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Redraw_Request_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    frame_number:               Int,                                                    # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
                                    site:                       g2d::Box,                                               # Widget's assigned area in window coordinates.
                                    duration_in_seconds:        Float,                                                  # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
                                    gadget_mode:                gt::Gadget_Mode,
                                    popup_nesting_depth:        Int,                                                    # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
                                    #
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    theme:                      wt::Widget_Theme,
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_redraw_request_fn, ... };

                        case drawpane_redraw_request_fn
                            #
                            NULL => WORK [];

                            THE drawpane_redraw_request_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_redraw_request_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            frame_number,
                                            site,
                                            duration_in_seconds,
                                            gadget_mode,
                                            popup_nesting_depth,
                                            #
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            theme,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_redraw_request_fn  drawpane_redraw_request_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_redraw_request_in>";                                       # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_mouse_click_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Mouse_Click_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    button:                     evt::Mousebutton,
                                    event:                      gt::Mousebutton_Event,                                  # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
                                    point:                      g2d::Point,
                                    widget_layout_hint:         gt::Widget_Layout_Hint,
                                    frame_indent_hint:          gt::Frame_Indent_Hint,
                                    site:                       g2d::Box,                                               # Widget's assigned area in window coordinates.
                                    modifier_keys_state:        evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                                    mousebuttons_state:         evt::Mousebuttons_State,                                # State of mouse buttons as a bool record.
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    theme:                      wt::Widget_Theme,
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_mouse_click_fn, ... };

                        case drawpane_mouse_click_fn
                            #
                            NULL => WORK [];

                            THE drawpane_mouse_click_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_mouse_click_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            button,
                                            event,
                                            point,
                                            widget_layout_hint,
                                            frame_indent_hint,
                                            site,
                                            modifier_keys_state,
                                            mousebuttons_state,
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            theme,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_mouse_click_fn  drawpane_mouse_click_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_mouse_click_in>";                                  # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_mouse_drag_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Mouse_Drag_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    button:                     evt::Mousebutton,
                                    event_point:                g2d::Point,
                                    start_point:                g2d::Point,
                                    last_point:                 g2d::Point,
                                    phase:                      gt::Drag_Phase, 
                                    widget_layout_hint:         gt::Widget_Layout_Hint,
                                    frame_indent_hint:          gt::Frame_Indent_Hint,
                                    site:                       g2d::Box,                                               # Widget's assigned area in window coordinates.
                                    modifier_keys_state:        evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                                    mousebuttons_state:         evt::Mousebuttons_State,                                # State of mouse buttons as a bool record.
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    theme:                      wt::Widget_Theme,
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_mouse_drag_fn, ... };

                        case drawpane_mouse_drag_fn
                            #
                            NULL => WORK [];

                            THE drawpane_mouse_drag_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_mouse_drag_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            button,
                                            event_point,
                                            start_point,
                                            last_point,
                                            phase,
                                            widget_layout_hint,
                                            frame_indent_hint,
                                            site,
                                            modifier_keys_state,
                                            mousebuttons_state,
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            theme,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_mouse_drag_fn  drawpane_mouse_drag_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_mouse_drag_in>";                                   # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_get_drawpane_mouse_transit_result
                      (
                        runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
                        #
                        arg:    mt::Drawpane_Mouse_Transit_Arg
                      )
                    =
                    {
                        arg ->    {
                                    drawpane_id:                Id,                                                     # Unique id of this drawpane widget.
                                    doc:                        String,                                                 # Text description of this drawpane widget for debug/display purposes.
                                    transit:                    gt::Gadget_Transit,                                     # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
                                    event_point:                g2d::Point,
                                    widget_layout_hint:         gt::Widget_Layout_Hint,
                                    frame_indent_hint:          gt::Frame_Indent_Hint,
                                    site:                       g2d::Box,                                               # Widget's assigned area in window coordinates.
                                    modifier_keys_state:        evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                                    point_and_mark:             mt::Point_And_Mark,
                                    lastmark:                   Null_Or( g2d::Point ),                                  # Last valid value of 'mark' if any -- used to retrieve old mark values by   exchange_point_and_mark    in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                    screen_origin:                       g2d::Point,                                    # Origin of pane-visible text relative to textmill contents:  (0,0) means we're showing top of buffer at top of textpane.
                                    visible_lines:              Int,                                                    # Number of lines of text visible in pane.
                                    log_undo_info:              Bool,                                                   # If log_undo_info is FALSE no entry will be made in the undo history.
                                    pane_tag:                   Int,                                                    # Tag of pane for which this editfn is being invoked.  This is a small int for human/GUI use.
                                    pane_id:                    Id,                                                     # Id  of pane for which this editfn is being invoked.
                                    widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                                    theme:                      wt::Widget_Theme,
                                    #
                                    mainmill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
                                    minimill_modestate:         mt::Panemode_State,                                     # Any persistent per-mode state (e.g., private state for    minimill-mode.pkg) for mini mill is available via this.
                                    #
                                    textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
                                    mode_to_drawpane:           m2d::Mode_To_Drawpane,                                  # 
                                    valid_completions:          Null_Or( String -> List(String) ),                      # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
                                    #
                                    do:                         (Void -> Void) -> Void,                                 # Used by widget subthreads to run code in main widget microthread.
                                    to:                         Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                                  };

                        mainmill_modestate.mode
                            ->
                            mt::PANEMODE { drawpane_mouse_transit_fn, ... };

                        case drawpane_mouse_transit_fn
                            #
                            NULL => WORK [];

                            THE drawpane_mouse_transit_fn
                                =>
                                {   was = *me.state;
                                    #
#                                   runstate.mill_to_millboss
#                                       ->
#                                       mt::MILL_TO_MILLBOSS eb;                                                                # We don't currently use 'eb' here.

                                    stipulate
                                        fun make_pane_guiplan ()                                                                # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
                                            =
                                            {   filepath      =  *me.filepath;
                                                textpane_hint =  *me.textpane_hint;
                                                #
                                                make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                            };
                                    herein
                                        drawpane_mouse_transit_in
                                          =
                                          {
                                            drawpane_id,
                                            doc,
                                            transit,
                                            event_point,
                                            widget_layout_hint,
                                            frame_indent_hint,
                                            site,
                                            modifier_keys_state,
                                            textlines           =>  was.textlines,
                                            point_and_mark,
                                            lastmark,
                                            screen_origin,
                                            visible_lines,
                                            readonly            => *me.readonly,
                                            pane_tag,
                                            pane_id,
                                            mill_id             => id,
                                            edit_history        => *me.edit_history,
                                            widget_to_guiboss,
                                            mill_to_millboss,
                                            theme,
                                            #
                                            mainmill_modestate,
                                            minimill_modestate,
                                            #
                                            mill_extension_state => *mill_extension_state__global,              
                                            textpane_to_textmill,
                                            mode_to_drawpane,
                                            valid_completions,
                                            #
                                            do,
                                            to
                                          };
                                    end;


                                    editfn_out =    (drawpane_mouse_transit_fn  drawpane_mouse_transit_in)
                                                    except _ = FAIL "<uncaught exception in drawpane_mouse_transit_in>";                                # Handle any uncaught exceptions in editfn. (Shouldn't happen.)

                                    do_editfn_out (runstate, editfn_out, log_undo_info);
                                };
                        esac;
                    };

                fun do_undo (runstate as { id, me, textmill_statechange__watchers, ... }:       Runstate)                                               # THIS IS PROBABLY DUE TO BE DELETED. EARLY CODE, SYSTEM EVOLVED A DIFFERENT DIRECTION. XXX SUCKO FIXME. 
                    =
                    if (bq::length *me.edit_history > 0)
                        #
                        (*me.state)                 -> (             was);
                        (bq::pull *me.edit_history) -> (new_history, now);

                        now     = the now;                                                                                                              # Should not be NULL because we checked for history_len==0 above.

                        me.edit_history :=  new_history;
                        me.state        :=  now;

                        tell__textmill_statechange__watchers
                          (
                            *textmill_statechange__watchers,
                            mt::UNDO { was, now },
                            runstate
                          );
                    fi;



                #################################################################################
                # App_To_Mill interface fns::
                #
                #
                fun get_pane_guiplan ():        gt::Gp_Widget_Type                                                                                      # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Gp_Widget_Type );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, make_pane_guiplan', textpane_to_textmill, ... }: Runstate)
                                =
                                {   filepath      =  *me.filepath;
                                    textpane_hint =  *me.textpane_hint;
                                    #
                                    gp_widget = make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                    #
                                    put_in_oneshot (reply_oneshot, gp_widget);
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_pane_guiplan (replyqueue: Replyqueue) (reply_handler: gt::Gp_Widget_Type -> Void):     Void                                    # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Gp_Widget_Type );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, make_pane_guiplan', textpane_to_textmill, ... }: Runstate)
                                =
                                {   filepath      =  *me.filepath;
                                    textpane_hint =  *me.textpane_hint;
                                    #
                                    gp_widget = make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
                                    #
                                    put_in_oneshot (reply_oneshot, gp_widget);
                                }
                        );

                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };
                    #

                fun get_dirty ()                                                                                                                        # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.dirty)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_dirty  (replyqueue: Replyqueue)  (reply_handler: Bool -> Void)                                                                 # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.dirty)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_filepath (filepath: Null_Or( String ))                                                                                          # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                {   tell__textmill_statechange__watchers
                                      (
                                        *textmill_statechange__watchers,
                                        mt::FILEPATH_CHANGED { now => filepath, was => *me.filepath },
                                        runstate
                                      );
                                    #
                                    me.filepath := filepath;
                                }
                        );

                    };
                    #
                fun get_filepath ()                                                                                                                     # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or( String ) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.filepath)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_filepath (replyqueue: Replyqueue)  (reply_handler: Null_Or( String ) -> Void)                                                  # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or( String ) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.filepath)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_name (name: String)                                                                                                             # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                {   tell__textmill_statechange__watchers
                                      (
                                        *textmill_statechange__watchers,
                                        mt::NAME_CHANGED { now => name, was => *me.name },
                                        runstate
                                      );
                                    #
                                    me.name := name;
                                }
                        );
                    };
                    #
                fun get_name ()                                                                                                                         # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( String );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.name)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_name  (replyqueue: Replyqueue)  (reply_handler: String -> Void)                                                                # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( String );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.name)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun note__textmill_statechange
                      (
                        outport:                mt::Outport,                                                                                    #
                        textmill_statechange:   mt::Textmill_Statechange                                                                        # 
                      ) 
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate as { id, me, textmill_statechange__watchers, textmill_statechange__millin, ... }: Runstate)
                                =
                                {   case textmill_statechange
                                        #
                                        ( mt::TEXTSTATE_CHANGED { was: mt::Textstate,      now: mt::Textstate     }
                                        | mt::UNDO                      { was: mt::Textstate,      now: mt::Undostate     }
                                        )
                                            =>
                                            {   was_dirty    =  *me.dirty;
                                                #       
                                                me.state        :=  now;
                                                me.dirty        :=  FALSE;                                                                      # As a mirror of our upstream input, it makes no sense to think of our copy as 'dirty'.
                                                me.readonly     :=  TRUE;                                                                       # As a mirror of our upstream input, it makes no sense to let user try to edit us.
                                                me.edit_history :=  bq::make_queue  max_history_length;                                         # As a mirror of our upstream input, it makes no sense to maintain an edit history.

                                                tell__textmill_statechange__watchers                                                            # Tell anyone watching us about our change of text contents.
                                                  (
                                                    *textmill_statechange__watchers,
                                                    mt::TEXTSTATE_CHANGED  { was, now },
                                                    runstate
                                                  );
                                                                                                                                                # We don't tell our watchers about any change in our dirty or readonly state because they are basically meaningless.
                                                                                                                                                # Compare with above logic in do_get_or_pass_edit_result().
                                            };
                                                                                                                                                # List rest explicitly so that if one gets added we'll draw a compile error which reminds us to think about whether support is needed here.
                                        mt::FILEPATH_CHANGED    _ => ();                                                                        # Ignore because we're mirroring input from upstream so we don't want to be independently trying to save it to disk or such.
                                        mt::NAME_CHANGED        _ => ();                                                                        # Ignore because as a mirror we don't want to have same name as upstream mill.
                                        mt::READONLY_CHANGED    _ => ();                                                                        # Ignore because as a mirror we always want to be readonly -- doesn't make sense to let user try to edit stuff which can be overwritten at any moment.
                                        mt::DIRTY_CHANGED       _ => ();                                                                        # Ignore because as a readonly mirror 'dirty' isn't really relevant to us.
                                    esac;

                                    counter  =  textmill_statechange__millin.counter;                                                           # Count messages read through port,
                                    counter := *counter + 1;                                                                                    # for debug/display purposes.
                                }
                        );
                    };
                    #
                fun make__textmill_statechange__millout (textmill_statechange__outport: mt::Outport):  mt::Millout
                    =
                    {   outport = textmill_statechange__outport;                                                                                #
                        #                                                                                                                       #
                        textmill_statechange_millout                                                                                            #
                          =                                                                                                                     #
                          { note_watcher =>  note__textmill_statechange__watcher,                                                               #
                            drop_watcher =>  drop__textmill_statechange__watcher                                                                #
                          };                                                                                                                    #
                                                                                                                                                #
                        millout  =  tso::wrap__textmill_statechange_millout                                                                     # Wrap it so millboss, plugboard &tc don't need to know about port-specific types.
                                      (                                                                                                         #
                                        outport,                                                                                                #
                                        textmill_statechange_millout                                                                            #
                                      );                                                                                                        #
                        millout;
                    }
                also
                fun note__textmill_statechange__watcher                                                                                         # PUBLIC.
                      (
                        watcher:                mt::Inport,                                                                                     # 
                        millin:                 Null_Or(mt::Millin),                                                                            # This will be NULL if watcher is not another mill (e.g. a pane).
                        watchfn:                (mt::Outport, mt::Textmill_Statechange) -> Void                                                 # 
                      ) 
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate
                                as
                                { id,
                                  me,
                                  textmill_statechange__watchers,
                                  textmill_statechange__outport,
                                  textmill_statechange__millout,
                                  ...
                                }:      Runstate)
                                =
                                {   textmill_statechange__watchers
                                        :=
                                        mt::ipm::set ( *textmill_statechange__watchers,
                                                        watcher,
                                                        (watcher, watchfn)
                                                     );

                                    case millin
                                        #
                                        THE millin
                                            =>
                                            m2m.note_millwatch  millwatch                                                                       # Tell millboss about new watcher/watchee edge in the millgraph.
                                                where
                                                    millout   =  textmill_statechange__millout;
                                                    #
                                                    millwatch =  { millin, millout };

                                                end;
                                        NULL => ();
                                    esac;

                                    tell__textmill_statechange__watcher_full_state (watchfn, runstate);                                         # Make sure new watcher starts out with full update.
                                }
                        );
                    }
                also
                fun drop__textmill_statechange__watcher (inport:  mt::Inport)                                                                   # PUBLIC.
                    =
                    {   
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, textmill_statechange__outport, ... }: Runstate)
                                =
                                {   textmill_statechange__watchers
                                        :=
                                        mt::ipm::drop (*textmill_statechange__watchers, inport);

                                    m2m.drop_millwatch  millwatch                                                                               # Tell millboss about vanished watcher/watchee edge in the millgraph.
                                        where                                                                                                   # If we're not a mill-mill edge we won't be in millboss' graph, but
                                            outport   = textmill_statechange__outport;                                                          # that is OK because removing a non-existent edge is a no-op.
                                            #
                                            millwatch = { inport, outport };
                                        end;
                                }       
                        );
                    };


                fun make__textmill_statechange__millin (textmill_statechange__inport: mt::Inport):      mt::Millin                              # Construct a description of the inport on which we read textmill_statechange events, for publication in our App_To_Mill export.  This is normally NULL -- we operate autonomously.
                      =                                                                                                                         # 
                      { inport     =>   textmill_statechange__inport,                                                                           # This gives the world a globally unique name for this particular inport.
                        port_type  =>   tso::port_type,                                                                                         # This tells the world that on this port we listen for textmill_statechange events.
                        mono       =>   TRUE,                                                                                                   # This tells the world that we listen on at most one input textmill_statechange stream at a time.
                        #                                                                                                                       # 
                        note_input =>   note__textmill_statechange__watchee,                                                                    # Caller uses this to tell us to start reading from a different textmill_statechange stream.
                        drop_input =>   drop__textmill_statechange__watchee,                                                                    # Caller uses this to disconnect us from input textmill_statechange stream.
                        #                                                                                                                       # 
                        counter    =>   REF 0   
                      }                                                                                                                         #
                also
                fun note__textmill_statechange__watchee (wrapped_millout: mt::Millout)                                                          # PUBLIC.  Start watching 'wrapped_millout'.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate
                                as
                                { id,
                                  me,
                                  textmill_statechange__watchee,
                                  textmill_statechange__inport,
                                  textmill_statechange__millin,
                                  ...
                                }:              Runstate)
                                =
                                {   millout =  tso::unwrap__textmill_statechange_millout  wrapped_millout;
                                    #
                                    case *textmill_statechange__watchee
                                        #
                                        THE watchee =>  watchee.millout.drop_watcher  textmill_statechange__inport;                             # Say goodbye to previous watchee.
                                        NULL        =>  ();
                                    esac;

                                    millout.note_watcher                                                                                        # Say hello   to new watchee.
                                      (
                                        textmill_statechange__inport,
                                        THE textmill_statechange__millin,                                                                       # So note_watcher can pass Millout+Millin to millboss at same time, keeping millboss consistent.
                                        note__textmill_statechange
                                      );

                                    textmill_statechange__watchee
                                        :=
                                        THE  { wrapped_millout, millout };
                                }
                        );
                    }
                also
                fun drop__textmill_statechange__watchee (wrapped_millout:  mt::Millout)                                                         # PUBLIC.
                    =
                    {   
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchee, textmill_statechange__inport, ... }: Runstate)
                                =
                                {   millout =  tso::unwrap__textmill_statechange_millout  wrapped_millout;
                                    #
                                    case *textmill_statechange__watchee
                                        #
                                        THE watchee =>  watchee.millout.drop_watcher  textmill_statechange__inport;                             # Say goodbye to previous watchee.
                                        NULL        =>  ();
                                    esac;

                                    textmill_statechange__watchee
                                        :=
                                        NULL;
                                }
                        );
                    };




                fun make_millouts                                                                                                               # Construct a description of all our outports, for client use.
                      (
                        textmill_statechange__outport:  mt::Outport,
                        textmill_statechange__millout:  mt::Millout
                      )
                    :                                   mt::opm::Map(mt::Millout)
                    =
                    {   millouts =  mt::opm::empty;                                                                                             # Start with an empty outport map.
                        #
                        millouts =  mt::opm::set (millouts, textmill_statechange__outport, textmill_statechange__millout );                     # Add our textmill_statechange outport.

                        millouts;                                                                                                               # Return map defining all our our outports.
                    };

                fun make_millins
                      (
                        textmill_statechange__inport:   mt::Inport,
                        textmill_statechange__millin:   mt::Millin
                      ) 
                    :                                   mt::ipm::Map(mt::Millin)                                                                # Construct a description of all our inports, for client use.
                    =
                    {   millins = mt::ipm::empty;                                                                                               # Start with an empty inport map.
                        #
                        millins = mt::ipm::set (millins, textmill_statechange__inport, textmill_statechange__millin );                          # Add our textmill_statechange inport.

                        millins;                                                                                                                # Return map defining all our our inports.
                    };

                fun reload_from_file  ()                                                                                                        # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                case (*me.filepath)
                                    #
                                    NULL   =>  ();

                                    THE filepath
                                        =>
                                        {   as_lines =  file::as_lines;
                                            #
                                            lines = as_lines filepath
                                                    except _ =
                                                        {
                                                            [ "\n" ];
                                                        };

                                            lines     = map do_line lines
                                                        where
                                                            fun do_line (string: String)
                                                                =
                                                                mt::MONOLINE { string, prefix => NULL };
                                                        end;

                                            textlines = nl::from_list  lines;

                                            me.state := { textlines,
                                                          editcount =>  1
                                                        };
                                        };
                                esac
                        );
                    };
                    #
                fun save_to_file  ()                                                                                                            # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate as  { id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                case (*me.filepath)
                                    #
                                    NULL   =>  ();

                                    THE filepath
                                        =>
                                        if *me.dirty                                                                                            # No point saving buffer contents to disk unless they have been modified.
                                            #

# XXX SUCKO FIXME We're not doing any handling of errors like failure to open or write or close here.  We probably should also be writing to a tempfile and then renaming to the actual filename only if the complete write+close sequence succeeds.
                                            (file::open_for_write filepath)
                                                ->
                                                outstream;

                                            apply  do_line  (nl::vals_list (*me.state).textlines)
                                                where
                                                    fun write_monoline (monoline: mt::Monoline)
                                                        =
                                                        file::write (outstream, monoline.string);

                                                    fun do_line (textline: mt::Textline)
                                                        =
                                                        case textline
                                                            #
                                                            mt::MONOLINE monoline
                                                                =>
                                                                write_monoline monoline;

                                                            mt::POLYLINE { line, more }
                                                                =>
                                                                {   write_monoline line;
                                                                    apply write_monoline more;
                                                                };
                                                        esac;
                                                end;

                                            file::close_output  outstream;

                                            me.dirty := FALSE;

                                            tell__textmill_statechange__watchers
                                              (
                                                *textmill_statechange__watchers,
                                                mt::DIRTY_CHANGED { was => TRUE, now => FALSE },
                                                runstate
                                              );
                                        fi;
                                esac
                        );
                    };



                #################################################################################
                # millboss_to_mill interface fns::
                #
                #
                fun wakeup
                      {
                        wakeup_arg:     mt::Wakeup_Arg,
                        wakeup_fn:      mt::Wakeup_Arg -> Void          # Mill thunk registered via mill_to_millboss.wake_me[].
                      }
                    =
                    put_in_mailqueue  (textmill_q,
                        #
                        \\ ({ id, me, ... }: Runstate)
                            =
                            wakeup_fn  wakeup_arg
                    );



                #################################################################################
                # texteditor interface fns::
                #
                #

                fun get_maxline ()                                                                                                              # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Int );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    put_in_oneshot (reply_oneshot, the (nl::max_key state.textlines));
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_maxline  (replyqueue: Replyqueue)  (reply_handler: Int -> Void)                                                        # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Int );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    put_in_oneshot (reply_oneshot, the (nl::max_key state.textlines));
                                }
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };


                fun get_line (i: Int)                                                                                                           # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or(String) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    line  = case (nl::find (state.textlines, i))
                                                #
                                                THE line =>  THE (mt::visible_line line);
                                                NULL     =>  NULL;
                                            esac;

                                    put_in_oneshot (reply_oneshot, line);
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #           
                fun pass_line  (replyqueue: Replyqueue)  (i: Int)  (reply_handler: Null_Or(String) -> Void)                                     # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or(String) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    line  = case (nl::find (state.textlines, i))
                                                #
                                                THE line =>  THE (mt::visible_line line);
                                                NULL     =>  NULL;
                                            esac;

                                    put_in_oneshot (reply_oneshot, line);
                                }
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };



                fun set_lines  (lines: List(String))                                                                                            # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                {   (*me.state) ->    { textlines, editcount };
                                    #
                                    lines     = map do_line lines
                                                where
                                                    fun do_line (string: String)
                                                        =
                                                        mt::MONOLINE { string, prefix => NULL };
                                                end;

                                    me.state :=       { textlines =>  nl::from_list  lines,
                                                        editcount =>  editcount + 1
                                                      };
                                }
                        );
                    };
                    #
                fun get_lines (first: Int,  last: Int)                                                                                          # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( List(String) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    put_in_oneshot (reply_oneshot, (line_range (state.textlines, first, last)));
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #           
                fun pass_lines  (replyqueue: Replyqueue)  (first: Int, last: Int)  (reply_handler: List(String) -> Void)                        # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( List(String) );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {   state = *me.state;
                                    #
                                    put_in_oneshot (reply_oneshot, (line_range (state.textlines, first, last)));
                                }
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };


                fun get_textstate ()                                                                                                            # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Textstate );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.state)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_textstate  (replyqueue: Replyqueue)  (reply_handler: mt::Textstate -> Void)                                            # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Textstate );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.state)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun get_edit_result                                                                                                             # PUBLIC.
                      #         
                      (arg:             mt::Edit_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_or_pass_edit_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                fun pass_edit_result                                                                                                            # PUBLIC.
                      #         
                      (arg:             mt::Edit_Arg)
                      #         
                      (replyqueue:      Replyqueue)
                      #         
                      (reply_handler:   mt::Editfn_Out -> Void)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_or_pass_edit_result (r, arg))
                        );

                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun get_drawpane_startup_result                                                                                                 # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Startup_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_startup_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_shutdown_result                                                                                                # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Shutdown_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_shutdown_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_initialize_gadget_result                                                                                       # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Initialize_Gadget_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_initialize_gadget_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_redraw_request_result                                                                                          # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Redraw_Request_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_redraw_request_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_mouse_click_result                                                                                             # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Mouse_Click_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_click_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_mouse_drag_result                                                                                              # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Mouse_Drag_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_drag_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_drawpane_mouse_transit_result                                                                                           # PUBLIC.
                      #         
                      (arg:             mt::Drawpane_Mouse_Transit_Arg)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Editfn_Out );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_transit_result (r, arg))
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun undo ()                                                                                                                     # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (r: Runstate)
                                =
                                do_undo r
                        );
                    };


                fun set_readonly (readonly: Bool)                                                                                               # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                {   was = *me.readonly;
                                    now =     readonly;

                                    me.readonly := readonly;

                                    tell__textmill_statechange__watchers
                                      (
                                        *textmill_statechange__watchers,
                                        mt::READONLY_CHANGED { was, now },
                                        runstate
                                      );
                                }
                        );
                    };
                    #
                fun get_readonly ()                                                                                                             # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.readonly)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_readonly  (replyqueue: Replyqueue)  (reply_handler: Bool -> Void)                                                      # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.readonly)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };


                fun set_textpane_hint (hint: Crypt)                                                                                             # PUBLIC.
                    =
                    {   put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                me.textpane_hint := hint
                        );

                    };
                    #
                fun get_textpane_hint ()                                                                                                        # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Crypt );
                        #
                        put_in_mailqueue  (textmill_q,
                            #
                            \\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.textpane_hint)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #


            end;

        #
        fun process_options
              (
                options:        List( mt::Textmill_Option ),
                #
                { name,
                  id,
                  filename,
                  text,
                  textmill_extension
                }
              )
            =
            {   my_name                 =  REF name;
                my_id                   =  REF id;
                my_filename             =  REF filename;        
                my_text                 =  REF text;
                my_textmill_extension   =  REF textmill_extension;

                apply  do_option  options
                where
                    fun do_option (mt::MICROTHREAD_NAME     n)  =>  my_name               :=      n;
                        do_option (mt::ID                   i)  =>  my_id                 :=      i;
                        do_option (mt::INITIAL_FILENAME     n)  =>  my_filename           :=      n;
                        do_option (mt::UTF8                 n)  =>  my_text               :=      n;
                        do_option (mt::TEXTMILL_EXTENSION   x)  =>  my_textmill_extension :=  THE x;
                    end;
                end;

                { name                  =>  *my_name,
                  id                    =>  *my_id,
                  filename              =>  *my_filename,
                  text                  =>  *my_text,
                  textmill_extension    =>  *my_textmill_extension
                };
            };


        ##########################################################################################
        # PUBLIC.
        #
        fun make_textmill_egg
              (textmill_arg:            mt::Textmill_Arg)                                                                                       # PUBLIC. PHASE 1: Construct our state and initialize from 'options'.
            =
            {   textmill_arg ->  { name, textmill_options };
                #
                (process_options
                  ( textmill_options,
                    { name,
                      id                 =>  id_zero,
                      filename           =>  "",
                      text               =>  "\n",
                      textmill_extension => NULL
                    }
                ) )
                    ->
                    { name,
                      id,
                      filename,
                      text,
                      textmill_extension
                    };

                filepath =  if (filename == "")         NULL;
                            else                        THE filename;
                            fi;

                my (id, textmill_options)
                    =
                    if (id_to_int(id) == 0)
                        id = issue_unique_id();                                                                                                 # Allocate unique imp id.
                        (id, mt::ID id ! textmill_options);                                                                                     # Make our id stable across stop/restart cycles.
                    else
                        (id, textmill_options);
                    fi;

                me =    { state         =>  REF textstate,
                          edit_history  =>  REF (bq::make_queue max_history_length),                                                            # 
                          filepath      =>  REF filepath,
# XXX BUGGO FIXME We need to ensure here that name is unique.  (Or is that an upstream responsibility? Anyhow, someone needs to be doing that.)  LATER: There is now a uniquify_name() to help out with this in  src/lib/x-kit/widget/edit/millboss-imp.pkg
                          name          =>  REF name,
                          dirty         =>  REF FALSE,
                          readonly      =>  REF FALSE,
                          textpane_hint =>  REF { id   =>  issue_unique_id (),                                                                  # Just being type-correct here. It is up to textpane.pkg to set a reasonable value via set_textpane_hint().
                                                  type =>  "textmill dummy",
                                                  info =>  "Ignore this",
                                                  data =>  DIE "dummy"
                                                }
                        }
                        where
                            textlines = string::lines  text;
                            #
                            textlines = map   do_line  textlines
                                        where
                                            fun do_line (string: String)
                                                =
                                                mt::MONOLINE { string, prefix => NULL };
                                        end;

                            textlines = nl::from_list  textlines;

                            textstate = { textlines,
                                          editcount =>  0
                                        };
                        end;

                \\ () = {   reply_oneshot = make_oneshot_maildrop():  Oneshot_Maildrop( (Me_Slot, Exports) );                                   # PUBLIC. PHASE 2: Start our microthread and return our Exports to caller.
                            #
                            xlogger::make_thread  name  (startup  (id, textmill_extension, reply_oneshot));                                     # Note that startup() is curried.

                            (get_from_oneshot  reply_oneshot) -> (me_slot, exports);

                            fun phase3                                                                                                          # PUBLIC. PHASE 3: Accept our Imports, then wait for Run_Gun to fire.
                                (
                                  imports:      Imports,
                                  run_gun':     Run_Gun,        
                                  end_gun':     End_Gun
                                )
                                =
                                {
                                    put_in_mailslot
                                        (
                                          me_slot,
                                          #
                                          { me,
                                            textmill_arg,
                                            imports,
                                            run_gun',
                                            end_gun'
                                          }
                                        );
                                };

                            (exports, phase3);
                        };
            };
    };

end;





Comments and suggestions to: bugs@mythryl.org

PreviousUpNext