## textlines-junk.pkg
#
# Support fns for manipulating textliens.
# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.sublibstipulate
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.pkgherein
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;