PreviousUpNext

15.4.1405  src/lib/x-kit/widget/edit/fundamental-mode.pkg

## fundamental-mode.pkg
#
# Support fns for textmill -- mostly editing                    # textmill                      is from   src/lib/x-kit/widget/edit/textmill.pkg
# fns to be bound to keystrokes.
#
# See also:
#     src/lib/x-kit/widget/edit/textpane.pkg
#     src/lib/x-kit/widget/edit/millboss-imp.pkg
#     src/lib/x-kit/widget/edit/textmill.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 gtj =  guiboss_types_junk;                          # guiboss_types_junk            is from   src/lib/x-kit/widget/gui/guiboss-types-junk.pkg
    package lms =  list_mergesort;                              # list_mergesort                is from   src/lib/src/list-mergesort.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 tlj =  textlines_junk;                              # textlines_junk                is from   src/lib/x-kit/widget/edit/textlines-junk.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 sl  =  screenline;                                  # screenline                    is from   src/lib/x-kit/widget/edit/screenline.pkg
    package p2l =  textpane_to_screenline;                      # textpane_to_screenline        is from   src/lib/x-kit/widget/edit/textpane-to-screenline.pkg
    package frm =  frame;                                       # frame                         is from   src/lib/x-kit/widget/leaf/frame.pkg
    package wt  =  widget_theme;                                # widget_theme                  is from   src/lib/x-kit/widget/theme/widget/widget-theme.pkg
    package tp  =  textpane;                                    # textpane                      is from   src/lib/x-kit/widget/edit/textpane.pkg

    package ct  =  cutbuffer_types;                             # cutbuffer_types               is from   src/lib/x-kit/widget/edit/cutbuffer-types.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 bt  =  gui_to_sprite_theme;                         # gui_to_sprite_theme           is from   src/lib/x-kit/widget/theme/sprite/gui-to-sprite-theme.pkg

    package psx =  posixlib;                                    # posixlib                      is from   src/lib/std/src/psx/posixlib.pkg
    package sj  =  string_junk;                                 # string_junk                   is from   src/lib/std/src/string-junk.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 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 idm =  id_map;                                      # id_map                        is from   src/lib/src/id-map.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 m2d =  mode_to_drawpane;                            # mode_to_drawpane              is from   src/lib/x-kit/widget/edit/mode-to-drawpane.pkg

    package mt  =  millboss_types;                              # millboss_types                is from   src/lib/x-kit/widget/edit/millboss-types.pkg
    package tmc =  textmill_crypts;                             # textmill_crypts               is from   src/lib/x-kit/widget/edit/textmill-crypts.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 kmj =  keystroke_macro_junk;                        # keystroke_macro_junk          is from   src/lib/x-kit/widget/edit/keystroke-macro-junk.pkg

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

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

herein

    package fundamental_mode {                                  # 
        #
        exception FUNDAMENTAL_MODE__STATE;                                                                      # Our per-pane persistent state (currently none).

        fun next_line (arg:     mt::Editfn_In)
            :                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or( g2d::Point ),                                  # 
                            lastmark:                   Null_Or( g2d::Point ),                                  # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                row = row + 1;

                point =  { row, col };

                WORK [ mt::POINT point ];
            };
        next_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "next_line",
                  doc    =>  "Move point (cursor) to next line.",
                  args   =>  [],
                  editfn =>  next_line
                }
              );                                my _ =
        mt::note_editfn  next_line__editfn;


        fun previous_line (arg: mt::Editfn_In)
            :                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                row = (row > 0) ?? row - 1 :: row;

                point =  { row, col };

                WORK [ mt::POINT point ];
            };
        previous_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "previous_line",
                  doc    =>  "Move point (cursor) to previous line.",
                  args   =>  [],
                  editfn =>  previous_line
                }
              );                                my _ =
        mt::note_editfn  previous_line__editfn;


        fun previous_char (arg: mt::Editfn_In)
            :                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                text =  mt::findline (textlines, line_key);

                text = string::chomp text;

                (string::expand_tabs_and_control_chars
                  {
                    utf8text    =>   text,
                    startcol    =>   0,
                    screencol1  =>   col,
                    screencol2  =>  -1,                                                                         # Don't-care.
                    utf8byte    =>  -1                                                                          # Don't-care.
                  })
                  ->
                  { screencol1_firstcol_on_screen:      Int,
                    screencol1_colcount_on_screen:      Int,
                    ...
                  };

                col =  screencol1_firstcol_on_screen - 1;                                                       # The point of this is that if we're on a control-char or tab (which both display on multiple screen columns) we want to move to the previous char, which may mean moving multiple screen columns.

                result =    if   (col >= 0)  WORK [ mt::POINT { row, col } ];                                   # Normal case: moved back a char within current line.
                            elif (row == 1)  FAIL "Start of buffer";                                            # Abnormal case:  Was at start of buffer, couldn't move back.
                            else                                                                                # Moved back beyond start of current line so move cursor to end of previous line.
                                text =  mt::findline (textlines, line_key - 1);

                                text = string::chomp text;

                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text    =>   text,
                                    startcol    =>   0,
                                    screencol1  =>  -1,                                                         # Don't-care.
                                    screencol2  =>  -1,                                                         # Don't-care.
                                    utf8byte    =>  -1                                                          # Don't-care.
                                  })
                                  ->
                                  { screentext_length_in_screencols:    Int,
                                    ...
                                  };

                                WORK [ mt::POINT { row => row - 1, col => screentext_length_in_screencols } ];
                            fi;

                result;
            };
        previous_char__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "previous_char",
                  doc    =>  "Move point (cursor) to previous char.",
                  args   =>  [],
                  editfn =>  previous_char
                }
              );                                my _ =
        mt::note_editfn  previous_char__editfn;


        fun forward_char (arg:  mt::Editfn_In)
            :                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                text =  mt::findline (textlines, line_key);

                text =  string::chomp  text;

                (string::expand_tabs_and_control_chars
                  {
                    utf8text    =>   text,
                    startcol    =>   0,
                    screencol1  =>   col,
                    screencol2  =>  -1,                                                                         # Don't-care.
                    utf8byte    =>  -1                                                                          # Don't-care.
                  })
                  ->
                  { screencol1_firstcol_on_screen:      Int,
                    screencol1_colcount_on_screen:      Int,
                    screentext_length_in_screencols:    Int,
                    ...
                  };

                col =  screencol1_firstcol_on_screen + screencol1_colcount_on_screen;                           # The point of this is that if we're on a control-char or tab (which both display on multiple screen columns) we want to move to the next char, which may mean moving multiple screen columns.

                point =     if (col <= screentext_length_in_screencols)
                                #
                                { row, col };                                                                   # Move right within line.
                            else
                                { row => row + 1, col => 0 };                                                   # Wrap around at end of line to start of next line.
                            fi;

                WORK [ mt::POINT point ];
            };
        forward_char__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "forward_char",
                  doc    =>  "Move point (cursor) to next char.",
                  args   =>  [],
                  editfn =>  forward_char
                }
              );                                my _ =
        mt::note_editfn  forward_char__editfn;


        fun move_beginning_of_line (arg:        mt::Editfn_In)
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                point =  { row, col => 0 };

                WORK [ mt::POINT point ];
            };
        move_beginning_of_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "move_beginning_of_line",
                  doc    =>  "Move point (cursor) to start of current line.",
                  args   =>  [],
                  editfn =>  move_beginning_of_line
                }
              );                                my _ =
        mt::note_editfn  move_beginning_of_line__editfn;


        fun move_end_of_line (arg:              mt::Editfn_In)
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                point -> { row, col };

                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                text =  mt::findline (textlines, line_key);

                text =  string::chomp  text;

                (string::expand_tabs_and_control_chars
                  {
                    utf8text    =>  text,
                    startcol    =>  0,
                    screencol1  => -1,                                                                          # Don't-care.
                    screencol2  => -1,                                                                          # Don't-care.
                    utf8byte    => -1                                                                           # Don't-care.
                  })
                  ->
                  { screentext_length_in_screencols:    Int,
                    ...
                  };

                point =  { row, col => screentext_length_in_screencols };

                WORK [ mt::POINT point ];
            };
        move_end_of_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "move_end_of_line",
                  doc    =>  "Move point (cursor) to start of current line.",
                  args   =>  [],
                  editfn =>  move_end_of_line
                }
              );                                my _ =
        mt::note_editfn  move_end_of_line__editfn;


        fun delete_one_char                                                                                     # Implements functionality common to delete_char and delete_backward_char.
              (
                textlines:      mt::Textlines,
                point:          g2d::Point,
                mark:           Null_Or(g2d::Point)                                                             # 
              )
            :                                   mt::Editfn_Out
            =
            {   point -> { row, col };
                #
                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                result
                    =
                    case (nl::find (textlines, line_key))
                        #
                        THE textline
                            =>
                            {   text         =  mt::visible_line textline;
                                chomped_text =  string::chomp    text;

                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text    =>  chomped_text,
                                    startcol    =>  0,
                                    screencol1  =>  col,
                                    screencol2  => -1,                                                  # Don't-care.
                                    utf8byte    => -1                                                   # Don't-care.
                                  })
                                  ->
                                  { screentext_length_in_screencols:    Int,
                                    #
                                    screencol1_byteoffset_in_utf8text:  Int,
                                    screencol1_bytescount_in_utf8text:  Int,
                                    ...
                                  };

                                if (col >= screentext_length_in_screencols)
                                    #
                                    WORK [ ];                                                                   # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either. (emacs deletes the end-of-line newline here, but I prefer to have only kill_line do that.)
                                else
                                                                                                                # Cursor is on an existing char, possibly a multibyte utf8 char.  Excise it by replacing the line with the concatenation of the substrings preceding and following the char.
                                    text_before_point
                                        =
                                        string::substring
                                          (
                                            text,                                                               # String from which to extract substring.
                                            0,                                                                  # The substring we want starts at offset 0.
                                            screencol1_byteoffset_in_utf8text                                   # The substring we want runs to location of cursor.  Treating cursor offset as length works (only) because we're starting substring at offset zero.
                                          );

                                    text_beyond_point
                                        =
                                        string::extract
                                          (
                                            text,                                                               # String from which to extract substring.
                                            screencol1_byteoffset_in_utf8text + screencol1_bytescount_in_utf8text,      # Substring starts immediately after the byte(s) under the cursor.  (Cursor will mark multiple bytes only if it is on a multibyte utf8 char.)
                                            NULL                                                                # Substring runs to end of 'text'.
                                          );

                                    updated_text        =  string::cat [ text_before_point,
                                                                         text_beyond_point
                                                                       ];

                                    updated_text        =  mt::MONOLINE { string =>  updated_text,
                                                                          prefix =>  NULL
                                                                        };

                                    updated_textlines                                                           # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                        =
                                        (nl::remove (textlines, line_key))
                                        except _ = textlines;                                                   # This will happen if there is no line 'line_key' in textlines.

                                    updated_textlines                                                           # Now insert updated line.
                                        =
                                        nl::set (updated_textlines, line_key, updated_text);

                                    WORK  [ mt::TEXTLINES updated_textlines,
                                            mt::POINT { row, col }                                              # Needed for delete_backward_char, where cursor position changes.
                                          ];
                                fi;     
                            };

                        NULL     => WORK [ ];                                                                   # Cursor is on non-existent line.  Don't fail, but don't do anything either.
                    esac;

                result;
            };

        fun delete_char (arg:                   mt::Editfn_In)
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only.";
                else
                    point -> { row, col };

                    delete_one_char (textlines, point, mark);                                                           # Code shared with delete_backward_char.
                fi;
            };
        delete_char__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "delete_char",
                  doc    =>  "Delete char under point (cursor).",
                  args   =>  [],
                  editfn =>  delete_char
                }
              );                                my _ =
        mt::note_editfn  delete_char__editfn;


        fun delete_backward_char (arg:          mt::Editfn_In)
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else    
                    point -> { row, col };

                    if (col > 0)
                        #
                        col = col - 1;

                        point = { row, col };

                        delete_one_char (textlines, point, mark);                                                               # Code shared with delete_char.
                        #
                    elif (row > 1)                                                                                              # Delete preceding newline, appending current line to previous line.
                        #
                        point -> { row, col };

                        line_key2 =        row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).
                        line_key1 =  line_key2 - 1;                                                                             # 

                        result =    case (nl::find (textlines, line_key1), nl::find (textlines, line_key2))
                                        #
                                        (THE textline1, THE textline2)
                                            =>
                                            {   line1 = mt::visible_line textline1;
                                                line2 = mt::visible_line textline2;

                                                chomped_line1 =  string::chomp  line1;
                                                #
                                                (string::expand_tabs_and_control_chars
                                                  {
                                                    utf8text   =>  chomped_line1,
                                                    startcol   =>  0,
                                                    screencol1 =>  col,
                                                    screencol2 => -1,                                                           # Don't-care.
                                                    utf8byte   => -1                                                            # Don't-care.
                                                  })
                                                  ->
                                                  { screentext_length_in_screencols:            Int,
                                                    ...
                                                  };

                                                line12 = string::cat [ chomped_line1, line2 ];                                  # Prepend line1 (sans newline) to line2 to produce replacement for the pair of them.

                                                line12 = mt::MONOLINE { string =>  line12,
                                                                        prefix =>  NULL
                                                                      };

                                                updated_textlines                                                               # First remove existing two lines -- nl::set does NOT remove any previous line at that key.
                                                    =
                                                    {   updated_textlines = nl::remove (textlines,         line_key1);
                                                        updated_textlines = nl::remove (updated_textlines, line_key1);

                                                        updated_textlines;
                                                    }   
                                                    except _ = textlines;                                                       # This will happen if there is no line 'line_key' in textlines.

                                                updated_textlines                                                               # Now insert updated line.
                                                    =
                                                    nl::set (updated_textlines, line_key1, line12);

                                                WORK  [ mt::TEXTLINES updated_textlines,
                                                        mt::POINT { row => row - 1, col => screentext_length_in_screencols }    # Position cursor at end of previous line1 -- start of contents of merged-in former line2.
                                                      ];
                                            };

                                        _  => FAIL "<???>";                                                                     # Should maybe think harder about how/if this case can happen and if it can, what we should be doing. XXX SUCKO FIXME.
                                    esac;

                        result;
                    else
                        FAIL "No preceding char in line to delete";                                                             # Fail: No preceding char to delete in line.
                    fi;
                fi;
            };
        delete_backward_char__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "delete_backward_char",
                  doc    =>  "Delete char to left of point (cursor).",
                  args   =>  [],
                  editfn =>  delete_backward_char
                }
              );                                my _ =
        mt::note_editfn  delete_backward_char__editfn;


        fun list_mills (arg:                            mt::Editfn_In)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                WORK [ ];                                                                                               # 
            };
        list_mills__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "list_mills",
                  doc    =>  "List running mills in new pane.",
                  args   =>  [],
                  editfn =>  list_mills
                }
              );                                my _ =
        mt::note_editfn  list_mills__editfn;


        fun kill_mill (arg:                             mt::Editfn_In)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };


                WORK [ ];                                                                                               # 
            };
        kill_mill__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "kill_mill",
                  doc    =>  "Kill mill underlying current pane.",
                  args   =>  [],
                  editfn =>  kill_mill
                }
              );                                my _ =
        mt::note_editfn  kill_mill__editfn;


        fun save_some_mills (arg:                       mt::Editfn_In)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };


                WORK [ ];                                                                                               # 
            };
        save_some_mills__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "save_some_mills",
                  doc    =>  "Save state of any dirty mills with savefiles.",
                  args   =>  [],
                  editfn =>  save_some_mills
                }
              );                                my _ =
        mt::note_editfn  save_some_mills__editfn;


        fun save_mills_kill_mythryl (arg:               mt::Editfn_In)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                widget_to_guiboss.g.shut_down_guiboss ();

                WORK [ ];                                                                                               # 
            };
        save_mills_kill_mythryl__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "save_mills_kill_mythryl",
                  doc    =>  "Save state of all running mills then exit.",
                  args   =>  [],
                  editfn =>  save_mills_kill_mythryl
                }
              );                                my _ =
        mt::note_editfn  save_mills_kill_mythryl__editfn;


        fun self_insert_command (arg:           mt::Editfn_In)
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    point -> { row, col };

                    line_key = row;                                                                                     # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    text =  mt::findline (textlines, line_key);

                    chomped_text =  string::chomp  text;

                    (string::expand_tabs_and_control_chars
                      {
                        utf8text        =>  chomped_text,
                        startcol        =>  0,
                        screencol1      =>  col,
                        screencol2      => -1,                                                                          # Don't-care.
                        utf8byte        => -1                                                                           # Don't-care.
                      })
                      ->
                      { screentext_length_in_screencols:        Int,
                        screencol1_byteoffset_in_utf8text:      Int,
                        ...
                      };

                    if (col >= screentext_length_in_screencols)
                        #
# XXX SUCKO FIXME: TBD 
                        WORK [ ];                                                                                       # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either. (emacs deletes the end-of-line newline here, but I prefer to have only kill_line do that.)
                    else
                                                                                                                        # Cursor is on an existing char, possibly a multibyte utf8 char.  Excise it by replacing the line with the concatenation of the substrings preceding and following the char.
                        text_before_point
                            =
                            string::substring
                              (
                                text,                                                                                   # String from which to extract substring.
                                0,                                                                                      # The substring we want starts at offset 0.
                                screencol1_byteoffset_in_utf8text                                                       # The substring we want runs to location of point.  Treating cursor offset as length works (only) because we're starting substring at offset zero.
                              );

                        text_beyond_point
                            =
                            string::extract
                              (
                                text,                                                                                   # String from which to extract substring.
                                screencol1_byteoffset_in_utf8text,                                                      # Substring starts at the byte(s) under the cursor.  (Cursor will mark multiple bytes only if it is on a multibyte utf8 char.)
                                NULL                                                                                    # Substring runs to end of 'text'.
                              );

                        repeat_factor
                            =
                            case numeric_prefix
                                #
                                THE repeat_factor =>  max (1, repeat_factor);
                                NULL              =>  1;
                            esac;

                        updated_text    =  string::cat [ text_before_point,
                                                         string::repeat (keystring, repeat_factor),
                                                         text_beyond_point
                                                       ];

                        updated_text    =  mt::MONOLINE   { string =>  updated_text,
                                                            prefix =>  NULL
                                                          };

                        updated_textlines                                                                               # First remove existing line -- nl::set does NOT remove any previous line at that key.
                            =
                            (nl::remove (textlines, line_key))
                            except _ = textlines;                                                                       # This will happen if there is no line 'line_key' in textlines.

                        updated_textlines                                                                               # Now insert updated line.
                            =
                            nl::set (updated_textlines, line_key, updated_text);

                        point = { row, col => col + repeat_factor };                                                    # XXX BUGGO FIXME This will not leave cursor on right screen column if 'keystring' contained TAB, say.

                        WORK  [ mt::TEXTLINES updated_textlines,
                                mt::POINT point
                              ];
                    fi;         
                fi;
            };
        self_insert_command__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "self_insert_command",
                  doc    =>  "Insert keystroke at point (cursor).",
                  args   =>  [],
                  editfn =>  self_insert_command
                }
              );                                my _ =
        mt::note_editfn  self_insert_command__editfn;


        fun quoted_insert (arg:         mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::QUOTE_NEXT self_insert_command__editfn
                          ];
                fi;
            };
        quoted_insert__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "quoted_insert",
                  doc    =>  "Insert next keystroke literally at cursor, no matter what it is.",
                  args   =>  [],
                  editfn =>  quoted_insert
                }
              );                                my _ =
        mt::note_editfn  quoted_insert__editfn;


        fun point_to_register' (arg:    mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "point_to_register'/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::MODELINE_MESSAGE "point_to_register unimplemented"
                          ];
                fi;
            };
        point_to_register'__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "point_to_register'",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  point_to_register'
                }
              );
        # NB: We deliberately do NOT register point_to_register'__editfn -- it is purely internal.
        fun point_to_register (arg:     mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "point_to_register/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::QUOTE_NEXT point_to_register'__editfn                                                   # This will result in  point_to_register'  being called with 'keystring' set to next char typed by user.
                          ];
                fi;
            };
        point_to_register__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "point_to_register",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  point_to_register
                }
              );                                my _ =
        mt::note_editfn  point_to_register__editfn;

        fun insert_register' (arg:      mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "insert_register'/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::MODELINE_MESSAGE "point_to_register unimplemented"
                          ];
                fi;
            };
        insert_register'__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "insert_register'",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  insert_register'
                }
              );
        # NB: We deliberately do NOT register insert_register'__editfn -- it is purely internal.
        fun insert_register (arg:       mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "insert_register/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::QUOTE_NEXT insert_register'__editfn                                                     # This will result in  insert_register'  being called with 'keystring' set to next char typed by user.
                          ];
                fi;
            };
        insert_register__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "insert_register",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  insert_register
                }
              );                                my _ =
        mt::note_editfn  insert_register__editfn;

        fun jump_to_register' (arg:     mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "jump_to_register'/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::MODELINE_MESSAGE "point_to_register unimplemented"
                          ];
                fi;
            };
        jump_to_register'__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "jump_to_register'",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  jump_to_register'
                }
              );
        # NB: We deliberately do NOT register jump_to_register'__editfn -- it is purely internal.
        fun jump_to_register (arg:      mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "jump_to_register/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::QUOTE_NEXT jump_to_register'__editfn                                                    # This will result in  jump_to_register'  being called with 'keystring' set to next char typed by user.
                          ];
                fi;
            };
        jump_to_register__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "jump_to_register",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  jump_to_register
                }
              );                                my _ =
        mt::note_editfn  jump_to_register__editfn;

        fun kill_rectangle (arg:        mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "kill_rectangle/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [  mt::MODELINE_MESSAGE "kill_rectangle unimplemented"
                          ];
                fi;
            };
        kill_rectangle__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "kill_rectangle",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  kill_rectangle
                }
              );                                my _ =
        mt::note_editfn  kill_rectangle__editfn;

        fun copy_to_register' (arg:     mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "copy_to_register'/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::MODELINE_MESSAGE "point_to_register unimplemented"
                          ];
                fi;
            };
        copy_to_register'__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "copy_to_register'",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  copy_to_register'
                }
              );
        # NB: We deliberately do NOT register copy_to_register'__editfn -- it is purely internal.
        fun copy_to_register (arg:      mt::Editfn_In)
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

