PreviousUpNext

15.4.1429  src/lib/x-kit/widget/edit/textlines-junk.pkg

## textlines-junk.pkg
#
# Support fns for manipulating textliens.

# 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 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 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 textlines_junk {                                    # 
        #
        fun normalize_point (p: g2d::Point, textlines: mt::Textlines)                                           # Returned point guaranteed to be positioned on the first screen column of its char (which, like a tab or control char, may occupy multiple screen columns).
            =
            {   line_key = p.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  =>   p.col,
                    screencol2  =>  -1,                                                                         # Don't-care.
                    utf8byte    =>  -1                                                                          # Don't-care.
                  })
                  ->
                  { screencol1_firstcol_on_screen:      Int,                                                    # First screen column for 'point'.
#                   screencol1_colcount_on_screen:      Int,
                    ...
                  };

                { row => p.row,
                  col => screencol1_firstcol_on_screen
                };
            };

        fun insert_lines                                                                                        # Utility fn.  Used in (e.g.) fundamental_mode::yank()
              {
                lines_to_insert:                        List( String ),                                         # Lines are assumed to contain no newlines internally but to each end in a newline except possibly for the last line.
                point:                                  g2d::Point,                                             # Where in textlines to insert.
                textlines:                              mt::Textlines                                           # Textlines in which to insert.
              }
            :
              { updated_textlines:                      mt::Textlines,
                point_after_inserted_text:              g2d::Point
              }
            =
            {   
                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
                  {
                    utf8text    =>  chomped_text,
                    startcol    =>  0,
                    screencol1  =>  point.col,
                    screencol2  => -1,                                                                          # Don't-care.
                    utf8byte    => -1                                                                           # Don't-care.
                  })
                  ->
                  { screencol1_byteoffset_in_utf8text:  Int,
                    ...
                  };

                textlen = string::length_in_chars  chomped_text;                                                # This logic is cut-and-pasted from default_redraw_fn in screenline.pkg -- possibly it should be shared via some package.
                                                                                                                # 
                my  { text_before_point,                                                                        # 
                      text_beyond_point                                                                         # 
                    }                                                                                           #
                    =                                                                                           #
                    if (point.col >= textlen)                                                                   #
                        #
                        { text_before_point =>  chomped_text + (string::repeat(" ", point.col-textlen   )),     # 
                          text_beyond_point =>  ""
                        };
                    else                                                                                        # Region lies entirely within input string.

                        { text_before_point =>  string::substring (chomped_text, 0                            ,  screencol1_byteoffset_in_utf8text),
                          text_beyond_point =>  string::extract   (chomped_text, screencol1_byteoffset_in_utf8text,  NULL                        )      except INDEX_OUT_OF_BOUNDS = ""
                        };
                    fi;

                my (firstline, remaininglines)
                    =
                    case lines_to_insert
                        #
                        (firstline ! remaininglines) => (firstline, reverse remaininglines);
                        []                               => raise exception DIE "impossible";                   # We never generate a ct::MULTILINE containing less than two lines.
                    esac;

                updated_firstline = text_before_point + firstline;


                my (lastline, remaininglines)
                    =
                    case remaininglines
                        #
                        (lastline ! remaininglines) => (lastline, remaininglines);
                        []                          => raise exception DIE "impossible";                        # We never generate a ct::MULTILINE containing less than two lines.
                    esac;

                lastline = string::chomp lastline;                                                              # Avoid inserting a newline in the middle of updated_lastline.

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

                updated_textlines                                                                               # Drop existing firstline, which will be replaced by updated_firstline.
                    =
                    (nl::remove (textlines, line_key));

                updated_lastline
                    =
                    lastline + text_beyond_point + (chomped_text == text ?? "" :: "\n");


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

                updated_textlines
                    =
                    nl::set (updated_textlines, line_key, updated_lastline);

                updated_textlines
                    =
                    loop (remaininglines, updated_textlines)                                                    # 
                    where
                        fun loop ([], updated_textlines)
                                =>
                                updated_textlines;

                            loop (thisline ! remaininglines,  updated_textlines)
                                =>
                                {   thisline =  mt::MONOLINE { string => thisline,  prefix => NULL };
                                    #
                                    loop (remaininglines,  nl::set (updated_textlines, line_key, thisline));
                                };
                        end;
                    end;

                updated_textlines
                    =
                    nl::set (updated_textlines, line_key, updated_firstline);


                { updated_textlines,
                  #
                  point_after_inserted_text =>  { row =>  point.row  +  (list::length lines_to_insert) - 1,
                                                  col =>  new_cursor_col
                                                }
                };
            };

        fun insert_string                                                                                               # Utility fn.  Used in (e.g.) fundamental_mode::yank()
              {
                text_to_insert:                         String,                                                         # String to insert into textlines.  String is assumed not to contain newlines.
                point:                                  g2d::Point,                                                     # Where in textlines to insert.
                textlines:                              mt::Textlines                                                   # Textlines in which to insert.
              }
            :
              { updated_textlines:                      mt::Textlines,
                point_after_inserted_text:              g2d::Point
              } 
            =   
            {   text_to_insert = string::chomp text_to_insert;
                #
                if (string::is_substring  "\n"  text_to_insert)
                    #
                    insert_lines
                      { 
                        lines_to_insert =>  string::lines  text_to_insert,
                        point,
                        textlines
                      };
                else

                    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
                      {
                        utf8text        =>  chomped_text,
                        startcol        =>  0,
                        screencol1      =>  point.col,
                        screencol2      => -1,                                                                          # Don't-care.
                        utf8byte        => -1                                                                           # Don't-care.
                      })
                      ->
                      { screencol1_byteoffset_in_utf8text:      Int,
                        ...
                      };

                    textlen = string::length_in_chars  chomped_text;                                                    # This logic is cut-and-pasted from default_redraw_fn in screenline.pkg -- possibly it should be shared via some package.
                                                                                                                        # 
                    my  { text_before_point,                                                                            # 
                          text_beyond_point                                                                             # 
                        }                                                                                               #
                        =                                                                                               #
                        if (point.col >= textlen)                                                                       #
                            #
                            { text_before_point =>  chomped_text + (string::repeat(" ", point.col-textlen   )),         # 
                              text_beyond_point =>  ""
                            };
                        else                                                                                            # Region lies entirely within input string.

                            { text_before_point =>  string::substring (chomped_text, 0                                ,  screencol1_byteoffset_in_utf8text),
                              text_beyond_point =>  string::extract   (chomped_text, screencol1_byteoffset_in_utf8text,  NULL                             )     except INDEX_OUT_OF_BOUNDS = ""
                            };
                        fi;

                    updated_line = text_before_point
                                 + text_to_insert
                                 + text_beyond_point
                                 + (text == chomped_text ?? "" :: "\n")
                                 ;

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

                    updated_textlines
                        =
                        (nl::remove (textlines, line_key));

                    updated_textlines
                        =
                        nl::set (updated_textlines, line_key, updated_line);

                    (string::expand_tabs_and_control_chars                                                              # Now to compute screen column of end of text_to_insert.
                      {
                        utf8text        =>  text_before_point + text_to_insert,
                        startcol        =>  0,
                        screencol1      =>  point.col,
                        screencol2      => -1,                                                                          # Don't-care.
                        utf8byte        => -1                                                                           # Don't-care.
                      })
                      ->
                      { screentext_length_in_screencols:        Int,
                        ...
                      };

                    { updated_textlines,
                      point_after_inserted_text => { row => point.row, col => screentext_length_in_screencols }
                    };
                fi;
            };


        fun kill_region                                                                                                 # Utility fn.  Used in (e.g.) fundamental_mode::kill_region()
              {
                mark:                                   g2d::Point,                                                     # 
                point:                                  g2d::Point,                                                     # 
                textlines:                              mt::Textlines                                                   # Textlines in which to insert.
              }
            :
              { updated_textlines:                      mt::Textlines,
                cutbuffer_contents:                     ct::Cutbuffer_Contents,
                point:                                  g2d::Point
              }
            =
            {
                # The columns for 'mark' and 'point' may be
                # somewhere odd in the middle of (e.g.) tabs,
                # so start by deriving normalized versions:
                #
                mark'  = normalize_point (mark,  textlines);
                point' = 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_chars = string::length_in_chars  chomped_text;              # 
                    utf8_len_in_bytes = string::length_in_bytes  chomped_text;                                          # 
                                                                                                                        # 
                    my  { text_before_region,                                                                           # 
                          text_within_region,                                                                           # 
                          text_beyond_region                                                                            # 
                        }                                                                                               #
                        =                                                                                               #
                        if (screencol1_byteoffset_in_utf8text >= utf8_len_in_bytes)                                     # If region lies entirely beyond actual end of line in utf8text.
                            #
                            { text_before_region =>  chomped_text + (string::repeat(" ",  screencol1_byteoffset_in_utf8text-utf8_len_in_bytes )),
                              text_within_region =>                 (string::repeat(" ", (screencol2_byteoffset_in_utf8text-screencol1_byteoffset_in_utf8text) + 1)),           # 
                              text_beyond_region =>  ""                                                                 #
                            };
                        elif (col2 >= utf8_len_in_bytes)                                                                # Region starts within utf8text string but extends beyond actual end of line in utf8text.
                            #
                            { text_before_region =>   string::substring(chomped_text, 0,  screencol1_byteoffset_in_utf8text),
                              text_within_region =>  (string::extract  (chomped_text, screencol1_byteoffset_in_utf8text,  NULL)) + (string::repeat(" ", (screencol1_byteoffset_in_utf8text-utf8_len_in_bytes) + 1)),
                              text_beyond_region =>  ""
                            };
                        else                                                                                            # Region lies entirely within input string.
                            { text_before_region =>  string::substring (chomped_text, 0                                                               ,   screencol1_byteoffset_in_utf8text),
                              text_within_region =>  string::substring (chomped_text, screencol1_byteoffset_in_utf8text                                   ,  (screencol2_byteoffset_in_utf8text + screencol2_bytescount_in_utf8text) - screencol1_byteoffset_in_utf8text),
                              text_beyond_region =>  string::extract   (chomped_text, screencol2_byteoffset_in_utf8text + screencol2_bytescount_in_utf8text,  NULL                      )
                            };
                        fi;

                    cutbuffer_contents =  ct::PARTLINE  text_within_region;

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

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

                    updated_textlines
                        =
                        (nl::remove (textlines, line_key));

                    updated_textlines
                        =
                        nl::set (updated_textlines, line_key, updated_line);

                    { updated_textlines,
                      cutbuffer_contents,
                      point =>  { row => point.row,  col => col1 }
                    };

                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' = normalize_point (first, textlines);                                                        # Construct normalized versions of first and final, where screencol is at start of char each is on.
                    final' = 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.
                        ...
                      };



                    my  { text_before_firstline_region,                                                                 # 
                          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.
                            #
                            { text_before_firstline_region =>  chomped_firsttext + (string::repeat(" ",  firstcol_byteoffset_in_firsttext - firsttext_len_in_bytes)),
                              text_within_firstline_region =>  ""
                            };
                        else                                                                                            # If start of region lies within firsttext.
                            #
                            { text_before_firstline_region =>  string::substring (chomped_firsttext, 0,   firstcol_byteoffset_in_firsttext),
                              text_within_firstline_region =>  string::extract   (chomped_firsttext, firstcol_byteoffset_in_firsttext,  NULL)
                            };
                        fi;


                    my  { text_within_finalline_region,                                                                 # 
                          text_beyond_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.
                                #
                                { text_within_finalline_region =>  chomped_finaltext + (string::repeat(" ", beyondregion_byteoffset - finaltext_len_in_bytes)),
                                  text_beyond_finalline_region =>  ""                                                   #
                                };
                            else                                                                                        # If end of region lies within finaltext.
                                #
                                { text_within_finalline_region =>  string::substring (chomped_finaltext, 0,   beyondregion_byteoffset),
                                  text_beyond_finalline_region =>  string::extract   (chomped_finaltext, beyondregion_byteoffset,  NULL)
                                };
                            fi;
                        };

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

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

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

                                    loop (thisrow + 1, text ! result);
                                fi;
                        end;

                    text_within_region
                        #
                        =  [ text_within_firstline_region + "\n" ]                                                      # We know firstline had a terminal newline, since there was at least one later line (finalline).
                        @  whole_lines_in_cut
                        @  [ text_within_finalline_region ]
                        ;

                    cutbuffer_contents =  ct::MULTILINE  text_within_region;


                    updated_textlines                                                                                   # Drop firstline.
                        =
                        (nl::remove (textlines, first'.row));

                    updated_textlines                                                                                   # Drop lines between firstline and finalline.
                        =
                        {   lastrow = final'.row - 1;
                            #
                            loop (first'.row + 1, updated_textlines)
                            where
                                fun loop (thisrow, updated_textlines)
                                    =
                                    if (thisrow > lastrow)
                                        #
                                        updated_textlines;
                                    else
                                        updated_textlines
                                            =
                                            nl::remove (updated_textlines, first'.row);

                                        loop (thisrow + 1,  updated_textlines);
                                    fi;
                            end;
                        };

                    updated_textlines                                                                                   # Drop finalline.
                        =
                        nl::remove (updated_textlines, first'.row);

                    replacement_line
                        #
                        = text_before_firstline_region
                        + text_beyond_finalline_region
                        + (chomped_finaltext==finaltext ?? "" :: "\n");                                                 # Add back terminal newline, if original finalline had one.

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

                    updated_textlines
                        =
                        nl::set (updated_textlines, first'.row, replacement_line);


                    { updated_textlines,
                      cutbuffer_contents,
                      point =>  { row => first.row,  col => first.col }
                    };
                fi;
            };

        fun get_selection_as_string                                                                                     # Utility fn.  Used in (e.g.) eval_mode::input_done()
            ( arg
              as                
              {
                mark:                                   g2d::Point,                                                     # 
                point:                                  g2d::Point,                                                     # 
                textlines:                              mt::Textlines                                                   # Textlines in which to insert.
              }
            )
            :
              String
            =
            {   (kill_region  arg)                                                                                      # 
                  ->
                  { cutbuffer_contents, ... };

                case cutbuffer_contents
                    #
                    ct::PARTLINE  string  =>  string;
                    ct::WHOLELINE string  =>  string;
                    ct::MULTILINE strings =>  string::cat  strings;
                esac;           
            };

        fun append_lines                                                                                                # PUBLIC.
              (                                                                                                         # We code very defensively here so as to make this fn widely usable by clients without unpleasant surprises.
                textlines:                              mt::Textlines,
                lines:                                  List( String )
              )
            =
            {   fun normalize_lines ([], result)                                                                        # Ensure each line has a newline at end and no other newlines.
                        =>
                        reverse result;

                    normalize_lines (line ! rest, result)
                        =>
                        {   chomped_line  = string::chomp  line;                                                        # Yes, this is a silly way to test for presence of terminal newline.
                            #
                            line          = if (chomped_line == line)   line + "\n";                                    # Ensure line has a newline at end.
                                            else                        line;
                                            fi;

                            lines =  if (string::is_substring "\n" chomped_line)   string::lines line;                  # Ensure line has no embedded newlines.
                                     else                                          [ line ];
                                     fi;

                            normalize_lines  (rest,  reverse lines  @  result);
                        };
                end;

                lines =  normalize_lines  (lines, []);

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

                fun append' (textlines, nextkey, [])
                        =>
                        textlines;

                    append' (textlines, nextkey, line ! lines)
                        =>
                        {   line = mt::MONOLINE { string => line, prefix => NULL };
                            #
                            textlines = nl::set (textlines, nextkey, line);

                            append' (textlines, nextkey+1, lines);
                        };
                end;

                textlines = append' (textlines, maxkey+1, lines);
                
                textlines;
            };

        fun end_of_buffer_point (textlines: mt::Textlines):     g2d::Point
            =
            {   maxkey =    case (nl::max_key textlines)
                                #
                                THE maxkey => maxkey;
                                NULL       => 0;
                            esac;

                lastline =  case (nl::find (textlines, maxkey))
                                #
                                THE line =>  mt::visible_line line;
                                NULL     =>  "\n";
                            esac;

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

    };
end;



Comments and suggestions to: bugs@mythryl.org

PreviousUpNext