## 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.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 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.pkgherein
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