nb {. sprintf "copy_to_register/AAA --fundamental-mode.pkg"; }; 
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    WORK  [ mt::QUOTE_NEXT copy_to_register'__editfn                                                    # This will result in  copy_to_register'  being called with 'keystring' set to next char typed by user.
                          ];
                fi;
            };
        copy_to_register__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "copy_to_register",
                  doc    =>  "Save point (cursor) in register.",
                  args   =>  [],
                  editfn =>  copy_to_register
                }
              );                                my _ =
        mt::note_editfn  copy_to_register__editfn;

        fun newline             (arg:           mt::Editfn_In)                                                          # Split line at cursor, leave cursor at start of new line.
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    point -> { row, col };

                    line_key = row;                                                                                     # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    text =  mt::findline (textlines, line_key);

                    chomped_text =  string::chomp  text;

                    (string::expand_tabs_and_control_chars
                      {
                        utf8text        =>  chomped_text,
                        startcol        =>  0,
                        screencol1      =>  col,
                        screencol2      => -1,                                                                          # Don't-care.
                        utf8byte        => -1                                                                           # Don't-care.
                      })
                      ->
                      { screentext_length_in_screencols:        Int,
                        screencol1_byteoffset_in_utf8text:      Int,
                        ...
                      };

                    if (col >= screentext_length_in_screencols)
                        #
