## textmill.pkg # emacs calls these 'buffers' but 'mill' is shorter and more parellel to 'pane'.
# # Also, I want to eventually generalize this to various other foopane <-> foomill pairs, and generalize millboss to not know or care about the differences between them.
# A textmill contains (typically) the contents
# of one file open for editing. It is not a
# GUI widget; display of the textmill contents
# is handled by
#
#
src/lib/x-kit/widget/edit/textpane.pkg#
# In "Model/View/Controller" terms, textmill.pkg
# is the Model and textpane.pkg is the View+Controller.
#
# See also:
#
src/lib/x-kit/widget/edit/textpane.pkg#
src/lib/x-kit/widget/edit/millboss-imp.pkg# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.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 a2r = windowsystem_to_xevent_router; # windowsystem_to_xevent_router is from
src/lib/x-kit/xclient/src/window/windowsystem-to-xevent-router.pkg package gd = gui_displaylist; # gui_displaylist is from
src/lib/x-kit/widget/theme/gui-displaylist.pkg package pp = standard_prettyprinter; # standard_prettyprinter is from
src/lib/prettyprint/big/src/standard-prettyprinter.pkg package err = compiler::error_message; # compiler is from
src/lib/core/compiler/compiler.pkg # error_message is from
src/lib/compiler/front/basics/errormsg/error-message.pkg package bt = gui_to_sprite_theme; # gui_to_sprite_theme is from
src/lib/x-kit/widget/theme/sprite/gui-to-sprite-theme.pkg package ct = gui_to_object_theme; # gui_to_object_theme is from
src/lib/x-kit/widget/theme/object/gui-to-object-theme.pkg package wt = widget_theme; # widget_theme is from
src/lib/x-kit/widget/theme/widget/widget-theme.pkg package boi = spritespace_imp; # spritespace_imp is from
src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg package cai = objectspace_imp; # objectspace_imp is from
src/lib/x-kit/widget/space/object/objectspace-imp.pkg package pai = widgetspace_imp; # widgetspace_imp is from
src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg #
package gtg = guiboss_to_guishim; # guiboss_to_guishim is from
src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg package b2s = spritespace_to_sprite; # spritespace_to_sprite is from
src/lib/x-kit/widget/space/sprite/spritespace-to-sprite.pkg package c2o = objectspace_to_object; # objectspace_to_object is from
src/lib/x-kit/widget/space/object/objectspace-to-object.pkg package s2b = sprite_to_spritespace; # sprite_to_spritespace is from
src/lib/x-kit/widget/space/sprite/sprite-to-spritespace.pkg package o2c = object_to_objectspace; # object_to_objectspace is from
src/lib/x-kit/widget/space/object/object-to-objectspace.pkg package g2p = gadget_to_pixmap; # gadget_to_pixmap is from
src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg package m2d = mode_to_drawpane; # mode_to_drawpane is from
src/lib/x-kit/widget/edit/mode-to-drawpane.pkg package im = int_red_black_map; # int_red_black_map is from
src/lib/src/int-red-black-map.pkg# package is = int_red_black_set; # int_red_black_set is from
src/lib/src/int-red-black-set.pkg package sm = string_map; # string_map is from
src/lib/src/string-map.pkg package r8 = rgb8; # rgb8 is from
src/lib/x-kit/xclient/src/color/rgb8.pkg package r64 = rgb; # rgb is from
src/lib/x-kit/xclient/src/color/rgb.pkg package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package g2j = geometry2d_junk; # geometry2d_junk is from
src/lib/std/2d/geometry2d-junk.pkg package e2g = millboss_to_guiboss; # millboss_to_guiboss is from
src/lib/x-kit/widget/edit/millboss-to-guiboss.pkg package sj = string_junk; # string_junk is from
src/lib/std/src/string-junk.pkg package bq = bounded_queue; # bounded_queue is from
src/lib/src/bounded-queue.pkg package nl = red_black_numbered_list; # red_black_numbered_list is from
src/lib/src/red-black-numbered-list.pkg package tmc = textmill_crypts; # textmill_crypts is from
src/lib/x-kit/widget/edit/textmill-crypts.pkg package mt = millboss_types; # millboss_types is from
src/lib/x-kit/widget/edit/millboss-types.pkg package bo = bool_millout; # bool_millout is from
src/lib/x-kit/widget/edit/bool-millout.pkg package bso = bools_millout; # bools_millout is from
src/lib/x-kit/widget/edit/bools-millout.pkg package fo = float_millout; # float_millout is from
src/lib/x-kit/widget/edit/float-millout.pkg package fso = floats_millout; # floats_millout is from
src/lib/x-kit/widget/edit/floats-millout.pkg package io = int_millout; # int_millout is from
src/lib/x-kit/widget/edit/int-millout.pkg package iso = ints_millout; # ints_millout is from
src/lib/x-kit/widget/edit/ints-millout.pkg package so = string_millout; # string_millout is from
src/lib/x-kit/widget/edit/string-millout.pkg package sso = strings_millout; # strings_millout is from
src/lib/x-kit/widget/edit/strings-millout.pkg package xso = boolfloatintstrings_millout; # boolfloatintstrings_millout is from
src/lib/x-kit/widget/edit/boolfloatintstrings-millout.pkg package tso = textmill_statechange_millout; # textmill_statechange_millout is from
src/lib/x-kit/widget/edit/textmill-statechange-millout.pkg package mmo = millgraph_millout; # millgraph_millout is from
src/lib/x-kit/widget/edit/millgraph-millout.pkg package mgm = millgraph_mill; # millgraph_mill is from
src/lib/x-kit/widget/edit/millgraph-mill.pkg tracefile = "widget-unit-test.trace.log";
nb = log::note_on_stderr; # log is from
src/lib/std/src/log.pkgdummy0 = tmc::decrypt__textpane_to_textmill; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy2 = so::String_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy3 = io::Int_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy4 = fo::Float_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy5 = bo::Bool_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy6 = fso::Floats_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy7 = bso::Bools_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy8 = iso::Ints_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummy9 = sso::Strings_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummya = xso::Boolfloatintstrings_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
Dummyb = mmo::Millgraph_Millout; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
dummyc = mgm::millgraph_mill: mt::Textmill_Extension; # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.
herein
package textmill
: Textmill # Textmill is from
src/lib/x-kit/widget/edit/textmill.api {
max_history_length = 4000; # XXX SUCKO FIXME The 4000 max-history-length should be configurable.
#
fun dummy_make_pane_guiplan # Synthesize guiplan for a pane to display our state.
{
textpane_to_textmill: mt::Textpane_To_Textmill, #
filepath: Null_Or( String ), # make_pane_guiplan will (should!) often select the pane mode to use based on the filename.
textpane_hint: Crypt # Current pane mode (e.g. fundamental_mode) etc, wrapped up so textmill can't see the relevant types, in the interest of modularity.
}
: gt::Gp_Widget_Type
=
{ msg = "dummy_make_pane_guiplan() called?! --textmill.pkg";
log::fatal msg; # Should never return.
raise exception DIE msg; # To keep compiler happy.
};
make_pane_guiplan__hack # Nassssty hack to break a package dependency cycle.
= # This is used by App_To_Mill.make_pane_guiplan() below.
REF dummy_make_pane_guiplan; # This value will be overwritten by
src/lib/x-kit/widget/edit/make-textpane.pkg Textmill_State = mt::Textmill_State; #
Imports = mt::Textmill_Imports; # Ports we use, provided by other imps.
Textmill_Statechange__Watchers = mt::Textmill_Statechange__Watchers; # Type for tracking the set of clients subscribed to a mill for mt::Textmill_Statechange updates.
Textmill_Statechange__Watchee = mt::Textmill_Statechange__Watchee; # Type for tracking the client we are subscribed to for mt::Textmill_Statechange updates.
Runstate = mt::Textmill_Runstate; # These values will be statically globally visible throughout the code body for the imp.
Textmill_Q = mt::Textmill_Q;
Me_Slot #
= #
Mailslot ( #
{ imports: Imports, #
me: Textmill_State, #
textmill_arg: mt::Textmill_Arg, #
run_gun': Run_Gun, #
end_gun': End_Gun #
} #
); #
Exports = { # Ports we provide for use by other imps.
textpane_to_textmill: mt::Textpane_To_Textmill, # mt::Textpane_To_Textmill includes mt::App_To_Mill.
millboss_to_mill: mt::Millboss_To_Mill
};
Textmill_Egg = Void -> (Exports, (Imports, Run_Gun, End_Gun) -> Void);
fun run ( textmill_q: Textmill_Q, #
#
runstate as
{ # These values will be statically globally visible throughout the code body for the imp.
id: Id,
me: Textmill_State, #
textmill_arg: mt::Textmill_Arg,
textpane_to_textmill: mt::Textpane_To_Textmill,
mill_to_millboss: mt::Mill_To_Millboss,
millins: mt::ipm::Map(mt::Millin), # Millins for this mill indexed by Inport.
millouts: mt::opm::Map(mt::Millout), # Output streams available to watch. Indexed by Outport.
make_pane_guiplan': mt::Make_Pane_Guiplan_Fn,
finalize_textmill_extension: Void -> Void, # Function to be called at textmill shutdown, so textmill extension can do any required shutdown of its own.
imports: Imports, # Imps to which we send requests.
to: Replyqueue, # The name makes foo::pass_something(imp) to {. ... } syntax read well.
# #
textmill_statechange__outport: mt::Outport, #
textmill_statechange__millout: mt::Millout, #
textmill_statechange__watchers: Ref( Textmill_Statechange__Watchers ), #
# #
textmill_statechange__inport: mt::Inport, #
textmill_statechange__millin: mt::Millin,
textmill_statechange__watchee: Ref( Null_Or( Textmill_Statechange__Watchee ) ), #
# #
end_gun': End_Gun #
}
)
=
{ loop ();
}
where
#
fun loop () # Outer loop for the imp.
=
{ do_one_mailop' to [
#
end_gun' ==> shut_down_textmill',
take_from_mailqueue' textmill_q ==> do_textmill_plea
];
loop ();
}
where
fun do_textmill_plea thunk
=
thunk runstate;
#
fun shut_down_textmill' ()
=
{ finalize_textmill_extension (); # Let any textmill extension present do any needed shutdown work.
#
thread_exit { success => TRUE }; # Will not return.
};
end;
end;
#
fun startup # Root fn of imp microthread. Note currying.
( #
id: Id, #
textmill_extension: Null_Or( mt::Textmill_Extension ), #
reply_oneshot: Oneshot_Maildrop( (Me_Slot, Exports) ) #
) #
() # Curried to allow delayed activation of egg.
=
{ me_slot = make_mailslot () : Me_Slot;
#
textmill_statechange__outport
=
{ mill_id => id,
outport_name => "textmill_statechange"
};
textmill_statechange__inport
=
{ mill_id => id,
inport_name => "textmill_statechange"
};
textmill_statechange__millin
=
make__textmill_statechange__millin textmill_statechange__inport;
textmill_statechange__millout
=
make__textmill_statechange__millout textmill_statechange__outport;
millins = make_millins (textmill_statechange__inport, textmill_statechange__millin );
millouts = make_millouts (textmill_statechange__outport, textmill_statechange__millout);
#
make_pane_guiplan' = *make_pane_guiplan__hack; # This should be the only read of make_pane_guiplan__hack. Everywhere else we should use runstate.make_pane_guiplan' because that support textmill extension overrides of the value.
mill_extension_state = *mill_extension_state__global;
my { millins, millouts, make_pane_guiplan', mill_extension_state, finalize_textmill_extension } # If we have a textmill extension, let it initialize its state and register its millins and millouts, also finalization code and preferred pane.
=
case textmill_extension
#
NULL => { millins, millouts, make_pane_guiplan', mill_extension_state, finalize_textmill_extension => \\ () = () };
THE x => x.initialize_textmill_extension { mill_id => id, textmill_q, millins, millouts, make_pane_guiplan' };
esac;
mill_extension_state__global := mill_extension_state;
app_to_mill # Generic interface supported by all mills.
=
mt::APP_TO_MILL
{
id,
millins,
millouts,
get_dirty,
pass_dirty,
get_filepath,
set_filepath,
pass_filepath,
get_name,
set_name,
pass_name,
reload_from_file,
save_to_file,
get_pane_guiplan,
pass_pane_guiplan
};
textpane_to_textmill # textmill-specific interface.
=
mt::TEXTPANE_TO_TEXTMILL
{
id,
#
get_maxline,
pass_maxline,
get_line,
pass_line,
set_lines,
get_lines,
pass_lines,
get_textstate,
pass_textstate,
get_edit_result,
pass_edit_result,
get_drawpane_startup_result,
get_drawpane_shutdown_result,
get_drawpane_initialize_gadget_result,
get_drawpane_redraw_request_result,
get_drawpane_mouse_click_result,
get_drawpane_mouse_drag_result,
get_drawpane_mouse_transit_result,
undo,
set_readonly,
get_readonly,
pass_readonly,
set_textpane_hint,
get_textpane_hint,
note__textmill_statechange__watcher,
drop__textmill_statechange__watcher,
textmill_extension,
app_to_mill # Include the generic mill interface in our textmill-specific interface.
};
millboss_to_mill #
=
{ id => issue_unique_id (),
wakeup
};
exports = { textpane_to_textmill,
millboss_to_mill
};
to = make_replyqueue();
#
put_in_oneshot (reply_oneshot, (me_slot, exports)); # Return value from textmill_egg'().
(take_from_mailslot me_slot) # Imports from textmill_egg'().
->
{ me, textmill_arg, imports, run_gun', end_gun' };
block_until_mailop_fires run_gun'; # Wait for the starting gun.
textmill_statechange__watchers = REF mt::ipm::empty;
textmill_statechange__watchee = REF (NULL: Null_Or( Textmill_Statechange__Watchee ));
runstate = { id,
me,
textmill_arg,
textpane_to_textmill,
mill_to_millboss,
millins,
millouts,
make_pane_guiplan',
finalize_textmill_extension,
imports,
to,
#
textmill_statechange__outport,
textmill_statechange__millout,
textmill_statechange__watchers,
#
textmill_statechange__inport,
textmill_statechange__millin,
textmill_statechange__watchee,
#
end_gun'
};
tell__textmill_statechange__watchers_full_state runstate; # Make sure textmill_statechange__watchers start out with full update.
run (textmill_q, runstate); # Will not return.
}
where
textmill_q = make_mailqueue (get_current_microthread()): Textmill_Q;
#
(mt::get__mill_to_millboss "textmill::startup")
->
(mill_to_millboss as mt::MILL_TO_MILLBOSS m2m);
mill_extension_state__global
=
REF { id => issue_unique_id (),
type => "Void",
info => "dummy value for mill_extension_state -- textmill.pkg",
data => DIE "dummy value for mill_extension_state -- textmill.pkg"
};
fun tell__textmill_statechange__watchers
(
textmill_statechange__watchers: Textmill_Statechange__Watchers, #
statechange: mt::Textmill_Statechange,
r: Runstate
)
=
mt::ipm::apply tell_watcher textmill_statechange__watchers
where
fun tell_watcher
(
inport: mt::Inport, # Unique id identifying this watcher.
watcher: (mt::Outport, mt::Textmill_Statechange) -> Void #
)
=
{ outport = r.textmill_statechange__outport;
#
watcher (outport, statechange);
counter = r.textmill_statechange__millout.counter; # Count messages sent through port,
counter := *counter + 1; # for debug/display purposes.
};
end;
fun tell__textmill_statechange__watcher_full_state
(
watchfn: (mt::Outport, mt::Textmill_Statechange) -> Void,
r: Runstate
)
=
{ outport = r.textmill_statechange__outport;
#
watchfn (outport, mt::TEXTSTATE_CHANGED { was => *r.me.state, now => *r.me.state });
watchfn (outport, mt::FILEPATH_CHANGED { was => *r.me.filepath, now => *r.me.filepath });
watchfn (outport, mt::NAME_CHANGED { was => *r.me.name, now => *r.me.name });
watchfn (outport, mt::READONLY_CHANGED { was => *r.me.readonly, now => *r.me.readonly });
watchfn (outport, mt::DIRTY_CHANGED { was => *r.me.dirty, now => *r.me.dirty });
counter = r.textmill_statechange__millout.counter; # Count messages sent through port,
counter := *counter + 5; # for debug/display purposes.
};
fun tell__textmill_statechange__watchers_full_state
(
r: Runstate
)
=
mt::ipm::apply tell_watcher *r.textmill_statechange__watchers
where
fun tell_watcher
(
inport: mt::Inport, #
watchfn: (mt::Outport, mt::Textmill_Statechange) -> Void #
)
=
tell__textmill_statechange__watcher_full_state (watchfn, r);
end;
fun line_range (textlines: mt::Textlines, firstline: Int, lastline: Int): List(String)
=
lines (lastline, [])
where
fun lines (i: Int, result: List(String))
=
if (i < firstline) result;
else
result = case (nl::find (textlines, i))
#
THE line => mt::visible_line(line) ! result;
NULL => result;
esac;
lines (i - 1, result);
fi;
end;
fun changed_textlines ([]: List(mt::Editfn_Out_Option)) => NULL;
changed_textlines (mt::TEXTLINES textlines ! _) => THE textlines;
changed_textlines (_ ! rest) => changed_textlines rest;
end;
fun changed_readonly ([]: List(mt::Editfn_Out_Option)) => NULL;
changed_readonly (mt::READONLY readonly ! _) => THE readonly;
changed_readonly (_ ! rest) => changed_readonly rest;
end;
fun changed_edit_history ([]: List(mt::Editfn_Out_Option)) => NULL;
changed_edit_history (mt::EDIT_HISTORY history ! _) => THE history;
changed_edit_history (_ ! rest) => changed_edit_history rest;
end;
fun do_editfn_out
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
editfn_out: mt::Editfn_Out,
log_undo_info: Bool
)
=
{ was = *me.state;
#
case editfn_out
#
FAIL _ => (); # Editfn aborted, no changes to process.
WORK editfn_out_options # Editfn did not abort ...
=>
{
case (changed_readonly editfn_out_options)
#
NULL => (); #
THE readonly
=>
{ was = *me.readonly;
now = readonly;
me.readonly := readonly;
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::READONLY_CHANGED { was, now },
runstate
);
};
esac;
log_undo_info
=
case (changed_edit_history editfn_out_options)
#
NULL => log_undo_info; #
THE edit_history
=>
{ me.edit_history := edit_history;
#
# tell__textmill_statechange__watchers # XXX QUERO FIXME Is there any reason to do something like this?
# (
# *textmill_statechange__watchers,
# mt::EDIT_HISTORY_CHANGED { was, now },
# runstate
# );
FALSE;
};
esac;
case (changed_textlines editfn_out_options)
#
NULL => (); # Editfn did NOT change contents of textmill, so we're done.
THE new_textlines # Editfn DID change contents of textmill.
=>
{ now = { textlines => new_textlines,
editcount => was.editcount + 1
};
was_dirty = *me.dirty;
me.state := now;
me.dirty := TRUE;
me.readonly := FALSE;
if log_undo_info
#
me.edit_history := bq::push (*me.edit_history, was); # Add previous state to history.
fi;
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::TEXTSTATE_CHANGED { was, now },
runstate
);
if (was_dirty != *me.dirty)
#
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
#
mt::DIRTY_CHANGED
{ was => was_dirty,
now => *me.dirty
},
runstate
);
fi;
};
esac;
};
esac;
editfn_out; # Return all updates to caller.
};
fun do_get_or_pass_edit_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg as
{ keystring: String, # User keystroke that invoked this editfn.
numeric_prefix: Null_Or(Int), # ^U "Universal numeric prefix" value for this editfn if supplied by user, else NULL.
prompted_args: List( mt::Prompted_Arg ), # Args read interactively from user.
point_and_mark: mt::Point_And_Mark, # 'point' is the visible cursor. 'mark' (if set) is the other end of the selected region. (Emacs nomenclature.)
lastmark: Null_Or( g2d::Point ),
screen_origin: g2d::Point,
visible_lines: Int,
log_undo_info: Bool,
#
pane_tag: Int,
pane_id: Id,
editfn_node: mt::Editfn_Node,
widget_to_guiboss: gt::Widget_To_Guiboss, #
#
mainmill_modestate: mt::Panemode_State,
minimill_modestate: mt::Panemode_State,
#
textpane_to_textmill: mt::Textpane_To_Textmill,
mode_to_drawpane: Null_Or( m2d::Mode_To_Drawpane ),
valid_completions: Null_Or( String -> List(String) ) # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
}
)
=
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
editfn_in = { args => prompted_args,
textlines => was.textlines,
point => point_and_mark.point,
mark => point_and_mark.mark,
readonly => *me.readonly,
lastmark,
screen_origin,
visible_lines,
edit_history => *me.edit_history,
pane_tag,
pane_id,
mill_id => id,
to,
widget_to_guiboss,
mill_to_millboss,
keystring,
numeric_prefix,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions
};
end;
plain_editfn
=
case editfn_node
#
mt::PLAIN_EDITFN plain_editfn => plain_editfn;
mt::FANCY_EDITFN => { msg = "FANCY_EDITFN not supported -- do_pass_edit_result in textmill.pkg";
log::fatal msg;
raise exception DIE msg;
};
esac;
editfn_out = (plain_editfn.editfn editfn_in)
except _ = FAIL "<uncaught exception in editfn>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
fun do_get_drawpane_startup_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Startup_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
widget_to_guiboss: gt::Widget_To_Guiboss, #
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_startup_fn, ... };
case drawpane_startup_fn
#
NULL => WORK [];
THE drawpane_startup_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_startup_in
=
{
drawpane_id,
doc,
widget_to_guiboss,
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
mill_to_millboss,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_startup_fn drawpane_startup_in)
except _ = FAIL "<uncaught exception in drawpane_startup_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_shutdown_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Shutdown_Arg
)
=
{
arg -> {
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ) # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_shutdown_fn, ... };
case drawpane_shutdown_fn
#
NULL => WORK [];
THE drawpane_shutdown_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_shutdown_in
=
{
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
mill_to_millboss,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions
};
end;
editfn_out = (drawpane_shutdown_fn drawpane_shutdown_in)
except _ = FAIL "<uncaught exception in drawpane_shutdown_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_initialize_gadget_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Initialize_Gadget_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
site: g2d::Box, # Widget's assigned area in window coordinates.
pass_font: List(String) -> Replyqueue
-> (evt::Font -> Void) -> Void, # Nonblocking version of next, for use in imps.
get_font: List(String) -> evt::Font, # Accepts a list of font names which are tried in order.
make_rw_pixmap: g2d::Size -> g2p::Gadget_To_Rw_Pixmap, # Make an Xserver-side rw_pixmap for scratch use by widget. In general there is no need for the widget to explicitly free these -- guiboss_imp will do this automatically when the gui is killed.
#
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
widget_to_guiboss: gt::Widget_To_Guiboss, #
theme: wt::Widget_Theme,
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_initialize_gadget_fn, ... };
case drawpane_initialize_gadget_fn
#
NULL => WORK [];
THE drawpane_initialize_gadget_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_initialize_gadget_in
=
{
drawpane_id,
doc,
site,
pass_font,
get_font,
make_rw_pixmap,
#
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
widget_to_guiboss,
mill_to_millboss,
theme,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_initialize_gadget_fn drawpane_initialize_gadget_in)
except _ = FAIL "<uncaught exception in drawpane_initialize_gadget_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_redraw_request_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Redraw_Request_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
frame_number: Int, # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
site: g2d::Box, # Widget's assigned area in window coordinates.
duration_in_seconds: Float, # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
gadget_mode: gt::Gadget_Mode,
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
widget_to_guiboss: gt::Widget_To_Guiboss, #
theme: wt::Widget_Theme,
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_redraw_request_fn, ... };
case drawpane_redraw_request_fn
#
NULL => WORK [];
THE drawpane_redraw_request_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_redraw_request_in
=
{
drawpane_id,
doc,
frame_number,
site,
duration_in_seconds,
gadget_mode,
popup_nesting_depth,
#
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
widget_to_guiboss,
mill_to_millboss,
theme,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_redraw_request_fn drawpane_redraw_request_in)
except _ = FAIL "<uncaught exception in drawpane_redraw_request_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_mouse_click_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Mouse_Click_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
button: evt::Mousebutton,
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
widget_to_guiboss: gt::Widget_To_Guiboss, #
theme: wt::Widget_Theme,
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_mouse_click_fn, ... };
case drawpane_mouse_click_fn
#
NULL => WORK [];
THE drawpane_mouse_click_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_mouse_click_in
=
{
drawpane_id,
doc,
button,
event,
point,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
mousebuttons_state,
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
widget_to_guiboss,
mill_to_millboss,
theme,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_mouse_click_fn drawpane_mouse_click_in)
except _ = FAIL "<uncaught exception in drawpane_mouse_click_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_mouse_drag_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Mouse_Drag_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
button: evt::Mousebutton,
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
phase: gt::Drag_Phase,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
widget_to_guiboss: gt::Widget_To_Guiboss, #
theme: wt::Widget_Theme,
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_mouse_drag_fn, ... };
case drawpane_mouse_drag_fn
#
NULL => WORK [];
THE drawpane_mouse_drag_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_mouse_drag_in
=
{
drawpane_id,
doc,
button,
event_point,
start_point,
last_point,
phase,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
mousebuttons_state,
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
widget_to_guiboss,
mill_to_millboss,
theme,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_mouse_drag_fn drawpane_mouse_drag_in)
except _ = FAIL "<uncaught exception in drawpane_mouse_drag_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_get_drawpane_mouse_transit_result
(
runstate as { id, me, to, make_pane_guiplan', textmill_statechange__watchers, ... }: Runstate,
#
arg: mt::Drawpane_Mouse_Transit_Arg
)
=
{
arg -> {
drawpane_id: Id, # Unique id of this drawpane widget.
doc: String, # Text description of this drawpane widget for debug/display purposes.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
point_and_mark: mt::Point_And_Mark,
lastmark: Null_Or( g2d::Point ), # Last valid value of 'mark' if any -- used to retrieve old mark values by exchange_point_and_mark in
src/lib/x-kit/widget/edit/fundamental-mode.pkg screen_origin: g2d::Point, # Origin of pane-visible text relative to textmill contents: (0,0) means we're showing top of buffer at top of textpane.
visible_lines: Int, # Number of lines of text visible in pane.
log_undo_info: Bool, # If log_undo_info is FALSE no entry will be made in the undo history.
pane_tag: Int, # Tag of pane for which this editfn is being invoked. This is a small int for human/GUI use.
pane_id: Id, # Id of pane for which this editfn is being invoked.
widget_to_guiboss: gt::Widget_To_Guiboss, #
theme: wt::Widget_Theme,
#
mainmill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for fundamental-mode.pkg) for main mill is available via this.
minimill_modestate: mt::Panemode_State, # Any persistent per-mode state (e.g., private state for minimill-mode.pkg) for mini mill is available via this.
#
textpane_to_textmill: mt::Textpane_To_Textmill, # NB: Editfns run in textmill's microthread to guarantee atomicity, so any attempt by them to invoke blocking textpane_to_textmill.* fns is likely to deadlock.
mode_to_drawpane: m2d::Mode_To_Drawpane, #
valid_completions: Null_Or( String -> List(String) ), # If this is non-NULL then user is entering a commandname or filename or millname(=buffername) on the modeline, and given fn returns all valid completions of string-entered-so-far.
#
do: (Void -> Void) -> Void, # Used by widget subthreads to run code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
};
mainmill_modestate.mode
->
mt::PANEMODE { drawpane_mouse_transit_fn, ... };
case drawpane_mouse_transit_fn
#
NULL => WORK [];
THE drawpane_mouse_transit_fn
=>
{ was = *me.state;
#
# runstate.mill_to_millboss
# ->
# mt::MILL_TO_MILLBOSS eb; # We don't currently use 'eb' here.
stipulate
fun make_pane_guiplan () # This fn is safe to call from within editfns because it does not indirect through textmill_q, potentially deadlocking us if calling ourself.
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
};
herein
drawpane_mouse_transit_in
=
{
drawpane_id,
doc,
transit,
event_point,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
textlines => was.textlines,
point_and_mark,
lastmark,
screen_origin,
visible_lines,
readonly => *me.readonly,
pane_tag,
pane_id,
mill_id => id,
edit_history => *me.edit_history,
widget_to_guiboss,
mill_to_millboss,
theme,
#
mainmill_modestate,
minimill_modestate,
#
mill_extension_state => *mill_extension_state__global,
textpane_to_textmill,
mode_to_drawpane,
valid_completions,
#
do,
to
};
end;
editfn_out = (drawpane_mouse_transit_fn drawpane_mouse_transit_in)
except _ = FAIL "<uncaught exception in drawpane_mouse_transit_in>"; # Handle any uncaught exceptions in editfn. (Shouldn't happen.)
do_editfn_out (runstate, editfn_out, log_undo_info);
};
esac;
};
fun do_undo (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate) # THIS IS PROBABLY DUE TO BE DELETED. EARLY CODE, SYSTEM EVOLVED A DIFFERENT DIRECTION. XXX SUCKO FIXME.
=
if (bq::length *me.edit_history > 0)
#
(*me.state) -> ( was);
(bq::pull *me.edit_history) -> (new_history, now);
now = the now; # Should not be NULL because we checked for history_len==0 above.
me.edit_history := new_history;
me.state := now;
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::UNDO { was, now },
runstate
);
fi;
#################################################################################
# App_To_Mill interface fns::
#
#
fun get_pane_guiplan (): gt::Gp_Widget_Type # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( gt::Gp_Widget_Type );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, make_pane_guiplan', textpane_to_textmill, ... }: Runstate)
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
gp_widget = make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
#
put_in_oneshot (reply_oneshot, gp_widget);
}
);
get_from_oneshot reply_oneshot;
};
#
fun pass_pane_guiplan (replyqueue: Replyqueue) (reply_handler: gt::Gp_Widget_Type -> Void): Void # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( gt::Gp_Widget_Type );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, make_pane_guiplan', textpane_to_textmill, ... }: Runstate)
=
{ filepath = *me.filepath;
textpane_hint = *me.textpane_hint;
#
gp_widget = make_pane_guiplan' { textpane_to_textmill, filepath, textpane_hint };
#
put_in_oneshot (reply_oneshot, gp_widget);
}
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
#
fun get_dirty () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Bool );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.dirty)
);
get_from_oneshot reply_oneshot;
};
#
fun pass_dirty (replyqueue: Replyqueue) (reply_handler: Bool -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Bool );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.dirty)
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun set_filepath (filepath: Null_Or( String )) # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
=
{ tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::FILEPATH_CHANGED { now => filepath, was => *me.filepath },
runstate
);
#
me.filepath := filepath;
}
);
};
#
fun get_filepath () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Null_Or( String ) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.filepath)
);
get_from_oneshot reply_oneshot;
};
#
fun pass_filepath (replyqueue: Replyqueue) (reply_handler: Null_Or( String ) -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Null_Or( String ) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.filepath)
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun set_name (name: String) # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
=
{ tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::NAME_CHANGED { now => name, was => *me.name },
runstate
);
#
me.name := name;
}
);
};
#
fun get_name () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( String );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.name)
);
get_from_oneshot reply_oneshot;
};
#
fun pass_name (replyqueue: Replyqueue) (reply_handler: String -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( String );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.name)
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun note__textmill_statechange
(
outport: mt::Outport, #
textmill_statechange: mt::Textmill_Statechange #
)
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate as { id, me, textmill_statechange__watchers, textmill_statechange__millin, ... }: Runstate)
=
{ case textmill_statechange
#
( mt::TEXTSTATE_CHANGED { was: mt::Textstate, now: mt::Textstate }
| mt::UNDO { was: mt::Textstate, now: mt::Undostate }
)
=>
{ was_dirty = *me.dirty;
#
me.state := now;
me.dirty := FALSE; # As a mirror of our upstream input, it makes no sense to think of our copy as 'dirty'.
me.readonly := TRUE; # As a mirror of our upstream input, it makes no sense to let user try to edit us.
me.edit_history := bq::make_queue max_history_length; # As a mirror of our upstream input, it makes no sense to maintain an edit history.
tell__textmill_statechange__watchers # Tell anyone watching us about our change of text contents.
(
*textmill_statechange__watchers,
mt::TEXTSTATE_CHANGED { was, now },
runstate
);
# We don't tell our watchers about any change in our dirty or readonly state because they are basically meaningless.
# Compare with above logic in do_get_or_pass_edit_result().
};
# List rest explicitly so that if one gets added we'll draw a compile error which reminds us to think about whether support is needed here.
mt::FILEPATH_CHANGED _ => (); # Ignore because we're mirroring input from upstream so we don't want to be independently trying to save it to disk or such.
mt::NAME_CHANGED _ => (); # Ignore because as a mirror we don't want to have same name as upstream mill.
mt::READONLY_CHANGED _ => (); # Ignore because as a mirror we always want to be readonly -- doesn't make sense to let user try to edit stuff which can be overwritten at any moment.
mt::DIRTY_CHANGED _ => (); # Ignore because as a readonly mirror 'dirty' isn't really relevant to us.
esac;
counter = textmill_statechange__millin.counter; # Count messages read through port,
counter := *counter + 1; # for debug/display purposes.
}
);
};
#
fun make__textmill_statechange__millout (textmill_statechange__outport: mt::Outport): mt::Millout
=
{ outport = textmill_statechange__outport; #
# #
textmill_statechange_millout #
= #
{ note_watcher => note__textmill_statechange__watcher, #
drop_watcher => drop__textmill_statechange__watcher #
}; #
#
millout = tso::wrap__textmill_statechange_millout # Wrap it so millboss, plugboard &tc don't need to know about port-specific types.
( #
outport, #
textmill_statechange_millout #
); #
millout;
}
also
fun note__textmill_statechange__watcher # PUBLIC.
(
watcher: mt::Inport, #
millin: Null_Or(mt::Millin), # This will be NULL if watcher is not another mill (e.g. a pane).
watchfn: (mt::Outport, mt::Textmill_Statechange) -> Void #
)
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate
as
{ id,
me,
textmill_statechange__watchers,
textmill_statechange__outport,
textmill_statechange__millout,
...
}: Runstate)
=
{ textmill_statechange__watchers
:=
mt::ipm::set ( *textmill_statechange__watchers,
watcher,
(watcher, watchfn)
);
case millin
#
THE millin
=>
m2m.note_millwatch millwatch # Tell millboss about new watcher/watchee edge in the millgraph.
where
millout = textmill_statechange__millout;
#
millwatch = { millin, millout };
end;
NULL => ();
esac;
tell__textmill_statechange__watcher_full_state (watchfn, runstate); # Make sure new watcher starts out with full update.
}
);
}
also
fun drop__textmill_statechange__watcher (inport: mt::Inport) # PUBLIC.
=
{
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, textmill_statechange__outport, ... }: Runstate)
=
{ textmill_statechange__watchers
:=
mt::ipm::drop (*textmill_statechange__watchers, inport);
m2m.drop_millwatch millwatch # Tell millboss about vanished watcher/watchee edge in the millgraph.
where # If we're not a mill-mill edge we won't be in millboss' graph, but
outport = textmill_statechange__outport; # that is OK because removing a non-existent edge is a no-op.
#
millwatch = { inport, outport };
end;
}
);
};
fun make__textmill_statechange__millin (textmill_statechange__inport: mt::Inport): mt::Millin # Construct a description of the inport on which we read textmill_statechange events, for publication in our App_To_Mill export. This is normally NULL -- we operate autonomously.
= #
{ inport => textmill_statechange__inport, # This gives the world a globally unique name for this particular inport.
port_type => tso::port_type, # This tells the world that on this port we listen for textmill_statechange events.
mono => TRUE, # This tells the world that we listen on at most one input textmill_statechange stream at a time.
# #
note_input => note__textmill_statechange__watchee, # Caller uses this to tell us to start reading from a different textmill_statechange stream.
drop_input => drop__textmill_statechange__watchee, # Caller uses this to disconnect us from input textmill_statechange stream.
# #
counter => REF 0
} #
also
fun note__textmill_statechange__watchee (wrapped_millout: mt::Millout) # PUBLIC. Start watching 'wrapped_millout'.
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate
as
{ id,
me,
textmill_statechange__watchee,
textmill_statechange__inport,
textmill_statechange__millin,
...
}: Runstate)
=
{ millout = tso::unwrap__textmill_statechange_millout wrapped_millout;
#
case *textmill_statechange__watchee
#
THE watchee => watchee.millout.drop_watcher textmill_statechange__inport; # Say goodbye to previous watchee.
NULL => ();
esac;
millout.note_watcher # Say hello to new watchee.
(
textmill_statechange__inport,
THE textmill_statechange__millin, # So note_watcher can pass Millout+Millin to millboss at same time, keeping millboss consistent.
note__textmill_statechange
);
textmill_statechange__watchee
:=
THE { wrapped_millout, millout };
}
);
}
also
fun drop__textmill_statechange__watchee (wrapped_millout: mt::Millout) # PUBLIC.
=
{
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchee, textmill_statechange__inport, ... }: Runstate)
=
{ millout = tso::unwrap__textmill_statechange_millout wrapped_millout;
#
case *textmill_statechange__watchee
#
THE watchee => watchee.millout.drop_watcher textmill_statechange__inport; # Say goodbye to previous watchee.
NULL => ();
esac;
textmill_statechange__watchee
:=
NULL;
}
);
};
fun make_millouts # Construct a description of all our outports, for client use.
(
textmill_statechange__outport: mt::Outport,
textmill_statechange__millout: mt::Millout
)
: mt::opm::Map(mt::Millout)
=
{ millouts = mt::opm::empty; # Start with an empty outport map.
#
millouts = mt::opm::set (millouts, textmill_statechange__outport, textmill_statechange__millout ); # Add our textmill_statechange outport.
millouts; # Return map defining all our our outports.
};
fun make_millins
(
textmill_statechange__inport: mt::Inport,
textmill_statechange__millin: mt::Millin
)
: mt::ipm::Map(mt::Millin) # Construct a description of all our inports, for client use.
=
{ millins = mt::ipm::empty; # Start with an empty inport map.
#
millins = mt::ipm::set (millins, textmill_statechange__inport, textmill_statechange__millin ); # Add our textmill_statechange inport.
millins; # Return map defining all our our inports.
};
fun reload_from_file () # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
=
case (*me.filepath)
#
NULL => ();
THE filepath
=>
{ as_lines = file::as_lines;
#
lines = as_lines filepath
except _ =
{
[ "\n" ];
};
lines = map do_line lines
where
fun do_line (string: String)
=
mt::MONOLINE { string, prefix => NULL };
end;
textlines = nl::from_list lines;
me.state := { textlines,
editcount => 1
};
};
esac
);
};
#
fun save_to_file () # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
=
case (*me.filepath)
#
NULL => ();
THE filepath
=>
if *me.dirty # No point saving buffer contents to disk unless they have been modified.
#
# XXX SUCKO FIXME We're not doing any handling of errors like failure to open or write or close here. We probably should also be writing to a tempfile and then renaming to the actual filename only if the complete write+close sequence succeeds.
(file::open_for_write filepath)
->
outstream;
apply do_line (nl::vals_list (*me.state).textlines)
where
fun write_monoline (monoline: mt::Monoline)
=
file::write (outstream, monoline.string);
fun do_line (textline: mt::Textline)
=
case textline
#
mt::MONOLINE monoline
=>
write_monoline monoline;
mt::POLYLINE { line, more }
=>
{ write_monoline line;
apply write_monoline more;
};
esac;
end;
file::close_output outstream;
me.dirty := FALSE;
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::DIRTY_CHANGED { was => TRUE, now => FALSE },
runstate
);
fi;
esac
);
};
#################################################################################
# millboss_to_mill interface fns::
#
#
fun wakeup
{
wakeup_arg: mt::Wakeup_Arg,
wakeup_fn: mt::Wakeup_Arg -> Void # Mill thunk registered via mill_to_millboss.wake_me[].
}
=
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
wakeup_fn wakeup_arg
);
#################################################################################
# texteditor interface fns::
#
#
fun get_maxline () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Int );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
put_in_oneshot (reply_oneshot, the (nl::max_key state.textlines));
}
);
get_from_oneshot reply_oneshot;
};
#
fun pass_maxline (replyqueue: Replyqueue) (reply_handler: Int -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Int );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
put_in_oneshot (reply_oneshot, the (nl::max_key state.textlines));
}
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun get_line (i: Int) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Null_Or(String) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
line = case (nl::find (state.textlines, i))
#
THE line => THE (mt::visible_line line);
NULL => NULL;
esac;
put_in_oneshot (reply_oneshot, line);
}
);
get_from_oneshot reply_oneshot;
};
#
fun pass_line (replyqueue: Replyqueue) (i: Int) (reply_handler: Null_Or(String) -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Null_Or(String) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
line = case (nl::find (state.textlines, i))
#
THE line => THE (mt::visible_line line);
NULL => NULL;
esac;
put_in_oneshot (reply_oneshot, line);
}
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun set_lines (lines: List(String)) # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
=
{ (*me.state) -> { textlines, editcount };
#
lines = map do_line lines
where
fun do_line (string: String)
=
mt::MONOLINE { string, prefix => NULL };
end;
me.state := { textlines => nl::from_list lines,
editcount => editcount + 1
};
}
);
};
#
fun get_lines (first: Int, last: Int) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( List(String) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
put_in_oneshot (reply_oneshot, (line_range (state.textlines, first, last)));
}
);
get_from_oneshot reply_oneshot;
};
#
fun pass_lines (replyqueue: Replyqueue) (first: Int, last: Int) (reply_handler: List(String) -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( List(String) );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
{ state = *me.state;
#
put_in_oneshot (reply_oneshot, (line_range (state.textlines, first, last)));
}
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun get_textstate () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Textstate );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.state)
);
get_from_oneshot reply_oneshot;
};
#
fun pass_textstate (replyqueue: Replyqueue) (reply_handler: mt::Textstate -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Textstate );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.state)
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun get_edit_result # PUBLIC.
#
(arg: mt::Edit_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_or_pass_edit_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun pass_edit_result # PUBLIC.
#
(arg: mt::Edit_Arg)
#
(replyqueue: Replyqueue)
#
(reply_handler: mt::Editfn_Out -> Void)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_or_pass_edit_result (r, arg))
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun get_drawpane_startup_result # PUBLIC.
#
(arg: mt::Drawpane_Startup_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_startup_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_shutdown_result # PUBLIC.
#
(arg: mt::Drawpane_Shutdown_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_shutdown_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_initialize_gadget_result # PUBLIC.
#
(arg: mt::Drawpane_Initialize_Gadget_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_initialize_gadget_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_redraw_request_result # PUBLIC.
#
(arg: mt::Drawpane_Redraw_Request_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_redraw_request_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_mouse_click_result # PUBLIC.
#
(arg: mt::Drawpane_Mouse_Click_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_click_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_mouse_drag_result # PUBLIC.
#
(arg: mt::Drawpane_Mouse_Drag_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_drag_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun get_drawpane_mouse_transit_result # PUBLIC.
#
(arg: mt::Drawpane_Mouse_Transit_Arg)
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( mt::Editfn_Out );
#
put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
put_in_oneshot (reply_oneshot, do_get_drawpane_mouse_transit_result (r, arg))
);
get_from_oneshot reply_oneshot;
};
fun undo () # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ (r: Runstate)
=
do_undo r
);
};
fun set_readonly (readonly: Bool) # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ (runstate as { id, me, textmill_statechange__watchers, ... }: Runstate)
=
{ was = *me.readonly;
now = readonly;
me.readonly := readonly;
tell__textmill_statechange__watchers
(
*textmill_statechange__watchers,
mt::READONLY_CHANGED { was, now },
runstate
);
}
);
};
#
fun get_readonly () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Bool );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.readonly)
);
get_from_oneshot reply_oneshot;
};
#
fun pass_readonly (replyqueue: Replyqueue) (reply_handler: Bool -> Void) # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Bool );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.readonly)
);
put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
};
fun set_textpane_hint (hint: Crypt) # PUBLIC.
=
{ put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
=
me.textpane_hint := hint
);
};
#
fun get_textpane_hint () # PUBLIC.
=
{ reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( Crypt );
#
put_in_mailqueue (textmill_q,
#
\\ ({ id, me, textmill_statechange__watchers, ... }: Runstate)
=
put_in_oneshot (reply_oneshot, *me.textpane_hint)
);
get_from_oneshot reply_oneshot;
};
#
end;
#
fun process_options
(
options: List( mt::Textmill_Option ),
#
{ name,
id,
filename,
text,
textmill_extension
}
)
=
{ my_name = REF name;
my_id = REF id;
my_filename = REF filename;
my_text = REF text;
my_textmill_extension = REF textmill_extension;
apply do_option options
where
fun do_option (mt::MICROTHREAD_NAME n) => my_name := n;
do_option (mt::ID i) => my_id := i;
do_option (mt::INITIAL_FILENAME n) => my_filename := n;
do_option (mt::UTF8 n) => my_text := n;
do_option (mt::TEXTMILL_EXTENSION x) => my_textmill_extension := THE x;
end;
end;
{ name => *my_name,
id => *my_id,
filename => *my_filename,
text => *my_text,
textmill_extension => *my_textmill_extension
};
};
##########################################################################################
# PUBLIC.
#
fun make_textmill_egg
(textmill_arg: mt::Textmill_Arg) # PUBLIC. PHASE 1: Construct our state and initialize from 'options'.
=
{ textmill_arg -> { name, textmill_options };
#
(process_options
( textmill_options,
{ name,
id => id_zero,
filename => "",
text => "\n",
textmill_extension => NULL
}
) )
->
{ name,
id,
filename,
text,
textmill_extension
};
filepath = if (filename == "") NULL;
else THE filename;
fi;
my (id, textmill_options)
=
if (id_to_int(id) == 0)
id = issue_unique_id(); # Allocate unique imp id.
(id, mt::ID id ! textmill_options); # Make our id stable across stop/restart cycles.
else
(id, textmill_options);
fi;
me = { state => REF textstate,
edit_history => REF (bq::make_queue max_history_length), #
filepath => REF filepath,
# XXX BUGGO FIXME We need to ensure here that name is unique. (Or is that an upstream responsibility? Anyhow, someone needs to be doing that.) LATER: There is now a uniquify_name() to help out with this in
src/lib/x-kit/widget/edit/millboss-imp.pkg name => REF name,
dirty => REF FALSE,
readonly => REF FALSE,
textpane_hint => REF { id => issue_unique_id (), # Just being type-correct here. It is up to textpane.pkg to set a reasonable value via set_textpane_hint().
type => "textmill dummy",
info => "Ignore this",
data => DIE "dummy"
}
}
where
textlines = string::lines text;
#
textlines = map do_line textlines
where
fun do_line (string: String)
=
mt::MONOLINE { string, prefix => NULL };
end;
textlines = nl::from_list textlines;
textstate = { textlines,
editcount => 0
};
end;
\\ () = { reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( (Me_Slot, Exports) ); # PUBLIC. PHASE 2: Start our microthread and return our Exports to caller.
#
xlogger::make_thread name (startup (id, textmill_extension, reply_oneshot)); # Note that startup() is curried.
(get_from_oneshot reply_oneshot) -> (me_slot, exports);
fun phase3 # PUBLIC. PHASE 3: Accept our Imports, then wait for Run_Gun to fire.
(
imports: Imports,
run_gun': Run_Gun,
end_gun': End_Gun
)
=
{
put_in_mailslot
(
me_slot,
#
{ me,
textmill_arg,
imports,
run_gun',
end_gun'
}
);
};
(exports, phase3);
};
};
};
end;