# XXX SUCKO FIXME: TBD 
                        WORK [ ];                                                                                       # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either. (emacs deletes the end-of-line newline here, but I prefer to have only kill_line do that.)
                    else
                                                                                                                        # Cursor is on an existing char, possibly a multibyte utf8 char.  Excise it by replacing the line with the concatenation of the substrings preceding and following the char.
                        text_before_point
                            =
                            string::substring
                              (
                                text,                                                                                   # String from which to extract substring.
                                0,                                                                                      # The substring we want starts at offset 0.
                                screencol1_byteoffset_in_utf8text                                                       # The substring we want runs to location of cursor.  Treating cursor offset as length works (only) because we're starting substring at offset zero.
                              );

                        text_beyond_point
                            =
                            string::extract
                              (
                                text,                                                                                   # String from which to extract substring.
                                screencol1_byteoffset_in_utf8text,                                                      # Substring starts at the byte(s) under the cursor.  (Cursor will mark multiple bytes only if it is on a multibyte utf8 char.)
                                NULL                                                                                    # Substring runs to end of 'text'.
                              );

                        # We're splitting the current line into two.
                        # Synthesize those two lines:
                        #
                        line1 =  string::cat [ text_before_point, "\n" ];
                        line2 =                text_beyond_point;

                        line1 = mt::MONOLINE  { string => line1,  prefix =>  NULL };
                        line2 = mt::MONOLINE  { string => line2,  prefix =>  NULL };

                        updated_textlines                                                                               # First remove existing line -- nl::set does NOT remove any previous line at that key.
                            =
                            (nl::remove (textlines, line_key))
                            except _ = textlines;                                                                       # This will happen if there is no line 'line_key' in textlines.

                        updated_textlines = nl::set (updated_textlines, line_key, line2);                               # Now insert the two new lines.
                        updated_textlines = nl::set (updated_textlines, line_key, line1);                               # 

                        WORK  [ mt::TEXTLINES updated_textlines,
                                mt::POINT { row => row + 1, col => 0 }                                                  # Leave cursor at start of second line.
                              ];
                    fi;         
                fi;
            };
        newline__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "newline",
                  doc    =>  "Split line at point (cursor), leave point at start of new line.",
                  args   =>  [],
                  editfn =>  newline
                }
              );                                my _ =
        mt::note_editfn  newline__editfn;


        fun kill_whole_line     (arg:           mt::Editfn_In)                                                          # Remove complete line under cursor, leave cursor at same column on next line.
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    point -> { row, col };

                    line_key = row;                                                                                     # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    oldline = mt::findline (textlines, line_key);

                    updated_textlines                                                                                   # Remove line.
                        =
                        (nl::remove (textlines, line_key))
                        except _ = textlines;                                                                           # This will happen if there is no line 'line_key' in textlines.

                    mill_to_millboss
                        ->
                        mt::MILL_TO_MILLBOSS  eb;

                    eb.set_cutbuffer_contents (ct::WHOLELINE oldline);

                    WORK  [ mt::TEXTLINES updated_textlines ];
                fi;
            };
        kill_whole_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "kill_whole_line",
                  doc    =>  "Remove complete line under point (cursor), leave point at same column on next line.",
                  args   =>  [],
                  editfn =>  kill_whole_line
                }
              );                                my _ =
        mt::note_editfn  kill_whole_line__editfn;



        fun yank                (arg:           mt::Editfn_In)                                                          # Insert contents of cutbuffer at cursor. Insertion style depends on cutbuffer contents type.
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    point -> { row, col };

                    line_key = row;                                                                                     # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    mill_to_millboss
                        ->
                        mt::MILL_TO_MILLBOSS  eb;

                    case (eb.get_cutbuffer_contents())
                        #
                        ct::PARTLINE  text_to_insert                                                                    # Used for vanilla cut operations confined to a single line.
                            =>
                            {   (tlj::insert_string { text_to_insert, point, textlines })
                                  ->
                                  { updated_textlines,  point_after_inserted_text };

                                WORK  [ mt::TEXTLINES updated_textlines,
                                        mt::POINT     point_after_inserted_text,
                                        mt::MARK      NULL,
                                        mt::LASTMARK (THE point)
                                      ];
                            };

                        ct::WHOLELINE (line: String)                                                                    # Used for special cut operations which cut complete lines even if point (cursor) is in middle of line.
                            =>
                            {   line   = mt::MONOLINE { string => line,
                                                        prefix =>  NULL
                                                      };
                                #
                                updated_textlines
                                    =
                                    nl::set (textlines, line_key, line);

                                WORK  [ mt::TEXTLINES updated_textlines ];
                            };

                        ct::MULTILINE lines_to_insert                                                                   # Used for vanilla cut operations which happen to span more than one line.
                            =>
                            {   (tlj::insert_lines { lines_to_insert, point, textlines })
                                  ->
                                  { updated_textlines, point_after_inserted_text };

                                WORK  [ mt::TEXTLINES updated_textlines,
                                        mt::MARK      NULL,
                                        mt::LASTMARK (THE point),
                                        mt::POINT     point_after_inserted_text
                                      ];
                            };
                    esac;
                fi;
            };
        yank__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "yank",
                  doc    =>  "Insert contents of cutbuffer at point (cursor). Insertion style depends on cutbuffer contents type.",
                  args   =>  [],
                  editfn =>  yank
                }
              );                                my _ =
        mt::note_editfn  yank__editfn;


        fun set_mark_command    (arg:           mt::Editfn_In)                                                  # Insert contents of cutbuffer at cursor. Insertion style depends on cutbuffer contents type.
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                WORK  [ mt::MARK (THE point) ];
            };
        set_mark_command__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "set_mark_command",
                  doc    =>  "Set 'mark' to location of point (cursor).",
                  args   =>  [],
                  editfn =>  set_mark_command
                }
              );                                my _ =
        mt::note_editfn  set_mark_command__editfn;


        fun keyboard_quit       (arg:           mt::Editfn_In)                                                  # This is emacs' stop-everything command.  For the moment it just clears the mark.
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                WORK  [ mt::MARK NULL,
                        mt::QUIT                                                                                # Special hack just for keyboard_quit which instructs textpane.pkg to reset all ephemeral state etc.
                      ];
            };
        keyboard_quit__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "keyboard_quit",
                  doc    =>  "Stop everything, clear mark, reset to stable quiescient state.",
                  args   =>  [],
                  editfn =>  keyboard_quit
                }
              );                                my _ =
        mt::note_editfn  keyboard_quit__editfn;


        fun kill_line   (arg:                   mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    mill_to_millboss
                        ->
                        mt::MILL_TO_MILLBOSS  eb;

                    #
                    point' = tlj::normalize_point (point, textlines);                                           # The column for 'point' may be somewhere odd like in the middle of a tabs, so start by deriving normalized version.

                    line_key = point'.row;                                                                      # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    text =  mt::findline (textlines, line_key);

                    chomped_text =  string::chomp  text;

                    (string::expand_tabs_and_control_chars                                                      # Map screencols col1,col2 to byteoffsets in chomped_text.
                      {
                        utf8text        =>  chomped_text,
                        startcol        =>  0,
                        screencol1      =>  point'.col,
                        screencol2      => -1,                                                                  # Don't-care. 
                        utf8byte        => -1                                                                   # Don't-care. 
                      })
                      ->
                      { screencol1_byteoffset_in_utf8text => region_start,
                        ...
                      };


                    utf8_len_in_bytes = string::length_in_bytes  chomped_text;                                  # 

                    text_before_region =  string::substring (chomped_text, 0, region_start      );
                    text_within_region =  string::extract   (chomped_text,    region_start, NULL);

                    if (string::length_in_bytes text_within_region  >  0)                                       # Delete (and move to cutbuffer) ending part of line starting at point.
                        #
                        eb.set_cutbuffer_contents (ct::PARTLINE text_within_region);

                        updated_line =  text_before_region
                                     +  (chomped_text==text ?? "" :: "\n");                                     # Add back terminal newline, if original line had one.

                        updated_line =  mt::MONOLINE  { string =>  updated_line,
                                                        prefix =>  NULL
                                                      };

                        textlines =  nl::remove (textlines, line_key);
                        textlines =  nl::set    (textlines, line_key, updated_line);

                        WORK  [ mt::TEXTLINES textlines
                              ];
                    else                                                                                        # Cursor is at end of line: Join current line to next line.
                        max_key =   case (nl::max_key  textlines)
                                        #
                                        THE max_key => max_key;
                                        NULL        => 0;                                                       # We don't expect this.
                                    esac;

                        if (max_key > point'.row)                                                               # If we're not on the last line...
                            #
                            text2 = mt::findline (textlines, line_key + 1);

                            updated_line = chomped_text + text2;

                            updated_line =  mt::MONOLINE  { string =>  updated_line,
                                                            prefix =>  NULL
                                                          };

                            textlines =  nl::remove (textlines, line_key);
                            textlines =  nl::remove (textlines, line_key);
                            textlines =  nl::set    (textlines, line_key, updated_line);

                            eb.set_cutbuffer_contents (ct::MULTILINE [ "", "" ]);                               # Empirically, this works to effectively put a single newline in the cutbuffer.  I should read and document the code to figure out why. :-)

                            WORK  [ mt::TEXTLINES textlines
                                  ];

                        elif (chomped_text != text)                                                             # ... else if we're on the last line and chopping off its terminal newline...
                            #
                            updated_line = chomped_text;

                            updated_line =  mt::MONOLINE  { string =>  updated_line,
                                                            prefix =>  NULL
                                                          };

                            textlines =  nl::remove (textlines, line_key);
                            textlines =  nl::set    (textlines, line_key, updated_line);

                            eb.set_cutbuffer_contents (ct::MULTILINE [ "", "" ]);

                            WORK  [ mt::TEXTLINES textlines
                                  ];
                        else                                                                                    # ... else we're at the end of the last line in the buffer which already lacks a newline, so nothing to do.
                            WORK  [ 
                                  ];
                        fi;
                    fi;
                fi;
            };
        kill_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "kill_line",
                  doc    =>  "Kill to end of line. If at end of line, delete end of line. TBD: With numeric prefix, kill multiple lines starting at point.",
                  args   =>  [],
                  editfn =>  kill_line
                }
              );                                my _ =
        mt::note_editfn  kill_line__editfn;


        fun transpose_chars (arg:                       mt::Editfn_In)                                          # Interchange char under cursor with preceding char on line.  We treat the end-of-line cases differently than emacs because I don't like the emacs handling.  -- 2015-07-17 CrT
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    point =  tlj::normalize_point (point, textlines);                                                   # Normalize point because it might be in the middle of a multicolumn tab or other control char.

                    point -> { row, col };
                    #
                    line_key = row;                                                                                     # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                    case (nl::find (textlines, line_key))
                        #
                        THE textline
                            =>
                            {   text         =  mt::visible_line  textline;
                                chomped_text =  string::chomp     text;

                                my (col1, col2)
                                    =
                                    if (col > 0)
                                        #
                                        point' = { row => point.row,                                                    # Because 'point' is normalized, point.col-1 is guaranteed to be somewhere on the preceding char.
                                                   col => point.col - 1
                                                 };
                                        point' = tlj::normalize_point (point', textlines);                              # Now point'.col is guaranteed to be at the start of the preceding char.

                                        (point'.col, col);                                                              # Return (first_column_of_preceding_char, first_column_of_cursor_char)
                                    else
                                        (string::expand_tabs_and_control_chars                                          # Figure length-in-screen-cols of char under cursor.
                                          {
                                            utf8text    =>  chomped_text,
                                            startcol    =>  0,
                                            screencol1  =>  point.col,
                                            screencol2  => -1,                                                          # Don't-care.
                                            utf8byte    => -1                                                           # Don't-care.
                                          })
                                          ->
                                          { screencol1_colcount_on_screen:      Int,
                                            ...
                                          };
                                        (col, col + screencol1_colcount_on_screen);                                     # Return (first_column_of_cursor_char, first_column_of_next_char), since there is no preceding char.  A reasonable alternative would be to do nothing in this case.
                                    fi;

                                (string::expand_tabs_and_control_chars                                                  # We call  expand_tabs_and_control_chars  twice, first time is to get actual length-in-screen-cols of line.
                                  {                                                                                     # We can't combine that call with next because expand_tabs_and_control_chars() will blank-pad output as needed to make screencol1/screencol2 valid offsets.
                                    utf8text    =>  chomped_text,
                                    startcol    =>  0,
                                    screencol1  => -1,                                                                  # Don't-care.
                                    screencol2  => -1,                                                                  # Don't-care.
                                    utf8byte    => -1                                                                   # Don't-care.
                                  })
                                  ->
                                  { screentext_length_in_screencols:    Int,
                                    ...
                                  };

                                (string::expand_tabs_and_control_chars                                                  # Now find out chomped_text byte offsets of our two chars to be transposed, along with length-in-bytes for each.
                                  {
                                    utf8text    =>  chomped_text,
                                    startcol    =>  0,
                                    screencol1  =>  col1,
                                    screencol2  =>  col2,
                                    utf8byte    => -1                                                                   # Don't-care.
                                  })
                                  ->
                                  { screencol1_byteoffset_in_utf8text:  Int,
                                    screencol1_bytescount_in_utf8text:  Int,
                                    #
                                    screencol2_byteoffset_in_utf8text:  Int,
                                    screencol2_bytescount_in_utf8text:  Int,
                                    ...
                                  };

                                if (col2 >= screentext_length_in_screencols)
                                    #
                                    WORK [ ];                                                                           # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either.
                                else
                                                                                                                        # Cursor is on an existing char, possibly a multibyte utf8 char.  Excise it by replacing the line with the concatenation of the substrings preceding and following the char.
                                    text_before_charpair
                                        =
                                        string::substring
                                          (
                                            text,                                                                       # String from which to extract substring.
                                            0,                                                                          # The substring we want starts at offset 0.
                                            screencol1_byteoffset_in_utf8text                                           # The substring we want runs to location of cursor.  Treating cursor offset as length works (only) because we're starting substring at offset zero.
                                          );

                                    text_for_char1
                                        =
                                        string::substring
                                          (
                                            text,                                                                       # 
                                            screencol1_byteoffset_in_utf8text,                                          # 
                                            screencol1_bytescount_in_utf8text                                           # 
                                          );

                                    text_for_char2
                                        =
                                        string::substring
                                          (
                                            text,                                                                       # 
                                            screencol2_byteoffset_in_utf8text,                                          # 
                                            screencol2_bytescount_in_utf8text                                           # 
                                          );

                                    text_beyond_charpair
                                        =
                                        string::extract
                                          (
                                            text,                                                                       # String from which to extract substring.
                                            screencol2_byteoffset_in_utf8text + screencol2_bytescount_in_utf8text,      # Substring starts immediately after the byte(s) under the cursor.  (Cursor will mark multiple bytes only if it is on a multibyte utf8 char.)
                                            NULL                                                                        # Substring runs to end of 'text'.
                                          );

                                    updated_text        =  string::cat  [ text_before_charpair,
                                                                          text_for_char2,
                                                                          text_for_char1,
                                                                          text_beyond_charpair
                                                                        ];

                                    updated_text        = mt::MONOLINE  { string =>  updated_text,
                                                                          prefix =>  NULL
                                                                        };

                                    updated_textlines                                                                   # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                        =
                                        (nl::remove (textlines, line_key))
                                        except _ = textlines;                                                           # This will happen if there is no line 'line_key' in textlines.

                                    updated_textlines                                                                   # Now insert updated line.
                                        =
                                        nl::set (updated_textlines, line_key, updated_text);


                                    col' =  {                                                                           # We want to leave cursor one char to the right of the interchanged chars.  This is nontrivial if one of them was a tab (say).
                                                text_before_updated_cursor
                                                    =
                                                    string::cat [ text_before_charpair,
                                                                  text_for_char2,
                                                                  text_for_char1
                                                                ];

                                                (string::expand_tabs_and_control_chars                                  # Now find out chomped_text byte offsets of our two chars to be transposed, along with length-in-bytes for each.
                                                  {
                                                    utf8text    =>  text_before_updated_cursor,
                                                    startcol    =>  0,
                                                    screencol1  => -1,                                                  # Don't-care.
                                                    screencol2  => -1,                                                  # Don't-care.
                                                    utf8byte    => -1                                                   # Don't-care.
                                                  })
                                                  ->
                                                  { screentext_length_in_screencols:    Int,
                                                    ...
                                                  };

                                                screentext_length_in_screencols;
                                            };

                                    WORK  [ mt::TEXTLINES updated_textlines,
                                            mt::POINT { row, col => col' }                                              # Move the cursor one char to right.
                                          ];
                                fi;     
                            };

                        NULL     => WORK [ ];                                                                                   # Cursor is on non-existent line.  Don't fail, but don't do anything either.
                    esac;
                fi;
            };
        transpose_chars__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "transpose_chars",
                  doc    =>  "Interchange current and previous char.",
                  args   =>  [],
                  editfn =>  transpose_chars
                }
              );                                my _ =
        mt::note_editfn  transpose_chars__editfn;


        fun exchange_point_and_mark (arg:               mt::Editfn_In)                                          # 
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mark   =    case (mark, lastmark)
                                #
                                (THE _, _) =>     mark;                                                         # Use 'mark' if it is set.
                                _          => lastmark;                                                         # Use 'lastmark' otherwise.  (In this case lastmark will always be set unless no mark has ever been set in this buffer.)
                            esac;

                result =    case mark
                                #
                                NULL => FAIL "Mark is not set";                                                 # Can't exchange point and mark when mark isn't set!

                                THE mark
                                    =>
                                    if (mark.row <  point.row
                                    or (mark.row == point.row  and  mark.col < point.col))
                                        #
                                        WORK  [ mt::MARK  (THE { row => point.row, col => point.col - 1 }),
                                                mt::POINT mark
                                              ];
                                    else                                                                        # mark > point
                                        WORK  [ mt::MARK   (THE point),
                                                mt::POINT  { row => mark.row, col => mark.col + 1 }
                                              ];
                                    fi;
                            esac;

                result;
            };
        exchange_point_and_mark__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "exchange_point_and_mark",
                  doc    =>  "Exchange mark and point (cursor) if mark is set.  Fail if mark is not set.",
                  args   =>  [],
                  editfn =>  exchange_point_and_mark
                }
              );                                my _ =
        mt::note_editfn  exchange_point_and_mark__editfn;


        fun beginning_of_buffer (arg:           mt::Editfn_In)                                                  # 
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                result =    WORK  [ mt::POINT { row => 0, col => 0 }
                                  ];

                result;
            };
        beginning_of_buffer__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "beginning_of_buffer",
                  doc    =>  "Move point to first char of first line of buffer.",
                  args   =>  [],
                  editfn =>  beginning_of_buffer
                }
              );                                my _ =
        mt::note_editfn  beginning_of_buffer__editfn;


        fun end_of_buffer (arg:                         mt::Editfn_In)                                          # Move 'point' to end of buffer.  That means last line in buffer, just past last char (other than newline).
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                row =   case (nl::max_key textlines)                                                            # Finding number of last row is fairly easy.
                            #
                            THE row => row;
                            NULL    => 0;                                                                       # Shouldn't happen.
                        esac;
                                                                                                                # Now we find screencol of last char in line. That's harder.  Following code is duplicated from move_end_of_line(), probably we should move it into a shared fn.


                line =  mt::findline (textlines, row);                                                          # Get last line.

                chomped_line =  string::chomp  line;                                                            # Drop terminal newline if any.

                (string::expand_tabs_and_control_chars                                                          # Count number of screencols in last line.
                  {
                    utf8text    =>  chomped_line,
                    startcol    =>  0,
                    screencol1  => -1,                                                                          # Don't care.
                    screencol2  => -1,                                                                          # Don't care.
                    utf8byte    => -1                                                                           # Don't care.
                  })
                  ->
                  { screentext_length_in_screencols,
                    ...
                  };

                col    =  screentext_length_in_screencols;

                col    =  max (0, col - 1);                                                                     # Is this right?  XXX QUERO FIXME

                result =  WORK  [ mt::POINT { row, col }
                                ];
                result;
            };
        end_of_buffer__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "end_of_buffer",
                  doc    =>  "Move point to last char of last line of buffer.",
                  args   =>  [],
                  editfn =>  end_of_buffer
                }
              );                                my _ =
        mt::note_editfn  end_of_buffer__editfn;

        stipulate
            fun split_pane_vertically_or_horizontally
                  (
                    arg:                                mt::Editfn_In,
                    xirow_or_xicol                                                                              # gt::XI_ROW or gt::XI_COL, depending whether we're splitting horizontally or vertically.
                  )
                  :                                     mt::Editfn_Out
                =
                {   arg ->    { args:                   List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                                textlines:              mt::Textlines,
                                point:                  g2d::Point,                                             # As in Point_And_Mark.
                                mark:                   Null_Or(g2d::Point),                                    # 
                                lastmark:               Null_Or(g2d::Point),                                    # 
                                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.
                                readonly:               Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                                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.
                                edit_history:           mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                                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.
                                mill_id:                Id,                                                     # Id  of mill for which this editfn is being invoked.
                                to:                     Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                                widget_to_guiboss:      gt::Widget_To_Guiboss,                                  # 
                                mill_to_millboss:       mt::Mill_To_Millboss,
                                #
                                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.
                                #
                                mill_extension_state:   Crypt,
                                textpane_to_textmill:   mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                                mode_to_drawpane:       Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                                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.
                              };

                    textpane_to_textmill
                        ->
                        mt::TEXTPANE_TO_TEXTMILL  t2t;

                    t2t.app_to_mill
                        ->
                        mt::APP_TO_MILL  a2m;

                    a2m.pass_pane_guiplan to {.                                                                 # pass_pane_guiplan() synthesizes a guiplan for a pane which will display the state of textmill 'a2m'.
                        #                                                                                       # Ultimately this invokes  make_pane_guiplan'  in  src/lib/x-kit/widget/edit/make-textpane.pkg
                        pane_guiplan = #guiplan;

                        do_while_not {.                                                                         # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                            #                                                                                   # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                            get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                            install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                            (get_guipiths ())
                                ->
                                (gui_version, guipiths)
                                     #
                                     :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                                     ;

                            guipiths =  gtj::guipith_map (guipiths, options)
                                            where
                                                fun do_widget  (w: gt::Xi_Widget_Type):  gt::Xi_Widget_Type
                                                    =
                                                    case w
                                                        #
                                                        gt::XI_FRAME
                                                          { id:                 Id,
                                                            frame_widget:               gt::Xi_Widget_Type,                             # Widget which will draw the frame surround.
                                                            widget:                     gt::Xi_Widget_Type                              # Widget-tree to draw surrounded by frame.
                                                          }
                                                            =>
                                                            case frame_widget
                                                                #
                                                                gt::XI_WIDGET
                                                                  {
                                                                    widget_id:          Id,
                                                                    widget_layout_hint: gt::Widget_Layout_Hint,
                                                                    doc:                        String                                  # Debugging support: Allow XI_WIDGETs to be distinguishable for debug-display purposes.
                                                                  }
                                                                    =>
                                                                    if (not (same_id (widget_id, pane_id)))
                                                                        #
                                                                        w;
                                                                    else
                                                                        xirow_or_xicol                                          # gt::XI_ROW or gt::XI_COL, depending whether we're splitting horizontally or vertically.
                                                                          {
                                                                            id        =>  issue_unique_id (),
                                                                            #
                                                                            first_cut =>  NULL,
                                                                            widgets   =>  [ w,
                                                                                            gt::XI_GUIPLAN  pane_guiplan
                                                                                          ]
                                                                          };
                                                                    fi;


                                                                _ => w;
                                                            esac;

                                                        _ => w;
                                                    esac;

                                                options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                        #
                                                        : List( gtj::Guipith_Map_Option )
                                                        ;
                                            end;

                            install_updated_guipiths                                                                    # If this returns FALSE we'll loop and retry.
                                #
                                (gui_version, guipiths);
                        };                                                                                                      # do_while_not
                    };

                    result =  WORK  [ 
                                    ];
                    result;
                };
                                                                        
        herein

            fun split_pane_vertically (arg:                     mt::Editfn_In)                                          # Replace the current textpane by two textpanes half as high.
                :                                               mt::Editfn_Out                                          # NB: emacs called panes "windows", which was a mistake because when X came along emacs had to call windows "frames".  We call panes "panes" and windows "windows". :-)
                =
                split_pane_vertically_or_horizontally (arg, gt::XI_COL);
            #
            split_pane_vertically__editfn
                =
                mt::EDITFN (
                  mt::PLAIN_EDITFN
                    {
                      name       =>  "split_pane_vertically",
                      doc        =>  "Replace current textpane by two textpanes half as high.",
                      args       =>  [],
                      editfn =>  split_pane_vertically
                    }
                  );                            my _ =
            mt::note_editfn  split_pane_vertically__editfn;

            fun split_pane_horizontally (arg:           mt::Editfn_In)                                          # Replace the current textpane by two textpanes half as high
                :                                       mt::Editfn_Out
                =
                split_pane_vertically_or_horizontally (arg, gt::XI_ROW);
            #
            split_pane_horizontally__editfn
                =
                mt::EDITFN (
                  mt::PLAIN_EDITFN
                    {
                      name       =>  "split_pane_horizontally",
                      doc        =>  "Replace current textpane by two textpanes half as wide.",
                      args       =>  [],
                      editfn =>  split_pane_horizontally
                    }
                  );                            my _ =
            mt::note_editfn  split_pane_horizontally__editfn;
        end;


        fun rotate_panepair (arg:                       mt::Editfn_In)                                          # Do a 90-degree clockwise rotation of the current panepair: If active window was on top, it winds up at right. If at right, on bottom. If on bottom, at left. If at left, on top.
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.

                          };

                done = REF FALSE;

                do_while_not {.                                                                                 # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                    #                                                                                           # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                    get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                    install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                    (get_guipiths ())
                        ->
                        (gui_version, guipiths)
                             #
                             :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                             ;

                    guipiths =  gtj::guipith_map (guipiths, options)
                                    where
                                        fun is_us (widget: gt::Xi_Widget_Type):  Bool                                   # 
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_FRAME { frame_widget => gt::XI_WIDGET { widget_id, ... }, ... }  #
                                                    =>                                                                  #
                                                    if   *done                                          FALSE;          # Do only one substitution.  Without this check, we'll substitute recursively all the way up the tree, leaving only one pane.  (Which is the emacs semantics.)
                                                    elif (same_id (widget_id, pane_id))  done:= TRUE;   TRUE;           # 
                                                    else                                                FALSE;          #
                                                    fi;
                                                                                                                        #
                                                _   =>                                                  FALSE;          #
                                            esac;                                                                       #


                                        fun invert (first_cut: Null_Or(Float))                                          # Replace f by (1.0-f).
                                            =
                                            case first_cut
                                                #
                                                THE f =>  THE (1.0 - f);
                                                NULL  =>  NULL;
                                            esac;

                                        fun do_widget  (widget:  gt::Xi_Widget_Type):  gt::Xi_Widget_Type               #
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_ROW                                                              # If we've  found a ROW...      (Currently we can't just write (gt::XI_ROW | gt::XI_COL) here, we have to duplicate the pattern.)
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      gt::XI_COL { id => issue_unique_id(),  widgets => [ topwidget, botwidget ],  first_cut                   };
                                                    elif (is_us botwidget)      gt::XI_COL { id => issue_unique_id(),  widgets => [ topwidget, botwidget ],  first_cut                   };
                                                    else                        widget;                                 # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                gt::XI_COL                                                              # ... or if we've found a COL.  
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      gt::XI_ROW { id => issue_unique_id(), widgets => [ botwidget, topwidget ], first_cut => invert first_cut };
                                                    elif (is_us botwidget)      gt::XI_ROW { id => issue_unique_id(), widgets => [ botwidget, topwidget ], first_cut => invert first_cut };
                                                    else                        widget;                                 # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                _   =>  widget;                                                         # 'widget' is not a ROW/COL, so leave it unchnaged.
                                            esac;

                                        options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                #
                                                : List( gtj::Guipith_Map_Option )
                                                ;
                                    end;

                        install_updated_guipiths                                                                        # If this returns FALSE we'll loop and retry.
                            #
                            (gui_version, guipiths);
                    };                                                                                                  # do_while_not


                result =  WORK  [ 
                                ];
                result;
            };
        rotate_panepair__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "rotate_panepair",
                  doc    =>  "Rotate current panepair by ninety degrees.",
                  args   =>  [],
                  editfn =>  rotate_panepair
                }
              );                                my _ =
        mt::note_editfn  rotate_panepair__editfn;



        fun delete_other_pane (arg:                     mt::Editfn_In)                                          # Opposite of split_pane_vertically_or_horizontally:  Replace ROW/COL containing widget with just widget.  We assume a binary tree -- each ROW or COL has two children. (That's what split_pane_horizontally_or_vertically will create.)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.

                          };

                done = REF FALSE;

                do_while_not {.                                                                                 # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                    #                                                                                           # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                    get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                    install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                    (get_guipiths ())
                        ->
                        (gui_version, guipiths)
                             #
                             :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                             ;

                    guipiths =  gtj::guipith_map (guipiths, options)
                                    where
                                        fun is_us (widget: gt::Xi_Widget_Type):  Bool                                   # 
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_FRAME { frame_widget => gt::XI_WIDGET { widget_id, ... }, ... }  #
                                                    =>                                                                  #
                                                    if   *done                                          FALSE;          # Do only one substitution.  Without this check, we'll substitute recursively all the way up the tree, leaving only one pane.  (Which is the emacs semantics.)
                                                    elif (same_id (widget_id, pane_id))  done:= TRUE;   TRUE;           # 
                                                    else                                                FALSE;          #
                                                    fi;
                                                                                                                        #
                                                _   =>                                                  FALSE;          #
                                            esac;                                                                       #



                                        fun do_widget  (widget:  gt::Xi_Widget_Type):  gt::Xi_Widget_Type               #
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_ROW                                                              # If we've  found a ROW...      (Currently we can't just write (gt::XI_ROW | gt::XI_COL) here, we have to duplicate the pattern.)
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      topwidget;                              # The first widget  in this ROW/COL is us, so replace ROW/COL with just us.
                                                    elif (is_us botwidget)      botwidget;                              # The second widget in this ROW/COL is us, so replace ROW/COL with just us.
                                                    else                           widget;                              # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                gt::XI_COL                                                              # ... or if we've found a COL.  
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      topwidget;                              # The first widget  in this ROW/COL is us, so replace ROW/COL with just us.
                                                    elif (is_us botwidget)      botwidget;                              # The second widget in this ROW/COL is us, so replace ROW/COL with just us.
                                                    else                           widget;                              # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                _   =>  widget;                                                         # 'widget' is not a ROW/COL, so leave it unchnaged.
                                            esac;

                                        options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                #
                                                : List( gtj::Guipith_Map_Option )
                                                ;
                                    end;

                        install_updated_guipiths                                                                        # If this returns FALSE we'll loop and retry.
                            #
                            (gui_version, guipiths);
                    };                                                                                                  # do_while_not


                result =  WORK  [ 
                                ];
                result;
            };
        delete_other_pane__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "delete_other_pane",
                  doc    =>  "Delete other pane in ROW/COL containing current textpane.",
                  args   =>  [],
                  editfn =>  delete_other_pane
                }
              );                                my _ =
        mt::note_editfn  delete_other_pane__editfn;



        fun enlarge_pane (arg:                          mt::Editfn_In)                                                  # Re-allocate space after doing a split_pane_vertically or split_pane_horizontally.
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                            # 
                            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.
                            readonly:                   Bool,                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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_while_not {.                                                                                         # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                    #                                                                                                   # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                    get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                    install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                    (get_guipiths ())
                        ->
                        (gui_version, guipiths)
                             #
                             :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                             ;

                    guipiths =  gtj::guipith_map (guipiths, options)
                                    where
                                        fun is_us (widget: gt::Xi_Widget_Type):  Bool                                   # 
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_FRAME { frame_widget => gt::XI_WIDGET { widget_id, ... }, ... }  #
                                                    =>                                                                  #
                                                    if (same_id (widget_id, pane_id))   TRUE;                           # 
                                                    else                                FALSE;                          #
                                                    fi;
                                                                                                                        #
                                                _   =>                                  FALSE;                          #
                                            esac;                                                                       #

                                        fun bump_cut
                                              (
                                                first_cut:              Null_Or(Float),                                 # Fraction of available pixels to assign to first widget.
                                                delta:                  Float,                                          # Amount to change first_cut by.
                                                we_are_first_widget:    Bool                                            #
                                              )
                                            =
                                            {   first_cut
                                                    =
                                                    case numeric_prefix
                                                        #
                                                        THE i =>    if we_are_first_widget
                                                                        #
                                                                        0.01 * (float::from_int i);                     # User specified ^U dd so take that as absolute fraction to assign to widget and convert from (0 -> 100) to (0.0 -> 1.0) scaling, and from int to float.
                                                                    else                         
                                                                        0.01 * (float::from_int (100-i));               # If user thinks he's specifying size of second widget, convert to first-widget view by subtracting from 100%.
                                                                    fi; 
                                                        NULL  =>                                                        #
                                                            case first_cut                                              # No numeric prefix, so if
                                                                #                                                       #
                                                                THE f => f;                                             # we already have a first_cut value, go ahead and use it, otherwise 
                                                                NULL  => 0.5;                                           # default to 0.5 (equal pixels distribution between two widgets in ROW/COL).
                                                            esac;
                                                    esac;

                                                first_cut = first_cut + delta;                                          # Make requested change.

                                                first_cut = if   (first_cut < 0.05)  0.05;                              # Do a little data validation. We don't want to assign zero pixels to a widget -- it would confuse the user -- so we arbitrarily require a minimum of 5% pixels.
                                                            elif (first_cut > 0.95)  0.95;
                                                            else                     first_cut;
                                                            fi;

                                                THE first_cut;
                                            };



                                        fun do_widget  (widget:  gt::Xi_Widget_Type):  gt::Xi_Widget_Type               #
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_ROW                                                              # If we've  found a ROW...      (Currently we can't just write (gt::XI_ROW | gt::XI_COL) here, we have to duplicate the pattern.)
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets as  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    {
                                                        first_cut
                                                            =
                                                            if   (is_us topwidget)  bump_cut (first_cut,  0.05, TRUE );
                                                            elif (is_us botwidget)  bump_cut (first_cut, -0.05, FALSE);
                                                            else                              first_cut;
                                                            fi;

                                                        gt::XI_ROW { id, first_cut, widgets };
                                                    };

                                                gt::XI_COL                                                              # ... or if we've found a COL.  
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets as  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    {
                                                        first_cut
                                                            =
                                                            if   (is_us topwidget)  bump_cut (first_cut,  0.05, TRUE );
                                                            elif (is_us botwidget)  bump_cut (first_cut, -0.05, FALSE);
                                                            else                              first_cut;
                                                            fi;

                                                        gt::XI_COL { id, first_cut, widgets };
                                                    };

                                                _   =>  widget;                                                         # 'widget' is not a ROW/COL, so leave it unchnaged.
                                            esac;

                                        options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                #
                                                : List( gtj::Guipith_Map_Option )
                                                ;
                                    end;

                        install_updated_guipiths                                                                        # If this returns FALSE we'll loop and retry.
                            #
                            (gui_version, guipiths);
                    };                                                                                                  # do_while_not


                result =  WORK  [ 
                                ];
                result;
            };
        enlarge_pane__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "enlarge_pane",
                  doc    =>  "Enlarge space by 5% pixelspace in ROW/COL assigned to current textpane. With PREFIX in 5-95, set as absolute percentage of available pixels.",
                  args   =>  [],
                  editfn =>  enlarge_pane
                }
              );                                my _ =
        mt::note_editfn  enlarge_pane__editfn;


        fun delete_this_pane (arg:                      mt::Editfn_In)                                          # Opposite of split_pane_vertically_or_horizontally:  Replace ROW/COL containing widget with sib widget.  We assume a binary tree -- each ROW or COL has two children. (That's what split_pane_horizontally_or_vertically will create.)
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.

                          };

                done = REF FALSE;

                do_while_not {.                                                                                 # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                    #                                                                                           # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                    get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                    install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                    (get_guipiths ())
                        ->
                        (gui_version, guipiths)
                             #
                             :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                             ;

                    guipiths =  gtj::guipith_map (guipiths, options)
                                    where
                                        fun is_us (widget: gt::Xi_Widget_Type):  Bool                                   # 
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_FRAME { frame_widget => gt::XI_WIDGET { widget_id, ... }, ... }  #
                                                    =>                                                                  #
                                                    if   *done                                          FALSE;          # Do only one substitution.  Without this check, we'll substitute recursively all the way up the tree, leaving only one pane.  (Which is the emacs semantics.)
                                                    elif (same_id (widget_id, pane_id))  done:= TRUE;   TRUE;           # 
                                                    else                                                FALSE;          #
                                                    fi;
                                                                                                                        #
                                                _   =>                                                  FALSE;          #
                                            esac;                                                                       #



                                        fun do_widget  (widget:  gt::Xi_Widget_Type):  gt::Xi_Widget_Type               #
                                            =                                                                           #
                                            case widget                                                                 #
                                                #                                                                       #
                                                gt::XI_ROW                                                              # If we've  found a ROW...      (Currently we can't just write (gt::XI_ROW | gt::XI_COL) here, we have to duplicate the pattern.)
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      botwidget;                              # The first widget  in this ROW/COL is us, so replace ROW/COL with our sib.
                                                    elif (is_us botwidget)      topwidget;                              # The second widget in this ROW/COL is us, so replace ROW/COL with our sib.
                                                    else                           widget;                              # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                gt::XI_COL                                                              # ... or if we've found a COL.  
                                                  {
                                                    id:         Id,
                                                    first_cut:  Null_Or(Float),
                                                    widgets =>  [ topwidget:    gt::Xi_Widget_Type,                     # As above, we handle only ROW and COLs with two widgets.
                                                                  botwidget:    gt::Xi_Widget_Type
                                                                ]
                                                  }
                                                    =>
                                                    if   (is_us topwidget)      botwidget;                              # The first widget  in this ROW/COL is us, so replace ROW/COL with our sib.
                                                    elif (is_us botwidget)      topwidget;                              # The second widget in this ROW/COL is us, so replace ROW/COL with our sib.
                                                    else                           widget;                              # Neither widget in this ROW/COL is us, so leave it unchanged.
                                                    fi;

                                                _   =>  widget;                                                         # 'widget' is not a ROW/COL, so leave it unchnaged.
                                            esac;

                                        options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                #
                                                : List( gtj::Guipith_Map_Option )
                                                ;
                                    end;

                        install_updated_guipiths                                                                        # If this returns FALSE we'll loop and retry.
                            #
                            (gui_version, guipiths);
                    };                                                                                                  # do_while_not


                result =  WORK  [ 
                                ];
                result;
            };
        delete_this_pane__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "delete_this_pane",
                  doc    =>  "Delete this pane in ROW/COL containing current textpane.",
                  args   =>  [],
                  editfn =>  delete_this_pane
                }
              );                                my _ =
        mt::note_editfn  delete_this_pane__editfn;


        fun kill_region (arg:                           mt::Editfn_In)                                                                                  # 
            :                                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                                                       # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                                                                     # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                                                            # 
                            lastmark:                   Null_Or(g2d::Point),                                                                            # 
                            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.
                            readonly:                   Bool,                                                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                                                               # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                                                             # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                                                                     # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                                                          # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                                                                       # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                                                               # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    mill_to_millboss
                        ->
                        mt::MILL_TO_MILLBOSS  eb;


                    result =    case mark
                                    #
                                    NULL => FAIL "Mark is not set";                                                                                     # Can't kill region when mark isn't set!

                                    THE mark
                                        =>
                                        {   (tlj::kill_region { mark, point, textlines })
                                              ->
                                              { updated_textlines:                      mt::Textlines,
                                                cutbuffer_contents:                     ct::Cutbuffer_Contents,
                                                point:                                  g2d::Point
                                              };

                                            eb.set_cutbuffer_contents  cutbuffer_contents;

                                            WORK  [ mt::TEXTLINES updated_textlines,
                                                    mt::POINT point,
                                                    mt::MARK     NULL,
                                                    mt::LASTMARK NULL
                                                  ];
                                        };
                                esac;
                    result;
                fi;
            };
        kill_region__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "kill_region",
                  doc    =>  "Remove contents of region from buffer, saving in cutbuffer.  Fail if mark is not set.",
                  args   =>  [],
                  editfn =>  kill_region
                }
              );                                my _ =
        mt::note_editfn  kill_region__editfn;


        fun find_file   (arg:           mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                case args
                    #
                    [ mt::STRING_ARG { arg => filepath, ... } ]
                        =>
                        {   textmill_arg
                              =
                              { name             => "",
#                               panemode         => mainmill_modestate.mode,                                            # XXX SUCKO FIXME We'll want to pick this by file extension or such by and by.  
                                textmill_options => []
                              };

                            textpane_to_textmill
                                =
                                eb.get_or_make_filebuffer  textmill_arg  filepath;

#                           textpane_to_textmill
#                               ->
#                               mt::TEXTPANE_TO_TEXTMILL  tb;

#                           tb.reload_from_file ();                                                             # millboss-imp.pkg does this in get_or_make_filebuffer.

#                           (tb.get_textstate ())
#                               ->
#                               { textlines, editcount };

                            WORK  [ mt::TEXTMILL    textpane_to_textmill,                                       # Tell textpane to switch to displaying this textmill.
                        #           mt::TEXTLINES   textlines,                                                  # We do NOT want to do this because we are returning to the old textmill that called us, NOT the new textmill created above by get_or_make_filebuffer.
                                    mt::POINT       { row => 0,  col => 0 },
                                    mt::MARK        NULL,
                                    mt::LASTMARK    NULL
                                  ];
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        find_file__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "find_file",
                  doc    =>  "Load file given its full path.",
                  args   =>  [ mt::FILENAME { prompt => "Find file: ", doc => "Full path for file to read" }  ],
                  editfn =>  find_file
                }
              );                                my _ =
        mt::note_editfn  find_file__editfn;


        fun save_buffer (arg:           mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                WORK  [ mt::SAVE                                                                                # Signal   src/lib/x-kit/widget/edit/textpane.pkg  to call  save_to_file()  in  src/lib/x-kit/widget/edit/textmill.pkg
                      ];
            };
        save_buffer__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "save_buffer",
                  doc    =>  "Save current buffer to disk if modified.",
                  args   =>  [ ],
                  editfn =>  save_buffer
                }
              );                                my _ =
        mt::note_editfn  save_buffer__editfn;


        fun switch_to_mill (arg:        mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                case args
                    #
                    [ mt::STRING_ARG { arg => millname, ... } ]
                        =>
                        {   all_mills_by_name =  eb.get_mills_by_name ();
                            all_mills_by_id   =  eb.get_mills_by_id   ();
                            all_panes_by_id   =  eb.get_panes_by_id   ();

                            case (sm::get (all_mills_by_name, millname))
                                #
                                THE (mill: mt::Mill_Info)
                                    =>
                                    {
                                        mill ->   { mill_id => id:      Id,
                                                    freshness:          Int,
                                                    #
                                                    app_to_mill:        mt::App_To_Mill,
                                                    pane_to_mill:       Crypt,
                                                    #
                                                    name:               String,
                                                    filepath:           Null_Or( String ),
                                                    #
                                                    millins:            mt::ipm::Map(mt::Millin),
                                                    millouts:           mt::opm::Map(mt::Millout),
                                                    #
                                                    millboss_to_mill:   mt::Millboss_To_Mill
                                                  };

                                        if (same_id (id, mill_id))                                                                              # If id==mill_id then we are 'switching' to the same mill, which is a no-op.
                                            #                                                                                                   # It is important to special-case this because we're running in the microthread of our own mill,
                                            WORK  [                                                                                             # so attempting to call app_to_mill.make_pane_guiplan() on ourself (below) is likely to deadlock. (We could call 'make_pane' instead, but what's the point?
                                                  ];
                                        else
                                            case (tmc::get__null_or_textpane_to_textmill__from__null_or_textmill_info  (THE mill))
                                                #
                                                NULL => WORK  [ mt::MODELINE_MESSAGE (sprintf "Mill '%s' found but it is not a textmill, and other mills are not yet supported." millname)
                                                              ];
                                                THE textpane_to_textmill
                                                    =>
                                                    {
                                                        do_while_not {.                                                                         # Repeat guipith edit until it takes.  This is needed because other concurrent microthreads may be
                                                            #                                                                                   # attempting overlapping guipith edits with us.  This avoids deadlock at a (tiny) risk of livelock.
                                                            get_guipiths             =  widget_to_guiboss.g.get_guipiths;
                                                            install_updated_guipiths =  widget_to_guiboss.g.install_updated_guipiths;

                                                            (get_guipiths ())
                                                                ->
                                                                (gui_version, guipiths)
                                                                     #
                                                                     :  (Int, idm::Map( gt::Xi_Hostwindow_Info ))
                                                                     ;

                                                            guipiths =  gtj::guipith_map (guipiths, options)
                                                                            where
                                                                                fun do_widget  (w: gt::Xi_Widget_Type):  gt::Xi_Widget_Type
                                                                                    =
                                                                                    case w
                                                                                        #
                                                                                        gt::XI_FRAME
                                                                                          { id:                 Id,
                                                                                            frame_widget:               gt::Xi_Widget_Type,                     # Widget which will draw the frame surround.
                                                                                            widget:                     gt::Xi_Widget_Type                      # Widget-tree to draw surrounded by frame.
                                                                                          }
                                                                                            =>
                                                                                            case frame_widget
                                                                                                #
                                                                                                gt::XI_WIDGET
                                                                                                  {
                                                                                                    widget_id:          Id,
                                                                                                    widget_layout_hint: gt::Widget_Layout_Hint,
                                                                                                    doc:                String                                  # Debugging support: Allow XI_WIDGETs to be distinguishable for debug-display purposes.
                                                                                                  }
                                                                                                    =>
                                                                                                    if (not (same_id (widget_id, pane_id)))
                                                                                                        #
                                                                                                        w;
                                                                                                    else
                                                                                                        app_to_mill -> mt::APP_TO_MILL a2m;
                                                                                                        #                                                                                                       
                                                                                                        gt::XI_GUIPLAN (a2m.get_pane_guiplan ());
                                                                                                    fi;

                                                                                                _ => w;
                                                                                            esac;

                                                                                        _ => w;
                                                                                    esac;

                                                                                options = [  gtj::XI_WIDGET_TYPE_MAP_FN  do_widget  ]
                                                                                        #
                                                                                        : List( gtj::Guipith_Map_Option )
                                                                                        ;
                                                                            end;

                                                            install_updated_guipiths                                                                            # If this returns FALSE we'll loop and retry.
                                                                #
                                                                (gui_version, guipiths);
                                                        };                                                                                                      # do_while_not

                                                        WORK  [ 
                                                              ];
                                                    };
                                            esac;
                                        fi;
                                    };

                                NULL => WORK  [ mt::MODELINE_MESSAGE (sprintf "No mill '%s' found." millname)
                                              ];
                            esac;
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        switch_to_mill__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "switch_to_mill",
                  doc    =>  "Switch to different mill.",
                  args   =>  [ mt::MILLNAME { prompt => "Switch to mill", doc => "Name of mill (\"buffer\") to display in current pane" }  ],
                  editfn =>  switch_to_mill
                }
              );                                my _ =
        mt::note_editfn  switch_to_mill__editfn;


        fun mark_whole_buffer (arg:     mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mark =  {                                                                                       # Following logic cribbed from end_of_buffer().
                            row =   case (nl::max_key textlines)                                                # Finding number of last row is fairly easy.
                                        #
                                        THE row => row;
                                        NULL    => 0;                                                           # Shouldn't happen.
                                    esac;
                                                                                                                # Now we find screencol of last char in line. That's harder.  Following code is duplicated from move_end_of_line(), probably we should move it into a shared fn.


                            line =  mt::findline (textlines, row);                                                      # Get last line.

                            chomped_line =  string::chomp  line;                                                # Drop terminal newline if any.

                            (string::expand_tabs_and_control_chars                                              # Count number of screencols in last line.
                              {
                                utf8text        =>  chomped_line,
                                startcol        =>  0,
                                screencol1      => -1,                                                          # Don't care.
                                screencol2      => -1,                                                          # Don't care.
                                utf8byte        => -1                                                           # Don't care.
                              })
                              ->
                              { screentext_length_in_screencols,
                                ...
                              };

                            col    =  screentext_length_in_screencols;

                            col    =  max (0, col - 1);                                                         # Is this right?  XXX QUERO FIXME

                            { row, col };
                        };

                point = { row => 0,
                          col => 0
                        };

                screen_origin = point;
                                                                                                                #
                WORK  [ mt::MARK                (THE mark),                                                     # Put 'mark' one char past end of 'textlines' contents.
                        mt::SCREEN_ORIGIN       screen_origin,                                                  # Put 'screen_origin' at start of 'textlines' contents.
                        mt::POINT               point                                                           # Put 'point'         at start of 'textlines' contents.
                      ];
            };
        mark_whole_buffer__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "mark_whole_buffer",
                  doc    =>  "Set region to include entire buffer.",
                  args   =>  [ ],
                  editfn =>  mark_whole_buffer
                }
              );                                my _ =
        mt::note_editfn  mark_whole_buffer__editfn;


        fun other_pane (arg:            mt::Editfn_In)                                                          # Switch keyboard focus to another pane.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                panes_by_id = eb.get_panes_by_id ();

                panes = idm::vals_list  panes_by_id;

                case numeric_prefix
                    #
                    NULL =>                                                                                     # User didn't specify a pane_tag to switch the keyboard focus to, so switch to the next in pane_tag order, wrapping around if necessary.
                        {   panes = lms::sort_list  gt  panes
                                        where
                                            fun gt ( pane1: mt::Pane_Info,
                                                     pane2: mt::Pane_Info
                                                   )
                                                =
                                                pane1.pane_tag > pane2.pane_tag;
                                        end;

                            next__pane_id
                                =
                                find_next__pane_id  panes
                                    where
                                        fun find_next__pane_id  (p ! (rest as (q ! _)))
                                                =>
                                                if (same_id (p.pane_id, pane_id))   q.pane_id;
                                                else                                find_next__pane_id  rest;
                                                fi;

                                            find_next__pane_id  (_: List(mt::Pane_Info))
                                                =>
                                                (head panes).pane_id;
                                        end;
                                    end;

                            widget_to_guiboss.g.request_keyboard_focus  next__pane_id;


                            WORK  [ # mt::MODELINE_MESSAGE "other_pane not implemented   -- fundamental-mode.pkg"
                                  ];
                        };

                    THE next__pane_tag                                                                          # User specified the pane_tag to switch the keyboard focus to, so find the corresponding pane_id and do the deed.
                        =>
                        find_next__pane_id  panes
                            where
                                fun find_next__pane_id []
                                        =>
                                        WORK  [ mt::MODELINE_MESSAGE (sprintf "No pane %d found" next__pane_tag)
                                              ];
                                        
                                            
                                    find_next__pane_id ({ pane_id, pane_tag, mill_id } ! rest)
                                        =>
                                        if (pane_tag == next__pane_tag)
                                            #
                                            widget_to_guiboss.g.request_keyboard_focus  pane_id;

                                            WORK  [ ];
                                        else
                                            find_next__pane_id  rest;
                                        fi;
                                end;
                            end;
                esac;
            };
        other_pane__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "other_pane",
                  doc    =>  "Switch keyboard focus to another textpane.",
                  args   =>  [ ],
                  editfn =>  other_pane
                }
              );                                my _ =
        mt::note_editfn  other_pane__editfn;


        fun recenter_top_bottom (arg:   mt::Editfn_In)                                                          # Scroll screen so as to leave line containing cursor in the middle of the textpane display.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                point -> { row, col };

                row' = max (0, row - (visible_lines/2));                                                        # Figure screen_origin.row that would put cursor line in middle of pane -- but don't let origin row go negative.
                                                                                                                #
                if (row' != row)                                                                                # 
                    #                                                                                           # 
                    new_screen_origin                                                                           #
                        =                                                                                       # 
                        { row => row',                                                                          #
                          col => 0                                                                              #
                        };                                                                                      #       
                                                                                                                #
                    WORK  [ mt::SCREEN_ORIGIN   new_screen_origin                                               # 
                          ];
                else
                    WORK  [];
                fi;
            };
        recenter_top_bottom__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "recenter_top_bottom",
                  doc    =>  "Scroll textpane contents so as to leave cursor line in middle.",
                  args   =>  [ ],
                  editfn =>  recenter_top_bottom
                }
              );                                my _ =
        mt::note_editfn  recenter_top_bottom__editfn;


        fun scroll_up (arg:             mt::Editfn_In)                                                          # Aka "page down". Typically bound to C-v.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                screen_origin -> { row, col };

                last_line_number
                    =
                    case (nl::max_key textlines)
                        #
                        NULL  => 0;
                        THE n => n;
                    esac;

                if (row + visible_lines <= last_line_number)                                                    # If 'textlines' contains lines not visible below bottom of current textpane display...
                    #                                                                                           # 
                    new_screen_origin                                                                           #
                        =                                                                                       # 
                        { row => row + (visible_lines - 1),                                                     #
                          col => 0                                                                              #
                        };                                                                                      #       
                                                                                                                #
                    WORK  [ mt::SCREEN_ORIGIN   new_screen_origin,                                              # ... move screen origin down by one line less that a full textpane screenful
                            mt::POINT           new_screen_origin                                               # and position cursor at upper-left of visible pane.
                          ];
                else
                    WORK  [];
                fi;
            };
        scroll_up__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "scroll_up",
                  doc    =>  "Scroll textpane contents up one page.",
                  args   =>  [ ],
                  editfn =>  scroll_up
                }
              );                                my _ =
        mt::note_editfn  scroll_up__editfn;


        fun scroll_down (arg:           mt::Editfn_In)                                                          # Aka "page up". Typically bound to M-v.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                screen_origin -> { row, col };

                if (row > 0)                                                                                    # If 'textlines' contains lines not visible above top of current textpane display...
                    #                                                                                           #
                    row' = max (0, row - (visible_lines - 1));                                                  #
                                                                                                                #
                    new_screen_origin                                                                           #
                        =                                                                                       #
                        { row => row',                                                                          #
                          col => 0                                                                              #
                        };                                                                                      #
                                                                                                                #
                    row' = max (0, new_screen_origin.row + visible_lines - 2);                                  #
                                                                                                                #
                    new_point                                                                                   #
                        =                                                                                       #
                        { row => row',                                                                          #
                          col => 0                                                                              #
                        };                                                                                      #
                                                                                                                #
                    WORK  [ mt::SCREEN_ORIGIN   new_screen_origin,                                              # ... move screen origin up by one line less that a full textpane screenful
                            mt::POINT           new_point                                                       # and position cursor at lower-left of visible pane.
                          ];
                else
                    WORK  [];
                fi;
            };
        scroll_down__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "scroll_down",
                  doc    =>  "Scroll textpane contents down one page.",
                  args   =>  [ ],
                  editfn =>  scroll_down
                }
              );                                my _ =
        mt::note_editfn  scroll_down__editfn;


        fun count_lines_region (arg:    mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };
                                                                                                                                        # Following code is adapted from kill_region, possibly some code factorization would be good.

                mark =  case mark
                            #
                            THE mark => mark;
                            NULL     => case lastmark
                                            #
                                            THE mark => mark;
                                            NULL     => point;
                                        esac;
                        esac;

                lines = (point.row - mark.row) + 1                                                                                      # That's the easy part. :-)  Now for 'chars'.
                        where
                            my (mark, point)                                                                                            # Sort 'mark' and 'point' so mark comes first in buffer order.
                                =
                                if   (mark.row > point.row)  (point, mark);
                                elif (mark.row < point.row)  (mark, point);
                                elif (mark.col > point.col)  (point, mark);
                                else                         (mark, point);
                                fi;
                        end;

                chars = {
                            # The columns for 'mark' and 'point' may be
                            # somewhere odd in the middle of (e.g.) tabs,
                            # so start by deriving normalized versions:
                            #
                            mark'  = tlj::normalize_point (mark,  textlines);
                            point' = tlj::normalize_point (point, textlines);

                            if (mark'.row == point'.row)
                                #
                                line_key = mark'.row;                                                                                   # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                                text =  mt::findline (textlines, line_key);

                                chomped_text =  string::chomp  text;

                                my (col1, col2)                                                                                         # First screen cols for first and last chars in selected region.
                                    =                                                                                                   # NB: We interpret point'==mark' as designating a single-char region.  This preserves the invariant that "C-x C-x" (exchange_point_and_mark) does not change the selected region.
                                    if  (point'.col <= mark'.col)
                                        (point'.col,   mark'.col);
                                    else                                                                                                # point.col > mark.col
                                        # When point is beyond mark, don't include
                                        # point's char (screen column(s)) in the region:
                                        #
                                        (string::expand_tabs_and_control_chars
                                          {
                                            utf8text    =>  chomped_text,
                                            startcol    =>  0,
                                            screencol1  =>  point'.col - 1,                                                             # Since point'.col is guaranteed to be first col for char, subtracting one is guaranteed to put us on previous char.
                                            screencol2  => -1,                                                                          # Don't-care.
                                            utf8byte    => -1                                                                           # Don't-care.
                                          })
                                          ->
                                          { screencol1_firstcol_on_screen:              Int,                                            # First screen column of last char in selected region. Note that screencol1 is guaranteed to be nonnegative because point'.col > mark'.col and both are normalized and on same line.
                                            ...
                                          };

                                        (mark'.col,  screencol1_firstcol_on_screen);
                                    fi;

                                                                                                                                        # NB: We may have col1==col2 here.  That's OK, and indicates a one-char region to be moved to the cutbuffer -- remember, col1,col2 are both included in the region.
                                (string::expand_tabs_and_control_chars                                                                  # Map screencols col1,col2 to byteoffsets in chomped_text.
                                  {
                                    utf8text    =>  chomped_text,
                                    startcol    =>  0,
                                    screencol1  =>  col1,
                                    screencol2  =>  col2,
                                    utf8byte    => -1                                                                                   # Don't-care.
                                  })
                                  ->
                                  { screencol1_byteoffset_in_utf8text:  Int,
                                    screencol2_byteoffset_in_utf8text:  Int,
                                    screencol2_bytescount_in_utf8text:  Int,
                                    ...
                                  };

                                utf8_len_in_bytes = string::length_in_bytes  chomped_text;                                              # 
                                                                                                                                        # 
                                text_within_region                                                                                      # 
                                    =                                                                                                   #
                                    if (screencol1_byteoffset_in_utf8text >= utf8_len_in_bytes)                                         # If region lies entirely beyond actual end of line in utf8text.
                                        #
                                        string::repeat(" ", (screencol2_byteoffset_in_utf8text-screencol1_byteoffset_in_utf8text) + 1);

                                    elif (col2 >= utf8_len_in_bytes)                                                                    # Region starts within utf8text string but extends beyond actual end of line in utf8text.
                                        #
                                        string::extract  (chomped_text, screencol1_byteoffset_in_utf8text,  NULL)
                                        +
                                        string::repeat(" ", (screencol1_byteoffset_in_utf8text-utf8_len_in_bytes) + 1);

                                    else                                                                                                # Region lies entirely within input string.
                                        string::substring
                                          (
                                            chomped_text,
                                            screencol1_byteoffset_in_utf8text,
                                            (screencol2_byteoffset_in_utf8text + screencol2_bytescount_in_utf8text) - screencol1_byteoffset_in_utf8text
                                          );
                                    fi;

                                string::length_in_chars  text_within_region;
                            else                                                                                                        # mark'.row != point'.row, so this will be a cb::MULTILINE cut. 

                                my (first, final)                                                                                       # Sort point and mark and implement the convention that if point is last, it points to first char BEYOND region, but if mark is last it points to last char IN region.
                                    =
                                    if (point'.row < mark'.row)                                                                         # NB: We know from above that mark.row != point.row.
                                        #
                                        (point', mark');

                                    elif (point'.col == 0)                                                                              # Specialcase check to keep following clause from yielding a negative final.col value.

                                        (mark', point');
                                    else                                                                                                # point.row > mark.row
                                        # When point is beyond mark, don't include
                                        # point's char (screen column(s)) in the region:
                                        #
                                        finalline_key = mark'.row;                                                                      # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                                        finaltext =     mt::findline (textlines, finalline_key);

                                        chomped_finaltext =  string::chomp  finaltext;

                                        (string::expand_tabs_and_control_chars
                                          {
                                            utf8text    =>  chomped_finaltext,
                                            startcol    =>  0,
                                            screencol1  =>  point'.col - 1,                                                             # Since point' is normalized and point'.col is nonzero, subtracting one is guaranteed to put us on a valid previous char.
                                            screencol2  => -1,                                                                          # Don't-care.
                                            utf8byte    => -1                                                                           # Don't-care.
                                          })
                                          ->
                                          { screencol1_firstcol_on_screen:              Int,                                            # First screen column of last char in selected region.
                                            ...
                                          };

                                        (mark', { row => point'.row, col => screencol1_firstcol_on_screen } );
                                    fi;

                                first' = tlj::normalize_point (first, textlines);                                                       # Construct normalized versions of first and final, where screencol is at start of char each is on.
                                final' = tlj::normalize_point (final, textlines);

                                firstline_key = first'.row;                                                                             # 

                                firsttext =     mt::findline (textlines, firstline_key);

                                chomped_firsttext =  string::chomp  firsttext;

                                firsttext_len_in_bytes = string::length_in_bytes  chomped_firsttext;                                    # 


                                finalline_key = final'.row;                                                                             # 

                                finaltext =     mt::findline (textlines, finalline_key);

                                chomped_finaltext =  string::chomp  finaltext;

                                finaltext_len_in_bytes = string::length_in_bytes  chomped_finaltext;                                    # 


                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text    =>  chomped_firsttext,
                                    startcol    =>  0,
                                    screencol1  =>  first'.col,                                                                         # Since point' is normalized and point'.col is nonzero, subtracting one is guaranteed to put us on a valid previous char.
                                    screencol2  => -1,                                                                                  # Don't-care.
                                    utf8byte    => -1                                                                                   # Don't-care.
                                  })
                                  ->
                                  { screencol1_byteoffset_in_utf8text => firstcol_byteoffset_in_firsttext,                              # Byteoffset in firsttext corresponding to first char in selected region.
                                    ...
                                  };

                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text    =>  chomped_finaltext,
                                    startcol    =>  0,
                                    screencol1  =>  final'.col,                                                                         # Since point' is normalized and point'.col is nonzero, subtracting one is guaranteed to put us on a valid previous char.
                                    screencol2  => -1,                                                                                  # Don't-care.
                                    utf8byte    => -1                                                                                   # Don't-care.
                                  })
                                  ->
                                  { screencol1_byteoffset_in_utf8text => finalcol_byteoffset_in_finaltext,                              # Byteoffset in finaltext corresponding to final char in selected region.
                                    screencol1_bytescount_in_utf8text => finalcol_bytescount_in_finaltext,                              # Number of bytes in final char.
                                    ...
                                  };



                                text_within_firstline_region
                                    =                                                                                                   #
                                    if (firstcol_byteoffset_in_firsttext >= firsttext_len_in_bytes)                                     # If start of region lies beyond actual end of line in firsttext.
                                        #
                                        "";
                                    else                                                                                                # If start of region lies within firsttext.
                                        #
                                        string::extract   (chomped_firsttext, firstcol_byteoffset_in_firsttext,  NULL);
                                    fi;


                                text_within_finalline_region
                                    =                                                                                                   #
                                    {   beyondregion_byteoffset = finalcol_byteoffset_in_finaltext                                      # Compute first byteoffset BEYOND region.
                                                                + finalcol_bytescount_in_finaltext
                                                                ;
                                        if (beyondregion_byteoffset >= finaltext_len_in_bytes)                                          # If end of region lies beyond actual end of line in finaltext.
                                            #
                                            chomped_finaltext + (string::repeat(" ", beyondregion_byteoffset - finaltext_len_in_bytes));
                                        else                                                                                            # If end of region lies within finaltext.
                                            #
                                            string::substring (chomped_finaltext, 0,   beyondregion_byteoffset);
                                        fi;
                                    };

                                chars_in_firstline_region =  string::length_in_chars  text_within_firstline_region;
                                chars_in_finalline_region =  string::length_in_chars  text_within_finalline_region;

                                chars_in_whole_lines_in_cut                                                                             # Collect all lines strictly between firstline and finalline (== first'.row and final'.row).
                                    =
                                    loop (first'.row + 1, 0)
                                    where
                                        lastrow = final'.row - 1;

                                        fun loop (thisrow, result)
                                            =
                                            if (thisrow > lastrow)
                                                #
                                                result;
                                            else
                                                line_key = thisrow;

                                                text =  mt::findline (textlines, line_key);

                                                chars_in_text =  string::length_in_chars  text;

                                                loop (thisrow + 1, chars_in_text + result);
                                            fi;
                                    end;

                                chars_in_region =  chars_in_firstline_region
                                                +  chars_in_finalline_region
                                                +  chars_in_whole_lines_in_cut;

                                chars_in_region;
                            fi;
                        };

                WORK  [ mt::MODELINE_MESSAGE (sprintf "Region has %d lines, %d characters" lines chars)
                      ];
            };
        count_lines_region__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "count_lines_region",
                  doc    =>  "Count number of lines in region.",
                  args   =>  [ ],
                  editfn =>  count_lines_region
                }
              );                                my _ =
        mt::note_editfn  count_lines_region__editfn;


        fun undo         (arg:          mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                (bq::unpush  edit_history)
                    ->
                    (new_edit_history, old_editstate);

                case old_editstate
                    #
                    THE old_editstate
                        =>
                        {   old_editstate ->  { textlines => old_textlines, ... };
                            #
                            max1 =  case (nl::max_key old_textlines)
                                        #
                                        THE maxkey => maxkey;
                                        NULL       => -1;
                                    esac;

                            max2 =  case (nl::max_key textlines)
                                        #
                                        THE maxkey => maxkey;
                                        NULL       => -1;
                                    esac;

                            max12 = min (max1, max2);

                            point = find_first_difference 0                                                                             # We'll put cursor on first difference between old and new textlines.
                                    where
                                        fun find_first_difference i
                                            =
                                            if (i > max12)
                                                #
                                                { row =>  i - 1,
                                                  col =>  0
                                                };
                                            else
                                                line1 = mt::findline (old_textlines, i);
                                                line2 = mt::findline (    textlines, i);

                                                if (line1 == line2)
                                                    #
                                                    find_first_difference (i+1);
                                                else
                                                    prefix = string::longest_common_prefix (line1, line2);

                                                    chomped_prefix =  string::chomp  prefix;                                            # Drop terminal newline if any.

                                                    (string::expand_tabs_and_control_chars                                              # Count number of screencols in last line.
                                                      {
                                                        utf8text        =>  chomped_prefix,
                                                        startcol        =>  0,
                                                        screencol1      => -1,                                                          # Don't care.
                                                        screencol2      => -1,                                                          # Don't care.
                                                        utf8byte        => -1                                                           # Don't care.
                                                      })
                                                      ->
                                                      { screentext_length_in_screencols,
                                                        ...
                                                      };

                                                    col    =  screentext_length_in_screencols;

                                                    { row => i, col };
                                                fi;             
                                            fi;
                                    end;

                            WORK  [ mt::TEXTLINES       old_textlines,
                                    mt::EDIT_HISTORY    new_edit_history,
                                    mt::POINT           point
                                  ];
                        };
                    NULL =>
                        {
                            WORK  [ mt::MODELINE_MESSAGE "No further undo history available."
                                  ];
                        };
                esac;

            };
        undo__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "undo",
                  doc    =>  "Undo one edit operation.",
                  args   =>  [ ],
                  editfn =>  undo
                }
              );                                my _ =
        mt::note_editfn  undo__editfn;


        fun query_replace (arg:         mt::Editfn_In)                                                                                  # Typically bound to M-%.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                                       # Args read interactively from user per our __editfn.args spec.
                            readonly:                   Bool,                                                                           # TRUE iff contents of textmill are currently marked as read-only.
                            ...
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else

                    case args                                                                                                           # At this point we've read interactively from user  string_to_replace  but not yet  replacement_string.
                        #
                        [ mt::STRING_ARG { arg => string_to_replace, ... } ]
                            =>
                            {   fun query_replace' (arg:                mt::Editfn_In)                                                  # This version of the fn locks in the 'string_to_replace' value above.
                                    :                           mt::Editfn_Out
                                    =
                                    {   arg ->    { args:               List( mt::Prompted_Arg ),                                       # Args read interactively from user per our __editfn.args spec.
                                                    textlines:          mt::Textlines,
                                                    point:              g2d::Point,                                                     # As in Point_And_Mark.
                                                    mark:               Null_Or(g2d::Point),                                            # 
                                                    ...
                                                  };

                                        case args                                                                                       # At this point we've read interactively from user both  string_to_replace  and  replacement_string.
                                            #
                                            [ mt::STRING_ARG { arg => replacement_string, ... } ]
                                                =>
                                                do_next_match { textlines,
                                                                row => point.row,
                                                                col => point.col
                                                              }
                                                where
                                                    substitutions_done =  REF 0;
                                                    last_match         =  REF point;

                                                    fun do_next_match
                                                          {
                                                            textlines:          mt::Textlines,
                                                            row:                        Int,                                                                    # Line number  currently being searched for matches to 'string_to_replace'.
                                                            col:                        Int                                                                     # First screen column on line to search for matches to 'string_to_replace'.
                                                          }     
                                                        =
                                                        {   # Stting at 'point', see if we can find any instances
                                                            # of 'string_to_replace' in the buffer:

                                                            max_key  =  case (nl::max_key  textlines)
                                                                            #
                                                                            THE max_key => max_key;
                                                                            NULL            => 0;                                                               # We don't expect this.
                                                                        esac;

                                                            if (row > max_key)
                                                                #
                                                                WORK  [ mt::MODELINE_MESSAGE (sprintf "%d substitutions done" *substitutions_done),             # Done -- no lines left to search.
                                                                        mt::TEXTLINES textlines,                                                                # Update screen with changed 'textlines,' if it has changed.
                                                                        mt::POINT *last_match,                                                                  # Leave 'point' (=cursor) after last candidate substitution point.
                                                                        mt::MARK NULL                                                                           # Clear any mark we have left set.
                                                                      ];
                                                            else
                                                                line =  mt::findline (textlines, row);

                                                                chomped_line =  string::chomp  line;

                                                                (string::expand_tabs_and_control_chars                                                          # Find byteoffset in chomped_line corresponding to 'col'.  This is where we start our search.
                                                                  {
                                                                    utf8text    =>  chomped_line,
                                                                    startcol    =>  0,
                                                                    screencol1  =>  col,
                                                                    screencol2  => -1,                                                                          # Don't-care.
                                                                    utf8byte    => -1                                                                           # Don't-care.
                                                                  })
                                                                  ->
                                                                  { screencol1_byteoffset_in_utf8text => byteoffset_for_pointcol,
                                                                    ...
                                                                  };

                                                                case (string::find_substring' string_to_replace (line, byteoffset_for_pointcol))                # Search line for string_to_replace.
                                                                    #
                                                                    THE byteoffset_of__string_to_match                                                          # Found string_to_replace on line.
                                                                        =>
                                                                        {   # We want to highlight our match to 'string_to_replace'
                                                                            # by setting to 'region' to cover it.  First step is to
                                                                            # identify the starting and ending screen columns:

                                                                            (string::expand_tabs_and_control_chars                                              # Find first screencol in line of our string_to_match hit.
                                                                              {
                                                                                utf8text        =>  chomped_line,
                                                                                startcol        =>  0,
                                                                                screencol1      => -1,                                                          # Don't-care.                                
                                                                                screencol2      => -1,                                                          # Don't-care.
                                                                                utf8byte        =>  byteoffset_of__string_to_match                              # 
                                                                              })
                                                                              ->
                                                                              { utf8byte_firstcol_on_screen => first_screencol_for__string_to_match,
                                                                                ...
                                                                              };

                                                                            first_byteoffset_beyond__string_to_match
                                                                                =       
                                                                                byteoffset_of__string_to_match
                                                                                + 
                                                                                string::length_in_bytes  string_to_replace;

                                                                            (string::expand_tabs_and_control_chars                                              # Find first screencol in line beyond string_to_match hit.
                                                                              {
                                                                                utf8text        =>  chomped_line,
                                                                                startcol        =>  0,
                                                                                screencol1      => -1,                                                          # Don't-care.                                
                                                                                screencol2      => -1,                                                          # Don't-care.
                                                                                utf8byte        =>  first_byteoffset_beyond__string_to_match
                                                                              })
                                                                              ->
                                                                              { utf8byte_firstcol_on_screen => first_screencol_beyond__string_to_match,
                                                                                ...
                                                                              };

                                                                            fun query_replace'' (arg:   mt::Editfn_In)                                          # This version of the fn locks in both 'string_to_replace' and 'replacement_string' values above.
                                                                                :                               mt::Editfn_Out
                                                                                =
                                                                                {   arg ->    { args:                   List( mt::Prompted_Arg ),               # Args read interactively from user per our __editfn.args spec.
                                                                                                textlines:              mt::Textlines,
                                                                                                point:                  g2d::Point,                             # As in Point_And_Mark.
                                                                                                ...
                                                                                              };

                                                                                    case args                                                                   # 
                                                                                        #
                                                                                        [ mt::STRING_ARG { arg => y_or_n_string, ... } ]
                                                                                            =>
                                                                                            case y_or_n_string
                                                                                                #
                                                                                                "y" =>
                                                                                                    {   text_before_match
                                                                                                            =
                                                                                                            string::substring
                                                                                                              (
                                                                                                                chomped_line,                                                           # String from which to extract substring.
                                                                                                                0,                                                                      # The substring we want starts at offset 0.
                                                                                                                byteoffset_of__string_to_match                                          # The substring we want runs to location of string_to_match.
                                                                                                              );

                                                                                                        text_beyond_match
                                                                                                            =
                                                                                                            string::extract
                                                                                                              (
                                                                                                                chomped_line,                                                           # String from which to extract substring.
                                                                                                                first_byteoffset_beyond__string_to_match,                               # Substring we want starts immediately past end of string_to_match.
                                                                                                                NULL                                                                    # Substring runs to end of 'text'.
                                                                                                              );

                                                                                                        updated_line
                                                                                                            =
                                                                                                            string::cat [ text_before_match,
                                                                                                                          replacement_string,
                                                                                                                          text_beyond_match,
                                                                                                                          line == chomped_line  ??  ""  ::  "\n"
                                                                                                                        ];

                                                                                                        updated_line' = mt::MONOLINE  { string =>  updated_line,
                                                                                                                                        prefix =>  NULL
                                                                                                                                      };

                                                                                                        updated_textlines                                                               # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                                                                                            =
                                                                                                            (nl::remove (textlines, row))
                                                                                                            except _ = textlines;                                                       # This will happen if there is no line 'row' in textlines.

                                                                                                        updated_textlines                                                               # Now insert updated line.
                                                                                                            =
                                                                                                            nl::set (updated_textlines, row, updated_line');
 
                                                                                                        # Now to figure screen column corresponding to end of replacement text:
                                                                                                        #
                                                                                                        (string::expand_tabs_and_control_chars                                          # Find first screencol in line beyond string_to_match hit.
                                                                                                          {
                                                                                                            utf8text    =>  updated_line,
                                                                                                            startcol    =>  0,
                                                                                                            screencol1  => -1,                                                          # Don't-care.                                
                                                                                                            screencol2  => -1,                                                          # Don't-care.
                                                                                                            utf8byte    =>  byteoffset_of__string_to_match
                                                                                                                            + string::length_in_bytes replacement_string
                                                                                                          })
                                                                                                          ->
                                                                                                          { utf8byte_firstcol_on_screen => first_screencol_beyond__replacement_string,
                                                                                                            ...
                                                                                                          };

                                                                                                        substitutions_done :=  *substitutions_done + 1;

                                                                                                        last_match         :=   { row,
                                                                                                                                  col => first_screencol_beyond__replacement_string
                                                                                                                                };


                                                                                                        do_next_match { textlines => updated_textlines,
                                                                                                                        row,
                                                                                                                        col => first_screencol_beyond__replacement_string
                                                                                                                      };
                                                                                                    };

                                                                                                "n" =>
                                                                                                    {   last_match         :=   { row,
                                                                                                                                  col => first_screencol_beyond__string_to_match
                                                                                                                                };

                                                                                                        do_next_match { textlines, row, col => first_screencol_for__string_to_match + 1 };
                                                                                                    };  

                                                                                                _ =>                                                                                    # Handle any input other than "y"/"n" by exiting query-replace loop -- this is what emacs seems to do.
                                                                                                    {   WORK  [ mt::MODELINE_MESSAGE "query_replace aborted",
                                                                                                                mt::TEXTLINES textlines,                                                # Update screen with changed 'textlines,' if it has changed.
                                                                                                                mt::POINT *last_match,                                                  # Leave 'point' (=cursor) after last candidate substitution point.
                                                                                                                mt::MARK NULL                                                           # Clear any mark we have left set.
                                                                                                              ];
                                                                                                    };
                                                                                            esac;

                                                                                        [ mt::INCREMENTAL_STRING_ARG _ ]                                        # Terminate incremental input after first char.  (Using incremental input here is just a hack to save user having to hit <RET> after typing 'y' or 'n'.)
                                                                                            =>                                                                  # Execution will resume above in the mt::STRING_ARG case. 
                                                                                            WORK  [ mt::STRING_ENTRY_COMPLETE ];

                                                                                        _   =>  WORK  [ ];                                                      # Not possible.
                                                                                    esac;
                                                                                };                                                                              # "fun query_replace''", with both  string_to_replace  and  replacement_string  locked in.

                                                                            query_replace__editfn''                                                             # This (third-level) editfn will query for y-or-n decision on whether to do substition at current spot.
                                                                                =
                                                                                mt::EDITFN (
                                                                                  mt::PLAIN_EDITFN
                                                                                    {
                                                                                      name   =>  "query_replace''",
                                                                                      doc    =>  "Replace one string by another, querying user y-or-n for each substitution.",
                                                                                      args   =>   [ mt::INCREMENTAL_STRING { prompt => sprintf "Query replacing %s by %s: "  string_to_replace  replacement_string,
                                                                                                                             doc    => "'y' to replace, 'n' to leave unchanged"
                                                                                                                           }
                                                                                                  ],
                                                                                      editfn =>  query_replace''
                                                                                    }
                                                                                  );

                                                                            WORK  [ mt::TEXTLINES textlines,                                                    # Update screen with changed 'textlines,' if it has changed.
                                                                                    #
                                                                                    mt::POINT     { row,                                                        # Move 'point' (=cursor) one char past end of 'string_to_replace' match on line.
                                                                                                    col => first_screencol_beyond__string_to_match
                                                                                                  },
                                                                                    mt::MARK (THE { row,                                                        # Move 'mark' to start of string_to_match in 'line'.
                                                                                                    col => first_screencol_for__string_to_match
                                                                                                  }
                                                                                             ),
                                                                                    mt::EDITFN_TO_INVOKE  query_replace__editfn''                               # Submit y-or-n replace-string query.
                                                                                  ];
                                                                        };

                                                                    NULL =>      do_next_match { textlines,  row => row + 1,  col => 0 };                       # No more matches on this line, search next line for matches to 'string_to_match'.
                                                                esac;
                                                            fi;
                                                        };                                                                                                      # fun do_next_match
                                                end;                                                                                                            # where

                                            _   =>  WORK  [ ];                                                                                                  # Not possible.
                                        esac;
                                    };                                                                                                                          # "fun query_replace'", with string_to_replace locked in.

                                query_replace__editfn'                                                                                                          # This (second-level) editfn will query for  replacement_string.
                                    =
                                    mt::EDITFN (
                                      mt::PLAIN_EDITFN
                                        {
                                          name   =>  "query_replace'",
                                          doc    =>  "Replace one string by another, querying user y-or-n for each substitution.",
                                          args   =>  [ mt::STRING { prompt => sprintf "Query replace %s by: " string_to_replace,
                                                                    doc    => sprintf "Replacement string for %s throughout rest of file (per interactive y/n go-aheads)." string_to_replace
                                                                  }
                                                     ],
                                          editfn =>  query_replace'
                                        }
                                    );

                                WORK  [ mt::EDITFN_TO_INVOKE  query_replace__editfn'                                                                            # Submit query for  replacement_string.
                                      ];
                            };

                        _ => WORK  [ ];                                                                                                                         # Not possible.
                    esac;
                fi;                                                                                                                                             # readonly.
            };                                                                                                                                                  # Outermost (public) "fun query_replace"
        query_replace__editfn                                                                                                                                   # This (outermost) editfn will query for  string_to_replace.
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "query_replace",
                  doc    =>  "Replace one string by another, querying user y-or-n for each substitution.",
                  args   =>  [ mt::STRING { prompt => "Query replace: ",
                                            doc    => "String to replace throughout rest of file (per interactive y/n go-aheads)."
                                          }
                             ],
                  editfn =>  query_replace
                }
              );                                my _ =
        mt::note_editfn  query_replace__editfn;


        fun replace_string (arg:                        mt::Editfn_In)                                                                                          # Replace one string by another throughout rest of buffer.
            :                                           mt::Editfn_Out                                                                                          # This is just a non-interactive version of query_replace (above).
            =                                                                                                                                                   # This editfn is typically not bound to a keystroke -- run it via  M-x replace_string.
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                                                                               # Args read interactively from user per our __editfn.args spec.
                            readonly:                   Bool,                                                                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            ...
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else

                    case args                                                                                                                                   # At this point we've read interactively from user  string_to_replace  but not yet  replacement_string.
                        #
                        [ mt::STRING_ARG { arg => string_to_replace, ... } ]
                            =>
                            {   fun replace_string' (arg:                       mt::Editfn_In)                                                                  # This version of the fn locks in the 'string_to_replace' value above.
                                    :                                           mt::Editfn_Out
                                    =
                                    {   arg ->    { args:                       List( mt::Prompted_Arg ),                                                       # Args read interactively from user per our __editfn.args spec.
                                                    textlines:                  mt::Textlines,
                                                    point:                      g2d::Point,                                                                     # As in Point_And_Mark.
                                                    ...
                                                  };

                                        case args                                                                                                               # At this point we've read interactively from user both  string_to_replace  and  replacement_string.
                                            #
                                            [ mt::STRING_ARG { arg => replacement_string, ... } ]
                                                =>
                                                do_next_match { textlines,
                                                                row => point.row,
                                                                col => point.col
                                                              }
                                                where
                                                    substitutions_done =  REF 0;
                                                    last_match         =  REF point;

                                                    fun do_next_match
                                                          {
                                                            textlines:          mt::Textlines,
                                                            row:                        Int,                                                                    # Line number  currently being searched for matches to 'string_to_replace'.
                                                            col:                        Int                                                                     # First screen column on line to search for matches to 'string_to_replace'.
                                                          }     
                                                        =
                                                        {   # Stting at 'point', see if we can find any instances
                                                            # of 'string_to_replace' in the buffer:

                                                            max_key  =  case (nl::max_key  textlines)
                                                                            #
                                                                            THE max_key => max_key;
                                                                            NULL            => 0;                                                               # We don't expect this.
                                                                        esac;

                                                            if (row > max_key)
                                                                #
                                                                WORK  [ mt::MODELINE_MESSAGE (sprintf "%d substitutions done" *substitutions_done),             # Done -- no lines left to search.
                                                                        mt::TEXTLINES textlines,                                                                # Update screen with changed 'textlines,' if it has changed.
                                                                        mt::POINT *last_match,                                                                  # Leave 'point' (=cursor) after last substitution.
                                                                        mt::MARK NULL                                                                           # Clear any mark we have left set.
                                                                      ];
                                                            else
                                                                line =  mt::findline (textlines, row);

                                                                chomped_line =  string::chomp  line;

                                                                (string::expand_tabs_and_control_chars                                                          # Find byteoffset in chomped_line corresponding to 'col'.  This is where we start our search.
                                                                  {
                                                                    utf8text    =>  chomped_line,
                                                                    startcol    =>  0,
                                                                    screencol1  =>  col,
                                                                    screencol2  => -1,                                                                          # Don't-care.
                                                                    utf8byte    => -1                                                                           # Don't-care.
                                                                  })
                                                                  ->
                                                                  { screencol1_byteoffset_in_utf8text => byteoffset_for_pointcol,
                                                                    ...
                                                                  };

                                                                case (string::find_substring' string_to_replace (line, byteoffset_for_pointcol))                # Search line for string_to_replace.
                                                                    #
                                                                    THE byteoffset_of__string_to_replace                                                        # Found string_to_replace on line.
                                                                        =>
                                                                        {
                                                                            first_byteoffset_beyond__string_to_replace
                                                                                =       
                                                                                byteoffset_of__string_to_replace
                                                                                + 
                                                                                string::length_in_bytes  string_to_replace;


                                                                            (string::expand_tabs_and_control_chars                                              # Find first screencol in line beyond string_to_match hit.
                                                                              {
                                                                                utf8text        =>  chomped_line,
                                                                                startcol        =>  0,
                                                                                screencol1      => -1,                                                          # Don't-care.                                
                                                                                screencol2      => -1,                                                          # Don't-care.
                                                                                utf8byte        =>  first_byteoffset_beyond__string_to_replace
                                                                              })
                                                                              ->
                                                                              { utf8byte_firstcol_on_screen => first_screencol_beyond__string_to_replace,
                                                                                ...
                                                                              };


                                                                            text_before_match
                                                                                =
                                                                                string::substring
                                                                                  (
                                                                                    chomped_line,                                                               # String from which to extract substring.
                                                                                    0,                                                                          # The substring we want starts at offset 0.
                                                                                    byteoffset_of__string_to_replace                                            # The substring we want runs to location of string_to_match.
                                                                                  );

                                                                            text_beyond_match
                                                                                =
                                                                                string::extract
                                                                                  (
                                                                                    chomped_line,                                                               # String from which to extract substring.
                                                                                    first_byteoffset_beyond__string_to_replace,                                 # Substring we want starts immediately past end of string_to_match.
                                                                                    NULL                                                                        # Substring runs to end of 'text'.
                                                                                  );

                                                                            updated_line
                                                                                =
                                                                                string::cat [ text_before_match,
                                                                                              replacement_string,
                                                                                              text_beyond_match,
                                                                                              line == chomped_line  ??  ""  ::  "\n"
                                                                                            ];

                                                                            updated_line' = mt::MONOLINE  { string =>  updated_line,
                                                                                                            prefix =>  NULL
                                                                                                          };

                                                                            updated_textlines                                                                   # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                                                                =
                                                                                (nl::remove (textlines, row))
                                                                                except _ = textlines;                                                           # This will happen if there is no line 'row' in textlines.

                                                                            updated_textlines                                                                   # Now insert updated line.
                                                                                =
                                                                                nl::set (updated_textlines, row, updated_line');

                                                                            # Now to figure screen column corresponding to end of replacement text:
                                                                            #
                                                                            (string::expand_tabs_and_control_chars                                              # Find first screencol in line beyond string_to_match hit.
                                                                              {
                                                                                utf8text        =>  updated_line,
                                                                                startcol        =>  0,
                                                                                screencol1      => -1,                                                          # Don't-care.                                
                                                                                screencol2      => -1,                                                          # Don't-care.
                                                                                utf8byte        =>  byteoffset_of__string_to_replace
                                                                                                + string::length_in_bytes replacement_string
                                                                              })
                                                                              ->
                                                                              { utf8byte_firstcol_on_screen => first_screencol_beyond__replacement_string,
                                                                                ...
                                                                              };

                                                                            substitutions_done  :=  *substitutions_done + 1;

                                                                            last_match          :=  { row,
                                                                                                      col => first_screencol_beyond__replacement_string
                                                                                                    };

                                                                            do_next_match { textlines => updated_textlines,
                                                                                            row,
                                                                                            col => first_screencol_beyond__replacement_string
                                                                                          };

                                                                        };

                                                                    NULL =>      do_next_match { textlines,  row => row + 1,  col => 0 };                       # No more matches on this line, search next line for matches to 'string_to_match'.
                                                                esac;

                                                            fi;
                                                        };                                                                                                      # fun do_next_match
                                                end;                                                                                                            # where

                                            _   =>  WORK  [ ];                                                                                                  # Not possible.
                                        esac;
                                    };                                                                                                                          # "fun replace_string'", with string_to_replace locked in.

                                replace_string__editfn'                                                                                                         # This (second-level) editfn will query for  replacement_string.
                                    =
                                    mt::EDITFN (
                                      mt::PLAIN_EDITFN
                                        {
                                          name   =>  "replace_string'",
                                          doc    =>  "Replace one string by another, querying user y-or-n for each substitution.",
                                          args   =>  [ mt::STRING { prompt => sprintf "Query replace %s by: " string_to_replace,
                                                                    doc    => sprintf "Replacement string for %s throughout rest of file (per interactive y/n go-aheads)." string_to_replace
                                                                  }
                                                     ],
                                          editfn =>  replace_string'
                                        }
                                    );

                                WORK  [ mt::EDITFN_TO_INVOKE  replace_string__editfn'                                                                           # Submit query for  replacement_string.
                                      ];
                            };

                        _ => WORK  [ ];                                                                                                                         # Not possible.
                    esac;
                fi;                                                                                                                                             # readonly.
            };                                                                                                                                                  # Outermost (public) "fun replace_string"
        replace_string__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "replace_string",
                  doc    =>  "Replace one string by another through rest of file.",
                  args   =>  [ mt::STRING { prompt => "Query replace: ",
                                            doc    => "String to replace throughout rest of file (per interactive y/n go-aheads)."
                                          }
                             ],
                  editfn =>  replace_string
                }
              );                                my _ =
        mt::note_editfn  replace_string__editfn;


        fun just_one_space (arg:        mt::Editfn_In)                                                          # Replace whitespace string under cursor with a single blank. This is basically identical to delete_whitespace except for the added blank.
            :                           mt::Editfn_Out                                                          # 
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;


                point -> { row, col };
                #
                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                case (nl::find (textlines, line_key))
                    #
                    THE textline
                        =>
                        {   text         =  mt::visible_line  textline;
                            chomped_text =  string::chomp     text;

                            (string::expand_tabs_and_control_chars
                              {
                                utf8text        =>  chomped_text,
                                startcol        =>  0,
                                screencol1      =>  col,
                                screencol2      => -1,                                                  # Don't-care.
                                utf8byte        => -1                                                   # Don't-care.
                              })
                              ->
                              { screentext_length_in_screencols   => cols,
                                #
                                screencol1_byteoffset_in_utf8text => byteoffset,
                                screencol1_bytescount_in_utf8text => bytescount,
                                ...
                              };

                            if (col >= cols)
                                #
                                WORK [ ];                                                               # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either.

                            elif (char::is_space( string::get_byte_as_char( chomped_text, byteoffset ))) 
                                                                                                        # Cursor is on an existing whitespace char.  Excise it and neighboring whitespace by replacing the line with the concatenation of the whitespace-trimmed substrings preceding and following the char, with a single blank between them.
                                text_before_point
                                    =
                                    string::substring
                                      (
                                        chomped_text,                                                   # String from which to extract substring.
                                        0,                                                              # The substring we want starts at offset 0.
                                        byteoffset                                                      # The substring we want runs to location of cursor.
                                      );

                                text_beyond_point
                                    =
                                    string::extract
                                      (
                                        chomped_text,                                                   # String from which to extract substring.
                                        byteoffset + bytescount,                                        # Substring starts immediately after the byte(s) under the cursor.      (Currently all char::is_space-recognized whitespace chars are one byte long, so 'bytescount' will always be 1 here.) XXX SUCKO FIXME: Should support other UTF-8 whitespace.
                                        NULL                                                            # Substring runs to end of 'chomped_text'.
                                      );

                                leading_text  =  string::drop_trailing_whitespace  text_before_point;
                                trailing_text =  string::drop_leading_whitespace   text_beyond_point;

                                updated_text    =  string::cat [ leading_text,
                                                                 " ",
                                                                 trailing_text,
                                                                 text == chomped_text  ??  ""  ::  "\n"
                                                               ];

                                updated_text    =  mt::MONOLINE   { string =>  updated_text,
                                                                    prefix =>  NULL
                                                                  };

                                updated_textlines                                                       # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                    =
                                    (nl::remove (textlines, line_key))
                                    except _ = textlines;                                               # This will happen if there is no line 'line_key' in textlines.

                                updated_textlines                                                       # Now insert updated line.
                                    =
                                    nl::set (updated_textlines, line_key, updated_text);

                                (string::expand_tabs_and_control_chars                                  # Figure screen column for inserted blank.
                                  {
                                    utf8text    =>  leading_text,
                                    startcol    =>  0,
                                    screencol1  => -1,                                                  # Don't-care.
                                    screencol2  => -1,                                                  # Don't-care.
                                    utf8byte    => -1                                                   # Don't-care.
                                  })
                                  ->
                                  { screentext_length_in_screencols,
                                    ...
                                  };

                                WORK  [ mt::TEXTLINES updated_textlines,
                                        mt::POINT     { row, col => screentext_length_in_screencols }   # Leave cursor on the single blank.
                                      ];
                            else
                                WORK [ ];                                                               # Cursor is on non-whitespace char.  Don't fail, but don't do anything either.
                            fi;         
                        };

                    NULL     => WORK [ ];                                                                       # Cursor is on non-existent line.  Don't fail, but don't do anything either.
                esac;
            };
        just_one_space__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "just_one_space",
                  doc    =>  "Replace whitespace string under cursor with a single blank.",
                  args   =>  [ ],
                  editfn =>  just_one_space
                }
              );                                my _ =
        mt::note_editfn  just_one_space__editfn;


        fun delete_whitespace (arg:     mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;


                point -> { row, col };
                #
                line_key = row;                                                                                 # Internally lines are numbered 0->(N-1) (but we display them to user as 1-N).

                case (nl::find (textlines, line_key))
                    #
                    THE textline
                        =>
                        {   text         =  mt::visible_line textline; 
                            chomped_text =  string::chomp    text;
                            #
                            (string::expand_tabs_and_control_chars
                              {
                                utf8text        =>  chomped_text,
                                startcol        =>  0,
                                screencol1      =>  col,
                                screencol2      => -1,                                                  # Don't-care.
                                utf8byte        => -1                                                   # Don't-care.
                              })
                              ->
                              { screentext_length_in_screencols   => cols,
                                #
                                screencol1_byteoffset_in_utf8text => byteoffset,
                                screencol1_bytescount_in_utf8text => bytescount,
                                ...
                              };

                            if (col >= cols)
                                #
                                WORK [ ];                                                               # Cursor is on non-existent char past end of existing line.  Don't fail, but don't do anything either.

                            elif (char::is_space( string::get_byte_as_char( chomped_text, byteoffset ))) 
                                                                                                        # Cursor is on an existing whitespace char.  Excise it and neighboring whitespace by replacing the line with the concatenation of the whitespace-trimmed substrings preceding and following the char.
                                text_before_point
                                    =
                                    string::substring
                                      (
                                        chomped_text,                                                   # String from which to extract substring.
                                        0,                                                              # The substring we want starts at offset 0.
                                        byteoffset                                                      # The substring we want runs to location of cursor.
                                      );

                                text_beyond_point
                                    =
                                    string::extract
                                      (
                                        chomped_text,                                                   # String from which to extract substring.
                                        byteoffset + bytescount,                                        # Substring starts immediately after the byte(s) under the cursor.      (Currently all char::is_space-recognized whitespace chars are one byte long, so 'bytescount' will always be 1 here.) XXX SUCKO FIXME: Should support other UTF-8 whitespace.
                                        NULL                                                            # Substring runs to end of 'chomped_text'.
                                      );

                                leading_text  =  string::drop_trailing_whitespace  text_before_point;
                                trailing_text =  string::drop_leading_whitespace   text_beyond_point;

                                updated_text    =  string::cat [ leading_text,
                                                                 trailing_text,
                                                                 text == chomped_text ?? "" :: "\n"
                                                               ];

                                updated_text    =  mt::MONOLINE   { string =>  updated_text,
                                                                    prefix =>  NULL
                                                                  };

                                updated_textlines                                                       # First remove existing line -- nl::set does NOT remove any previous line at that key.
                                    =
                                    (nl::remove (textlines, line_key))
                                    except _ = textlines;                                               # This will happen if there is no line 'line_key' in textlines.

                                updated_textlines                                                       # Now insert updated line.
                                    =
                                    nl::set (updated_textlines, line_key, updated_text);

                                (string::expand_tabs_and_control_chars                                  # Figure screen column for start of trailing_text.
                                  {
                                    utf8text    =>  leading_text,
                                    startcol    =>  0,
                                    screencol1  => -1,                                                  # Don't-care.
                                    screencol2  => -1,                                                  # Don't-care.
                                    utf8byte    => -1                                                   # Don't-care.
                                  })
                                  ->
                                  { screentext_length_in_screencols,
                                    ...
                                  };

                                WORK  [ mt::TEXTLINES updated_textlines,
                                        mt::POINT     { row, col => screentext_length_in_screencols }   # Leave cursor on start of trailing_test.
                                      ];
                            else
                                WORK [ ];                                                               # Cursor is on non-whitespace char.  Don't fail, but don't do anything either.
                            fi;         
                        };

                    NULL     => WORK [ ];                                                                       # Cursor is on non-existent line.  Don't fail, but don't do anything either.
                esac;
            };
        delete_whitespace__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "delete_whitespace",
                  doc    =>  "Kill all whitespace under cursor.",
                  args   =>  [ ],
                  editfn =>  delete_whitespace
                }
              );                                my _ =
        mt::note_editfn  delete_whitespace__editfn;


        fun execute_extended_command (arg:      mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                case args
                    #
                    [ mt::STRING_ARG { arg => commandname, ... } ]
                        =>
                        {
                            WORK  [ mt::EXECUTE_COMMAND  commandname
                                  ];
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        execute_extended_command__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "execute_extended_command",
                  doc    =>  "Execute a command, reading its name via the modeline.",
                  args   =>  [ mt::COMMANDNAME { prompt => "M-x ", doc => "Name of command to execute" }  ],
                  editfn =>  execute_extended_command
                }
              );                                my _ =
        mt::note_editfn  execute_extended_command__editfn;


        fun n_cols_of_leading_whitespace (n: Int)
            =
            {   tabs   = n / 8;
                blanks = n % 8;

                string::repeat ("\t",tabs)
                + 
                string::repeat (" ", blanks);
            };

        fun indent_rigidly (arg:                mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                if readonly
                    #
                    FAIL "Buffer is read-only";
                else
                    mill_to_millboss
                        ->
                        mt::MILL_TO_MILLBOSS  eb;


                    case mark
                        #
                        NULL => FAIL "Mark is not set";                                                                                 # Can't kill region when mark isn't set!

                        THE mark
                            =>
                            {
                                # Indent rows between 'mark' and 'point.
                                # We ignore their 'col' components and just
                                # indent entire lines.  (This might be a mistake,
                                # since being able to indent a right-side comment
                                # column without touching the  left-side code
                                # column might be useful.)

                                cols_to_indent
                                    =
                                    case numeric_prefix
                                        #
                                        THE indent =>  indent;
                                        NULL       =>  4;
                                    esac; 

                                my (first_row, final_row)
                                    =
                                    if (mark.row < point.row)   (mark.row, point.row);
                                    else                        (point.row, mark.row); 
                                    fi;

                                max_key  =  case (nl::max_key  textlines)
                                                #
                                                THE max_key => max_key;
                                                NULL        => 0;                                               # We don't expect this.
                                            esac;

                                first_row = min (first_row, max_key);                                           # A little sanity checking -- make sure first_row and final_row correspond to actual text in 'textlines'.
                                final_row = min (final_row, max_key);

                                textlines
                                    =
                                    indent_affected_lines (first_row, textlines)
                                    where
                                        fun indent_affected_lines (row, textlines)
                                            =
                                            if (row > final_row)
                                                #
                                                textlines;
                                            else
                                                text          =  mt::findline (textlines, row);                                                 # Get line to indent.
                                                chomped_text  =  string::chomp  text;                                                           # Drop terminal newline (if any).
                                                chomped_text' =  string::drop_leading_whitespace chomped_text;                                  # Get version of 'chomped_text' without any leading whitespace.

                                                len  =  string::length_in_bytes  chomped_text ;                                                 # Get leading whitespace.  A regex might be simpler.  :-)
                                                len' =  string::length_in_bytes  chomped_text';                                                 #
                                                                                                                                                #
                                                bytes_of_leading_whitespace =  len - len';                                                      #
                                                                                                                                                #
                                                leading_whitespace = string::substring (chomped_text, 0, bytes_of_leading_whitespace);          #

                                                (string::expand_tabs_and_control_chars                                                          # Figure how many columns of leading whitespace we have.
                                                  {
                                                    utf8text    =>  leading_whitespace,
                                                    startcol    =>   0,
                                                    screencol1  =>  -1,                                                                         # Don't-care.
                                                    screencol2  =>  -1,                                                                         # Don't-care.
                                                    utf8byte    =>  -1                                                                          # Don't-care.
                                                  })
                                                  ->
                                                  { screentext_length_in_screencols => cols_of_leading_whitespace,
                                                    ...
                                                  };

                                                new_cols_of_leading_whitespace                                                                  # Figure how many columns of leading whitespace we want.
                                                    =
                                                    cols_of_leading_whitespace + cols_to_indent;

                                                new_cols_of_leading_whitespace                                                                  # Keep leading-whitespace length from going negative.
                                                    =
                                                    max (0, new_cols_of_leading_whitespace);

                                                new_leading_whitespace =  n_cols_of_leading_whitespace  new_cols_of_leading_whitespace;         # Synthesize replacement leading-whitespace string.

                                                new_text = new_leading_whitespace + chomped_text' + (text == chomped_text ?? "" :: "\n");       # Synthesize complete replacement line.

                                                new_text = mt::MONOLINE   { string =>  new_text,
                                                                            prefix =>  NULL
                                                                          };

                                                textlines = nl::remove (textlines, row);                                                        # Synthesize new 'textlines' with line replaced.
                                                textlines = nl::set    (textlines, row, new_text);

                                                indent_affected_lines (row + 1, textlines);                                                     # Loop to do next affected line.
                                            fi;
                                    end;


                                WORK  [ mt::TEXTLINES textlines
                                      ];
                            };
                    esac;
                fi;
            };
        indent_rigidly__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "indent_rigidly",
                  doc    =>  "Indent region by NUMERIC_PREFIX columns. (Can be negative.)",
                  args   =>  [ ],
                  editfn =>  indent_rigidly
                }
              );                                my _ =
        mt::note_editfn  indent_rigidly__editfn;


        fun commence_keystroke_macro (arg:      mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                WORK  [ mt::COMMENCE_KMACRO,
                        mt::MODELINE_MESSAGE "Defining keystroke macro..."
                      ];
            };
        commence_keystroke_macro__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "commence_keystroke_macro",
                  doc    =>  "Begin definition of keystroke macro.",
                  args   =>  [ ],
                  editfn =>  commence_keystroke_macro
                }
              );                                my _ =
        mt::note_editfn  commence_keystroke_macro__editfn;


        fun conclude_keystroke_macro (arg:      mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                global_keystroke_macro_state
                    =
                    kmj::get_or_make__global_keystroke_macro_state
                        #
                        widget_to_guiboss.g;

                WORK  [  mt::CONCLUDE_KMACRO,
                         mt::MODELINE_MESSAGE "Keystroke macro defined."
                      ];
            };
        conclude_keystroke_macro__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "conclude_keystroke_macro",
                  doc    =>  "Close definition of keystroke macro.",
                  args   =>  [ ],
                  editfn =>  conclude_keystroke_macro
                }
              );                                my _ =
        mt::note_editfn  conclude_keystroke_macro__editfn;


        fun activate_keystroke_macro (arg:      mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                repeat_factor
                    =
                    case numeric_prefix
                        #
                        THE repeat_factor =>  max (1, repeat_factor);
                        NULL              =>  1;
                    esac;

                WORK  [ mt::ACTIVATE_KMACRO  repeat_factor
                      ];
            };
        activate_keystroke_macro__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "activate_keystroke_macro",
                  doc    =>  "End definition of keystroke macro and invoke it.",
                  args   =>  [ ],
                  editfn =>  activate_keystroke_macro
                }
              );                                my _ =
        mt::note_editfn  activate_keystroke_macro__editfn;


        fun goto_line (arg:                     mt::Editfn_In)                                                  # 
            :                                   mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                case args
                    #
                    [ mt::STRING_ARG { arg => line_number, ... } ]                                              # User-entered line number in buffer. NB: At the user level lines are numbered 1->N not 0->(N-1).
                        =>
                        {   line1    =  case (int::from_string line_number)                                     # 1-based line number.  This is what we use at the GUI display level.
                                            #
                                            THE i => i;
                                            NULL  => 1;
                                        esac;

                            line0    =  line1 - 1;                                                              # 0-based line number.  This is what we use internally. 

                            line0    =  max (0, line0);                                                         # Do a little input validation:  Silently round negative line numbers up to 0.

                            max_key  =  case (nl::max_key  textlines)
                                            #
                                            THE max_key => max_key;
                                            NULL            => 0;                                               # We don't expect this.
                                        esac;


                            line0    =  min (max_key, line0);                                                   # More input validation: Silently round too-large line numbers down to last line in buffer.

                            WORK  [ mt::POINT       { row => line0,  col => 0 }
                                  ];
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        goto_line__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "goto_line",
                  doc    =>  "Prompt for line number then move cursor to that line.",
                  editfn =>  goto_line,
                  #
                  args   =>  [ mt::STRING { prompt => "Goto line: ",
                                            doc    => "Line number (1->N) to which cursor should be moved."
                                          }
                             ]
                }
              );                                my _ =
        mt::note_editfn  goto_line__editfn;


        fun toggle_readonly (arg:       mt::Editfn_In)                                                          # Emacs uses "read-only" not "readonly" but I prefer to collapse it because "toggle_readonly" parses obviously bug "toggle_read_only" requires more work to read.
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                WORK  [ mt::READONLY (not readonly)                                                             # 
                      ];
            };
        toggle_readonly__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "toggle_readonly",
                  doc    =>  "Reverse readonly flag on current buffer.",
                  args   =>  [ ],
                  editfn =>  toggle_readonly
                }
              );                                my _ =
        mt::note_editfn  toggle_readonly__editfn;


        fun isearch_forward (arg:       mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                case args
                    #
                    [ mt::INCREMENTAL_STRING_ARG { stage, arg => searchstring, ... } ]
                        =>
                        {
                            search_start                                                                        # On mt::INITIAL call 'point' is unchanged. On subsequent calls 'point' is updated per incremental search, but original 'point' value is saved in 'lastmark'.
                                =
                                case (stage, lastmark)
                                    #
                                    (mt::INITIAL,  _)   =>  point;
                                    (_, THE lastmark)   =>  lastmark;
                                    _                   =>  point;                                              # Shouldn't happen.
                                esac;
                                                                                                                # See if we can find 'searchstring in 'textlines' starting at 'search_start'.

                            lastline =  case (nl::max_key textlines)
                                            #
                                            THE maxkey => maxkey;
                                            NULL       => -1;
                                        esac;

                            thisline =  search_start.row;

                            my { newmark, newpoint }
                                =
                                find_match search_start
                                where
                                    search_line =  string::find_substring'  searchstring;                       # Set up for Knuth-Morris-Pratt searching for 'searchstring'.  Internally, this preconstructs the required table.
                                                                                                                # Note that our approach here won't match a string spanning more than one line -- i.e., one with embedded newlines. I can't remember the last time I wanted to do such a search, so I'm not sweating that right now. -- 2015-06-20 CrT

                                    searchstring_length_in_bytes
                                        =
                                        string::length_in_bytes  searchstring;

                                    fun find_match (point: g2d::Point)                                          # Search through 'textlines' for first match to 'searchstring', starting at 'point'.
                                        =
                                        {   line_number = point.row;
                                            #
                                            if (line_number > lastline) { newmark => NULL, newpoint => NULL };  # Didn't find searchstring anywhere, leave 'point' where it started.
                                            else
                                                line =  mt::findline (textlines, line_number);

                                                byteoffset                                                      # Screen col point.col as a byte offset into utf8-encoded 'line'.
                                                    =
                                                    case point.col
                                                        #
                                                        0 => 0;                                                 # Screen column zero is always byte offset zero.  This is worth special-casing because it is the typical case and the alternative is expensive.

                                                        c =>    {   (string::expand_tabs_and_control_chars
                                                                      {
                                                                        utf8text        =>  line,
                                                                        startcol        =>  0,
                                                                        screencol1      =>  c,                  # This is the one we care about.
                                                                        screencol2      => -1,                  # Don't-care.
                                                                        utf8byte        => -1                   # Don't-care.
                                                                      })
                                                                      ->
                                                                      { screencol1_byteoffset_in_utf8text,      # 
                                                                        ...
                                                                      };

                                                                    screencol1_byteoffset_in_utf8text;
                                                                };
                                                    esac;

                                                case (search_line (line, byteoffset))
                                                    #   
                                                    NULL => find_match  { row => point.row + 1,                 # Didn't find 'searchstring' on this line, so try next line (if any).
                                                                          col => 0
                                                                        };

                                                    THE byteoffset                                              # Success -- found 'searchstring" within 'line'.
                                                        =>
                                                        {   (string::expand_tabs_and_control_chars              # Now we need to convert the 'byteoffset' into utf8-encoded 'line' into a screen column suitable for 'mark'.
                                                              {
                                                                utf8text        =>  line,
                                                                startcol        =>  0,
                                                                screencol1      => -1,                          # Don't-care.
                                                                screencol2      => -1,                          # Don't-care.
                                                                utf8byte        =>  byteoffset
                                                              })
                                                              ->
                                                              { utf8byte_firstcol_on_screen => markcol,         # Screen column at which utf8text byteoffset 'utf8byte' begins.  Note that utf8byte may be (e.g.) somewhere in the middle of a tab, so computing this value is nontrivial. 
                                                                ...
                                                              };

                                                            (string::expand_tabs_and_control_chars              # Now we need to convert the 'byteoffset' into utf8-encoded 'line' into a screen column suitable for 'point'.
                                                              {
                                                                utf8text        =>  line,
                                                                startcol        =>  0,
                                                                screencol1      => -1,                          # Don't-care.
                                                                screencol2      => -1,                          # Don't-care.
                                                                #
                                                                utf8byte        =>  byteoffset + searchstring_length_in_bytes
                                                              })
                                                              ->
                                                              { utf8byte_firstcol_on_screen => pointcol,
                                                                ...
                                                              };

                                                            { newmark  => THE { row => line_number,             # 
                                                                                col => markcol
                                                                              },
                                                              newpoint => THE { row => line_number,             # 
                                                                                col => pointcol
                                                                              }
                                                            };  
                                                        };
                                                esac;
                                            fi;
                                        };
                                end;

                            result =    [ ];

                            result =    case newpoint
                                            #
                                            THE point   =>  (mt::POINT         point ) !   result;              # Move 'point' (==cursor) to screen address just past end of string match.
                                            NULL        =>                                 result;
                                        esac;

                            result =    case newmark
                                            #
                                            THE mark    =>  (mt::MARK     (THE mark )) !   result;              # Move 'mark' to screen address corresponding to start of string match.
                                            NULL        =>                                 result;
                                        esac;

                            result =    case stage                                                              # If this is our mt::INITIAL call, save 'point' in 'lastmark' because we need to know initial value of 'point' in later calls.
                                            #
                                            mt::INITIAL =>  (mt::LASTMARK (THE point))  !  result;
                                            _           =>                                 result;
                                        esac; 

                            WORK  result;
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        isearch_forward__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "isearch_forward",
                  doc    =>  "Incrementally search forward for search string as entered.",
                  args   =>  [ mt::INCREMENTAL_STRING { prompt => "I-search", doc => "String to search for" }  ],
                  editfn =>  isearch_forward
                }
              );


        fun isearch_backward (arg:      mt::Editfn_In)                                                          # 
            :                           mt::Editfn_Out
            =
            {   arg ->    { args:                       List( mt::Prompted_Arg ),                               # Args read interactively from user per our __editfn.args spec.
                            textlines:                  mt::Textlines,
                            point:                      g2d::Point,                                             # As in Point_And_Mark.
                            mark:                       Null_Or(g2d::Point),                                    # 
                            lastmark:                   Null_Or(g2d::Point),                                    # 
                            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.
                            readonly:                   Bool,                                                   # TRUE iff contents of textmill are currently marked as read-only.
                            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.
                            edit_history:               mt::Edit_History,                                       # Recent visible states of textmill, to support undo functionality.
                            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.
                            mill_id:                    Id,                                                     # Id  of mill for which this editfn is being invoked.
                            to:                         Replyqueue,                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                            widget_to_guiboss:          gt::Widget_To_Guiboss,                                  # 
                            mill_to_millboss:           mt::Mill_To_Millboss,
                            #
                            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.
                            #
                            mill_extension_state:       Crypt,
                            textpane_to_textmill:       mt::Textpane_To_Textmill,                               # NB: We're running in textmill's microthread to guarantee atomicity, so invoking blocking textpane_to_textmill.* fns is likely to deadlock.  See Note[1].
                            mode_to_drawpane:           Null_Or( m2d::Mode_To_Drawpane ),                       # This will be non-NULL iff we specified a non-NULL draw_*_fn in our mt::PANEMODE value at bottom of file (which we do not do in this package).
                            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.
                          };

                mill_to_millboss
                    ->
                    mt::MILL_TO_MILLBOSS  eb;

                case args
                    #
                    [ mt::INCREMENTAL_STRING_ARG { stage, arg => searchstring, ... } ]
                        =>
                        {
                            search_start                                                                        # On mt::INITIAL call 'point' is unchanged. On subsequent calls 'point' is updated per incremental search, but original 'point' value is saved in 'lastmark'.
                                =
                                case (stage, lastmark)
                                    #
                                    (mt::INITIAL,  _)   =>  point;
                                    (_, THE lastmark)   =>  lastmark;
                                    _                   =>  point;                                              # Shouldn't happen.
                                esac;
                                                                                                                # See if we can find 'searchstring in 'textlines' starting at 'search_start'.

                            lastline =  case (nl::max_key textlines)
                                            #
                                            THE maxkey => maxkey;
                                            NULL       => -1;
                                        esac;

                            thisline =  search_start.row;

                            my (newmark, newpoint)
                                =
                                find_match (search_start.row, THE search_start.col)
                                where
                                    search_line =  string::find_substring_backward'  searchstring;              # Set up for Knuth-Morris-Pratt searching for 'searchstring'.  Internally, this preconstructs the required table.
                                                                                                                # Note that our approach here won't match a string spanning more than one line -- i.e., one with embedded newlines. I can't remember the last time I wanted to do such a search, so I'm not sweating that right now. -- 2015-06-20 CrT
                                    searchstring_length_in_bytes
                                        =
                                        string::length_in_bytes  searchstring;

                                    fun find_match                                                              # Search through 'textlines' for first PRECEDING match to 'searchstring'.
                                          (
                                            line_number:        Int,                                            # Next line to search, as an index into 'textlines'.
                                            column:             Null_Or(Int)                                    # Screen column at which to start searching line.  We take NULL to mean start search at end of line.
                                          )
                                        =
                                        {   if (line_number < 0)  (NULL,NULL);                                  # Didn't find searchstring anywhere, leave 'point' where it started.
                                            else
                                                line =  mt::findline (textlines, line_number);

                                                eol =   string::length_in_bytes line                            # Compute last byteoffset in 'line' at which a match is possible.
                                                        -
                                                        searchstring_length_in_bytes;

                                                col =   case column
                                                            #
                                                            NULL =>     eol;                                    # Start searching at end of line.

                                                            THE c =>    {   (string::expand_tabs_and_control_chars
                                                                              {
                                                                                utf8text        =>  line,
                                                                                startcol        =>  0,
                                                                                screencol1      =>  c,          # This is the one we care about.
                                                                                screencol2      => -1,          # Don't-care.
                                                                                utf8byte        => -1           # Don't-care.
                                                                              })
                                                                              ->
                                                                              { screencol1_byteoffset_in_utf8text,
                                                                                ...
                                                                              };

                                                                            screencol1_byteoffset_in_utf8text;  # 
                                                                        };
                                                        esac;

                                                col =   min (col, eol);                                         # Keep search within actual available bytes. :-)   (We allow the screen cursor to wander off beyond the current physical end of line.)

                                                case (search_line (line, col))
                                                    #   
                                                    NULL => find_match  ( line_number - 1,                      # Didn't find 'searchstring' on this line, so try previous line (if any).
                                                                          NULL                                  # Search previous line starting at end.
                                                                        );

                                                    THE byteoffset                                              # Success -- found 'searchstring" within 'line'.
                                                        =>
                                                        {   (string::expand_tabs_and_control_chars              # Now we need to convert the 'byteoffset' into utf8-encoded 'line' into a screen column suitable for 'mark'.
                                                              {
                                                                utf8text        =>  line,
                                                                startcol        =>  0,
                                                                screencol1      => -1,                          # Don't-care.
                                                                screencol2      => -1,                          # Don't-care.
                                                                utf8byte        =>  byteoffset
                                                              })
                                                              ->
                                                              { utf8byte_firstcol_on_screen => markcol,         # Screen column at which utf8text byteoffset 'utf8byte' begins.  Note that utf8byte may be (e.g.) somewhere in the middle of a tab, so computing this value is nontrivial. 
                                                                ...
                                                              };

                                                            (string::expand_tabs_and_control_chars              # Now we need to convert the 'byteoffset' into utf8-encoded 'line' into a screen column suitable for 'point'.
                                                              {
                                                                utf8text        =>  line,
                                                                startcol        =>  0,
                                                                screencol1      => -1,                          # Don't-care.
                                                                screencol2      => -1,                          # Don't-care.
                                                                #
                                                                utf8byte        =>  byteoffset + searchstring_length_in_bytes
                                                              })
                                                              ->
                                                              { utf8byte_firstcol_on_screen => pointcol,
                                                                ...
                                                              };

                                                            ( THE { row => line_number,                         # newmark
                                                                    col => markcol
                                                                  },
                                                              THE { row => line_number,                         # newpoint
                                                                    col => pointcol
                                                                  }
                                                            );  
                                                        };
                                                esac;
                                            fi;
                                        };
                                end;

                            result =    [ ];

                            result =    case newpoint
                                            #
                                            THE point   =>  (mt::POINT         point ) !   result;              # Move 'point' (==cursor) to screen address just past end of string match.
                                            NULL        =>                                 result;
                                        esac;

                            result =    case newmark
                                            #
                                            THE mark    =>  (mt::MARK     (THE mark )) !   result;              # Move 'mark' to screen address corresponding to start of string match.
                                            NULL        =>                                 result;
                                        esac;

                            result =    case stage                                                              # If this is our mt::INITIAL call, save 'point' in 'lastmark' because we need to know initial value of 'point' in later calls.
                                            #
                                            mt::INITIAL =>  (mt::LASTMARK (THE point))  !  result;
                                            _           =>                                 result;
                                        esac; 

                            WORK   result;
                        };

                    _ => FAIL "<impossible>";                                                                   # Fail -- bad arglist.  This shouldn't be possible, textpane.pkg should always construct a good 'args' list before calling us.
                esac;
            };
        isearch_backward__editfn
            =
            mt::EDITFN (
              mt::PLAIN_EDITFN
                {
                  name   =>  "isearch_backward",
                  doc    =>  "Incrementally search backward for search string as entered.",
                  args   =>  [ mt::INCREMENTAL_STRING { prompt => "I-search", doc => "String to search for" }  ],
                  editfn =>  isearch_backward
                }
              );                                my _ =
        mt::note_editfn  isearch_backward__editfn;


        # Conventions on writing keymap strings:
        #
        # The prefixes
        #
        #     Super = 's-'
        #     Meta  = 'M-'
        #     Ctrl  = 'C-'
        #     Shift = 'S-'
        #
        # should always be written in the order
        #
        #     s-C-M-S-x
        #
        # Write "SPC" instead of " ".
        # Write "TAB" instead of "C-i".
        # Write "RET" instead of "C-m".
        # Write "ESC" instead of "C-[".
        #
        # Don't try to define    "C-u": Its meaning is hardwired in   src/lib/x-kit/widget/edit/textpane.pkg
        # Similarly, "ESC" will usually be interpreted as Meta.
        #
        # Use "S-" only with keys whose names are in angle brackets.
        #
        # The list of such keys is defined in
        #     src/lib/x-kit/xclient/src/window/keysym-to-ascii.pkg
        # viz:
        #     "<backspace>";            # Backspace key.
        #     "<pause>";                # 
        #     "<scrollLock>";           # Scroll Lock key.
        #     "<sysReq>";               # SysReq key.
        #     "<home>";                 # Home key.             # We use all-lowercase all through here to match emacs tradition.
        #     "<left>";                 # Left-arrow key.
        #     "<up>";                   # Up-arrow key.
        #     "<right>";                # Right-arrow key.
        #     "<down>";                 # Down-arrow key.
        #     "<pageUp>";               # Page Up key.
        #     "<pageDown>";             # Page Down key.
        #     "<end>";                  # End key.
        #     "<begin>";                # Begin key.
        #     "<select>";               # Select key.
        #     "<printScr>";             # Print-screen key.
        #     "<execute>";              # Execute key.
        #     "<insert>";               # Insert key.
        #     "<undo>";                 # Undo key.
        #     "<redo>";                 # Redo key.
        #     "<menu>";                 # Menu key.
        #     "<find>";                 # Find key.
        #     "<cancel>";               # Cancel key.
        #     "<help>";                 # Help key.
        #     "<break>";                # Break key.
        #     "<numLock>";              # Num Lock key.
        #     "<f1>";                   # F1 key.
        #     "<f2>";                   # F2 key.
        #     "<f3>";                   # F3 key.
        #     "<f4>";                   # F4 key.
        #     "<f5>";                   # F5 key.
        #     "<f6>";                   # F6 key.
        #     "<f7>";                   # F7 key.
        #     "<f8>";                   # F8 key.
        #     "<f9>";                   # F9 key.
        #     "<f10>";                  # F10 key.
        #     "<f11>";                  # F11 key.
        #     "<f12>";                  # F12 key.
        #     "<f13>";                  # F13 key.
        #     "<f14>";                  # F14 key.
        #     "<f15>";                  # F15 key.
        #     "<f16>";                  # F16 key.
        #     "<f17>";                  # F17 key.
        #     "<f18>";                  # F18 key.
        #     "<f19>";                  # F19 key.
        #     "<f20>";                  # F20 key.
        #     "<f21>";                  # F21 key.
        #     "<f22>";                  # F22 key.
        #     "<f23>";                  # F23 key.
        #     "<f24>";                  # F24 key.
        #     "<f25>";                  # F25 key.
        #     "<f26>";                  # F26 key.
        #     "<f27>";                  # F27 key.
        #     "<f28>";                  # F28 key.
        #     "<f29>";                  # F29 key.
        #     "<f30>";                  # F30 key.
        #     "<f31>";                  # F31 key.
        #     "<f32>";                  # F32 key.
        #     "<f33>";                  # F33 key.
        #     "<f34>";                  # F34 key.
        #     "<f35>";                  # F35 key.
        #     "<leftShift>";            # Left Shift key.
        #     "<rightShift>";           # Right Shift key.
        #     "<leftCtrl>";             # Left Ctrl key.
        #     "<rightCtrl>";            # Right Ctrl key.
        #     "<capsLock>";             # Caps Lock key.
        #     "<leftMeta>";             # Left Meta key.
        #     "<rightMeta>";            # Right Meta key.
        #     "<leftAlt>";              # Left Alt key.
        #     "<rightAlt>";             # Right Alt key.
        #     "<cmd>";                  # Windows/Apple key.
        #     "<delete>";               # Delete key.


        fundamental_mode_keymap
            =
            keymap
            where
                keymap = mt::empty_keymap;
                #
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-/"              ],      undo__editfn                            );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-@"              ],      set_mark_command__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-SPC"            ],      set_mark_command__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-_"              ],      undo__editfn                            );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-a"              ],      move_beginning_of_line__editfn          );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-b"              ],      previous_char__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-d"              ],      delete_char__editfn                     );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-e"              ],      move_end_of_line__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-f"              ],      forward_char__editfn                    );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-g"              ],      keyboard_quit__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-k"              ],      kill_line__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-l"              ],      recenter_top_bottom__editfn             );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-n"              ],      next_line__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-o"              ],      kill_whole_line__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-p"              ],      previous_line__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-q"              ],      quoted_insert__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-r"              ],      isearch_backward__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-s"              ],      isearch_forward__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-t"              ],      transpose_chars__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-v"              ],      scroll_up__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-w"              ],      kill_region__editfn                     );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "("         ],      commence_keystroke_macro__editfn        );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", ")"         ],      conclude_keystroke_macro__editfn        );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "}"         ],      rotate_panepair__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "0"         ],      delete_this_pane__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "1"         ],      delete_other_pane__editfn               );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "2"         ],      split_pane_vertically__editfn           );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "3"         ],      split_pane_horizontally__editfn         );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-b"       ],      list_mills__editfn                      );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-c"       ],      save_mills_kill_mythryl__editfn         );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-f"       ],      find_file__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-q"       ],      toggle_readonly__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-s"       ],      save_buffer__editfn                     );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "C-x"       ],      exchange_point_and_mark__editfn         );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "TAB"       ],      indent_rigidly__editfn                  );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "^"         ],      enlarge_pane__editfn                    );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "b"         ],      switch_to_mill__editfn                  );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "e"         ],      activate_keystroke_macro__editfn        );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "h"         ],      mark_whole_buffer__editfn               );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "o"         ],      other_pane__editfn                      );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "r", "SPC"  ],      point_to_register__editfn               );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "r", "g"    ],      insert_register__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "r", "j"    ],      jump_to_register__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "r", "k"    ],      kill_rectangle__editfn                  );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "r", "s"    ],      copy_to_register__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-x", "s"         ],      save_some_mills__editfn                 );
                keymap = mt::add_editfn_to_keymap (keymap, [ "C-y"              ],      yank__editfn                            );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-%"              ],      query_replace__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-<"              ],      beginning_of_buffer__editfn             );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-="              ],      count_lines_region__editfn              );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M->"              ],      end_of_buffer__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-SPC"            ],      just_one_space__editfn                  );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-\\"             ],      delete_whitespace__editfn               );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-g", "g"         ],      goto_line__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-v"              ],      scroll_down__editfn                     );
                keymap = mt::add_editfn_to_keymap (keymap, [ "M-x"              ],      execute_extended_command__editfn        );
                keymap = mt::add_editfn_to_keymap (keymap, [ "RET"              ],      newline__editfn                         );
                #
                keymap = mt::add_editfn_to_keymap (keymap, [ "<backspace>"      ],      delete_backward_char__editfn            );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<delete>"         ],      delete_char__editfn                     );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<down>"           ],      next_line__editfn                       );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<end>"            ],      move_end_of_line__editfn                );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<home>"           ],      move_beginning_of_line__editfn          );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<left>"           ],      previous_char__editfn                   );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<right>"          ],      forward_char__editfn                    );
                keymap = mt::add_editfn_to_keymap (keymap, [ "<up>"             ],      previous_line__editfn                   );

                keymap =    mt::add_editfn_to_keymap_throughout_char_range
                              {
                                keymap,
                                keymap_node =>  self_insert_command__editfn,
                                #
                                firstchar   =>  ' ',
                                lastchar    =>  '~'
                              };        
            end;

        stipulate
            #                                                                                                   # Initialize state for the fundamental-mode part of a textpane at startup.
            fun initialize_panemode_state                                                                       # Our canonical call is from textpane::startup_fn().            # textpane      is from   src/lib/x-kit/widget/edit/textpane.pkg
                  (                                                                                             # To maintain system-global state for mode use the guiboss_types::Gadget_To_Guiboss fns note_global, find_global, drop_global.
                    panemode:                           mt::Panemode,                                           # This will be fundamental_mode (below).
                    panemode_state:                     mt::Panemode_State,                                     #
                    textmill_extension:                 Null_Or( mt::Textmill_Extension ),                      #
                    panemode_initialization_options:    List(    mt::Panemode_Initialization_Option )           #
                  )
                  :             (       mt::Panemode_State,
                                        Null_Or( mt::Textmill_Extension ),
                                        List(    mt::Panemode_Initialization_Option )
                                )
                =
                {   val = { id   =>  issue_unique_id (),                                                        # Construct our state.
                            type => "fundamental_mode::PANEMODE__STATE",
                            info => "State for fundamental-mode.pkg fns",
                            data =>  FUNDAMENTAL_MODE__STATE
                          };

                    key = val.type;                                                                             # Enter our state into given mt::Panemode_State.
                    #                                                                                           #
                    panemode_state                                                                              #
                      =                                                                                         #
                      { mode => panemode_state.mode,                                                            #
                        data => sm::set (panemode_state.data, key, val)                                         #
                      };                                                                                        #

                    panemode ->  mt::PANEMODE  mm;                                                              # Let our parent panemodes also initialize.
                    #
                    case mm.parent
                        #
                        THE (parent as mt::PANEMODE p) =>  p.initialize_panemode_state (parent, panemode_state, textmill_extension, panemode_initialization_options);
                        NULL                           =>                                      (panemode_state, textmill_extension, panemode_initialization_options);
                    esac; 
                };

            fun finalize_state                                                                                  # Currently this seems to never get called.  XXX BUGGO FIXME 2015-07-11
                  (
                    panemode:           mt::Panemode,                                                           # This will be fundamental_mode (below).
                    panemode_state:     mt::Panemode_State
                  )
                  :                     Void
                =
                {   panemode ->  mt::PANEMODE  mm;                                                              # Let our parent panemodes also finalize.
                    #
                    case mm.parent
                        #
                        THE (parent as mt::PANEMODE p) =>  p.finalize_state (parent, panemode_state);
                        NULL                           =>                   (                      );
                    esac;
                };
        herein            

            fundamental_mode
                =
                mt::PANEMODE
                  {
                    id     =>   issue_unique_id (),
                    name   =>   "Fundamental",
                    doc    =>   "Root unspecialized textmill mode.",

                    keymap =>   REF fundamental_mode_keymap,
                    parent =>   NULL,

                    self_insert_command =>      self_insert_command__editfn,

                    initialize_panemode_state,
                    finalize_state,

                    drawpane_startup_fn           => NULL,
                    drawpane_shutdown_fn          => NULL,
                    drawpane_initialize_gadget_fn => NULL,
                    drawpane_redraw_request_fn    => NULL,
                    drawpane_mouse_click_fn       => NULL,
                    drawpane_mouse_drag_fn        => NULL,
                    drawpane_mouse_transit_fn     => NULL
                  };
        end;
    };
end;

###################################################################################
# Note[1]:  Deadlocks due to an imp calling its own external entrypoint.
#
# There's a pervasive problem that an imp will deadlock if it calls one of its
# own external entrypoints and waits for the result, if the entrypoint have the
# currently-standard structure
#
#   fun get_foo ():     Foo
#       =
#       {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Foo );
#           #
#           put_in_mailqueue  (request_q,
#               #
#               \\ (runstate: Runstate)
#                   =
#                   {   foo =   internal_get_foo  runstate;
#                       #
#                       put_in_oneshot (reply_oneshot, foo);
#                   }
#           );

#           get_from_oneshot  reply_oneshot;
#       };
#
# (NB: Using the alternative pass_foo() forms won't work if we're in (say)
# fundamental-mode.pkg and trying to keep atomicity.)
#
# I think the answer has to be to reformulate these entrypoints to look like
#
#   fun get_foo ():     Foo
#       =
#       if (already_running_in_own_microthread())
#           #
#           internal_get_foo (get_this_microthread's_runstate())
#       else
#           reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Foo );
#           #
#           put_in_mailqueue  (request_q,
#               #
#               \\ (runstate: Runstate)
#                   =
#                   {   foo =   internal_get_foo  runstate;
#                       #
#                       put_in_oneshot (reply_oneshot, foo);
#                   }
#           );

#           get_from_oneshot  reply_oneshot;
#       fi;
#
# This requires implementations for
#
#     already_running_in_own_microthread()
#     get_this_microthread's_runstate()
#
# The first can probably be solved by recording on mailqueues the id
# of the microthread reading from them.  Currently mailqueues may be
# read by multiple microthreads as well as written by microthreads;
# possibly we want a specialized variant which allows reads only by
# one microthread.
#
# The second can be solved by some thread-local storage arrangement
# where each imp stores its Runstate information on its microthread,
# so as to make it available at any point in computation.  IIRC
# the basic infrastructure needed to make this work is already
# floating around.
#                               -- 2015-07-23 CrT


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext