PipalTextEditorImpl.mesa
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Louis Monier February 3, 1988 11:33:05 am PST
Bertrand Serlet March 1, 1988 12:57:38 pm PST
PipalTextEditorImpl:
CEDAR
PROGRAM
IMPORTS InputFocus, IO, MessageWindow, Pipal, PipalEdit, PipalInt, PipalMutate, PipalOps, PipalPaint, PipalReal, PipalTextMutant, Rope, TerminalIO =
BEGIN OPEN PipalTextMutant;
Interactive Mutant
textEditorClass: Pipal.Class ← Pipal.RegisterClass[name: "TextEditor", type:
CODE [TextEditorRec]];
TextEditor: TYPE = REF TextEditorRec;
TextEditorRec:
TYPE =
RECORD [
textMutant: TextMutant,
notifyState: NotifyState ← [],
undoRedo: PipalOps.UndoRedo ← []
];
TextEditorSize: PipalReal.SizeProc ~ {
size ← PipalReal.ObjectSize[NARROW [object, TextEditor].textMutant];
};
TextEditorPaint: PipalPaint.PaintProc = {
PipalPaint.Paint[NARROW [object, TextEditor].textMutant, context];
};
NotifyState:
TYPE =
RECORD [
coords: PipalReal.Vector ← PipalReal.zeroVector, -- mx, my
editState: EditState ← reset,
sel: Selections ← primary,
selState: SelState ← reset,
pdelState: PDelState ← reset,
pDel: BOOL ← FALSE,
prevPSel: Selection ← [],
initialSelect: PipalInt.Interval ← [0, 0],
mouseColor: MouseColor ← red,
editMessage: Pipal.ROPE ← NIL];
EditState:
TYPE = {
reset, -- no mutant specified
abort, -- mutant aborted
tolimbo, -- delete primary
toprimary, -- copy/move secondary selection to/onto primary
tosecondary, -- copy/move primary to/onto secondary
toboth -- transpose primary and secondary
};
SelState:
TYPE = {
reset, -- not specified yet
primary, -- making a primary selection
secondary
};
PDelState:
TYPE = {
reset, -- not specified yet
pending, -- making a pending delete selection
not
};
LevelChange:
TYPE = {reduce, expand, same};
MouseColor:
TYPE = {red, yellow, blue, dead};
MessageArray: TYPE = ARRAY BOOL OF ARRAY BOOL OF Pipal.ROPE;
toPrimaryMessages: REF MessageArray ← NEW [MessageArray];
toSecondaryMessages:
REF MessageArray ←
NEW [MessageArray];
Notify: PipalEdit.NotifyProc = {
Complain:
PROC [atom:
ATOM] = {
IO.PutF[TerminalIO.TOS[], "Unimplemented tip table atom %g\n", IO.atom[atom]];
someComplaint ← TRUE;
};
AbortSecondary:
PROC = {
MessageWindow.Append["Make a primary selection first.",TRUE];
MessageWindow.Blink[];
d.editState ← abort; d.mouseColor ← dead
};
EditMessage:
PROC = {
msg: Pipal.
ROPE ←
SELECT d.editState
FROM
tolimbo => "Select for delete",
toprimary => toPrimaryMessages [textMutant.selections[primary].pendingDelete] [textMutant.selections[secondary].pendingDelete],
tosecondary => toSecondaryMessages [textMutant.selections[primary].pendingDelete] [textMutant.selections[secondary].pendingDelete],
toboth => "Select for transpose",
ENDCASE => NIL;
IF msg = NIL OR msg = d.editMessage THEN RETURN;
MessageWindow.Append[msg,TRUE];
d.editMessage ← msg
};
Extend:
PROC [saveEnds:
BOOL] = {
IF d.editState=abort THEN RETURN;
IF d.mouseColor # blue
THEN {
IF d.mouseColor = dead THEN SIGNAL BadMouse;
d.mouseColor ← blue;
d.prevPSel ← textMutant.selections[primary];
saveEnds ← TRUE;
};
{
grain: SelectionGrain ← textMutant.selections[d.sel].granularity;
new: PipalInt.Interval;
closerRight: BOOL;
set: PipalInt.Interval;
IF saveEnds THEN d.initialSelect ← textMutant.selections[d.sel].interval;
[new, closerRight] ← GetSelectionInterval[text, d.coords, grain];
IF d.initialSelect.base<=new.base
THEN {
set.base ← d.initialSelect.base;
set.size ← new.base+new.size-d.initialSelect.base;
}
ELSE {
set.base ← new.base;
set.size ← d.initialSelect.base+d.initialSelect.size-new.base;
};
[message, changed, textMutant] ← SetSelection[textMutant, set, d.sel, d.pDel, closerRight, grain];
IF d.sel=secondary THEN EditMessage[];
};
};
textEditor: TextEditor = NARROW [editor];
d: NotifyState ← textEditor.notifyState; -- we copy because we are changing in place that variable
textMutant: TextMutant ← textEditor.textMutant; -- all operations do return a new TextMutant
text: PipalText ← textMutant.text;
message: Pipal.ROPE ← NIL;
changed: PipalInt.Interval ← [0, PipalInt.infinity];
someComplaint: BOOL ← FALSE;
FOR input ← input, input.rest
UNTIL input=
NIL
DO
WITH input.first
SELECT
FROM
coords: REF Imager.VEC => d.coords ← coords^;
char:
REF
CHAR => {
SELECT char^
FROM
IO.BS => [message, changed, textMutant] ← Erase[textMutant ! Pipal.Error => IF reason=$noCharacterToErase THEN {TerminalIO.PutRope["No character to erase\n"]; CONTINUE} ELSE REJECT];
ENDCASE => {
otherChanged: PipalInt.Interval;
otherMessage: Pipal.ROPE;
newBase: Location ← textMutant.selections[primary].interval.base;
IF textMutant.selections[primary].caretAfter THEN newBase ← newBase + textMutant.selections[primary].interval.size;
[message, changed, textMutant] ← InsertChar[textMutant, char^];
[otherMessage, otherChanged, textMutant] ← SetSelection[tm: textMutant, interval: [newBase+1, 0], selection: primary, pendingDelete: FALSE, caretAfter: FALSE, granularity: point];
changed ← PipalInt.UnionIntervals[changed, otherChanged];
};
PipalPaint.Enqueue[queue, [
type: clearAndPaint,
area: PipalPaint.CreateExplicitArea[PipalTextMutant.GetIntervalArea[textMutant.text, changed]],
data: textMutant]];
};
atom:
ATOM => {
SELECT atom
FROM
$BlueMouse => {
IF d.mouseColor = dead THEN SIGNAL BadMouse;
d.mouseColor ← blue;
d.prevPSel ← textMutant.selections[primary];
};
$DoEdit => {
SELECT d.editState
FROM
tolimbo => [message, changed, textMutant] ← Delete[textMutant];
reset, abort => NULL;
toprimary => {
otherChanged: PipalInt.Interval;
otherMessage: Pipal.ROPE;
[message, changed, textMutant] ← Copy[textMutant];
[otherMessage, otherChanged, textMutant] ← CancelSelection[textMutant, secondary];
changed ← PipalInt.UnionIntervals[changed, otherChanged];
};
tosecondary => Complain[$DoEditToSecondary];
toboth => [message, changed, textMutant] ← Transpose[textMutant];
ENDCASE => ERROR;
d.editState ← reset;
d.sel ← primary;
d.selState ← reset;
d.pdelState ← reset;
d.pDel ← FALSE;
d.mouseColor ← red;
IF d.editMessage # NIL THEN MessageWindow.Clear[];
d.editMessage ← NIL;
};
$ForceSelNotPendDel => {
d.pdelState ← reset;
d.pdelState ← not;
d.pDel ← FALSE;
};
$ForceSelPendDel => {
d.pdelState ← reset;
d.pdelState ← pending;
d.pDel ← TRUE;
};
$RedMouse => {
IF d.mouseColor = dead THEN SIGNAL BadMouse;
d.mouseColor ← red;
d.prevPSel ← textMutant.selections[primary];
};
$SelChar => {
IF
NOT d.editState=abort
THEN {
IF d.sel=secondary AND NOT textMutant.selections[primary].valid THEN AbortSecondary[]
ELSE {
interval: PipalInt.Interval;
closerRight: BOOL;
[interval, closerRight] ← GetSelectionInterval[text, d.coords, char];
[message, changed, textMutant] ← SetSelection[textMutant, interval, d.sel, d.pDel, closerRight, char];
IF d.editState=tolimbo OR d.sel=secondary THEN EditMessage[];
};
};
};
$SelExpand => textMutant.selections[d.sel].granularity ←
SELECT textMutant.selections[d.sel].granularity
FROM
point => char,
char => word,
ENDCASE => word;
$SelExtend => Extend[FALSE];
$SelNotPendDel =>
IF d.pdelState=reset
THEN {
d.pdelState ← not;
d.pDel ← FALSE;
};
$SelPendDel =>
IF d.pdelState=reset
THEN {
d.pdelState ← pending;
d.pDel ← TRUE;
};
$SelReduce => textMutant.selections[d.sel].granularity ← char;
$SelSecondary =>
IF d.selState=reset
THEN {
d.selState ← secondary;
d.sel ← secondary;
};
$SelStartExtend => Extend[TRUE];
$SelUpdate => {
IF
NOT d.editState=abort
THEN {
IF d.sel=secondary AND NOT textMutant.selections[primary].valid THEN AbortSecondary[]
ELSE {
grain: SelectionGrain ← textMutant.selections[d.sel].granularity;
interval: PipalInt.Interval;
closerRight: BOOL;
[interval, closerRight] ← GetSelectionInterval[text, d.coords, grain];
[message, changed, textMutant] ← SetSelection[textMutant, interval, d.sel, d.pDel, closerRight, grain];
IF d.sel=secondary THEN EditMessage[];
};
};
};
$SelWord => {
IF
NOT d.editState=abort
THEN {
IF d.sel=secondary AND NOT textMutant.selections[primary].valid THEN AbortSecondary[]
ELSE {
interval: PipalInt.Interval;
closerRight: BOOL;
[interval, closerRight] ← GetSelectionInterval[text, d.coords, word];
[message, changed, textMutant] ← SetSelection[textMutant, interval, d.sel, d.pDel, closerRight, word];
IF d.editState=tolimbo OR d.sel=secondary THEN EditMessage[];
};
};
};
$ToLimbo => IF d.editState=reset THEN d.editState ← tolimbo;
$ToPrimary => IF d.editState=reset OR d.editState=tolimbo THEN d.editState ← toprimary;
$YellowMouse => {
IF d.mouseColor = dead THEN SIGNAL BadMouse;
d.mouseColor ← yellow;
d.prevPSel ← textMutant.selections[primary];
};
ENDCASE => {
IO.PutF[TerminalIO.TOS[], "Unknown tip table atom %g\n", IO.atom[atom]];
someComplaint ← TRUE;
};
InputFocus.SetInputFocus[viewer];
PipalPaint.Enqueue[queue, [
type: clearAndPaint,
area: PipalPaint.CreateExplicitArea[PipalTextMutant.GetIntervalArea[textMutant.text, changed]],
data: textMutant]];
};
ENDCASE; ENDLOOP;
newEditor ←
NEW [TextEditorRec ← [
textMutant: textMutant,
notifyState: d,
undoRedo: PipalOps.Do[textEditor.undoRedo, message, textEditor]
]];
IF someComplaint THEN TerminalIO.PutRope["\n"];
};
Little Quick and Dirty Test
EditPipalText: PipalEdit.EditProc = {
textMutant: TextMutant ← NARROW [mutant];
editor ← NEW [TextEditorRec ← [textMutant: textMutant]];
PipalEdit.CreateBiscroller[editor, Rope.Cat[PipalOps.wDir, "PipalTextEditor.TIP"], Notify, TRUE];
};
Test:
PUBLIC
PROC [size: PipalReal.Size]
RETURNS [editor: TextEditor] = {
text: PipalText = CreatePipalText[NIL, size];
editor ← NARROW [PipalEdit.Edit[PipalMutate.Mutation[text]]];
};
Undo/Redo Commands
DoUndoRedo:
PROC [old: TextEditor, message: Pipal.
ROPE, op: PipalOps.UndoRedoOp]
RETURNS [new: TextEditor] = {
newUndoRedo: PipalOps.UndoRedo;
newState: REF;
newTE: TextEditor;
[newUndoRedo, newState] ← op[old.undoRedo, old];
newTE ← NARROW [newState];
new ←
NEW [TextEditorRec ← [
textMutant: newTE.textMutant,
notifyState: newTE.notifyState,
undoRedo: newUndoRedo
]];
};
ResetCommand: PipalMutate.CommandProc ~ {
te: TextEditor = NARROW [mutant];
new ← DoUndoRedo[te, "Reset", PipalOps.Reset];
};
UndoCommand: PipalMutate.CommandProc ~ {
te: TextEditor = NARROW [mutant];
IF te.undoRedo.undo=
NIL
THEN {TerminalIO.PutF["*** Cant Undo!\n"]; RETURN [changedArea, Pipal.void, te]};
new ← DoUndoRedo[te, "Undo", PipalOps.Undo];
};
RedoCommand: PipalMutate.CommandProc ~ {
te: TextEditor = NARROW [mutant];
IF te.undoRedo.redo=
NIL
THEN {TerminalIO.PutF["*** Cant Redo!\n"]; RETURN [changedArea, Pipal.void, te]};
new ← DoUndoRedo[te, "Redo", PipalOps.Redo];
};
FlushCommand: PipalMutate.CommandProc ~ {
te: TextEditor = NARROW [mutant];
new ←
NEW [TextEditorRec ← [
textMutant: te.textMutant,
notifyState: te.notifyState,
undoRedo: []
]];
RETURN [changedArea, Pipal.void, new];
};
Initialization
PipalMutate.RegisterCommand[textEditorClass, $Reset, ResetCommand];
PipalMutate.RegisterCommand[textEditorClass, $Undo, UndoCommand];
PipalMutate.RegisterCommand[textEditorClass, $Redo, RedoCommand];
PipalMutate.RegisterCommand[textEditorClass, $Flush, FlushCommand];
Pipal.PutClassMethod[textEditorClass, PipalReal.sizeMethod, NEW [PipalReal.SizeProc ← TextEditorSize]];
Pipal.PutClassMethod[textEditorClass, PipalPaint.paintMethod, NEW [PipalPaint.PaintProc ← TextEditorPaint]];
Pipal.PutClassMethod[textMutantClass, PipalEdit.editMethod,
NEW [PipalEdit.EditProc ← EditPipalText]];
toPrimaryMessages[FALSE][FALSE] ← "Select for copy to caret";
toPrimaryMessages[FALSE][TRUE] ← "Select for move to caret";
toPrimaryMessages[TRUE][FALSE] ← "Select replacement";
toPrimaryMessages[TRUE][TRUE] ← "Select for move onto";
toSecondaryMessages[FALSE][FALSE] ← "Select destination for copy";
toSecondaryMessages[FALSE][TRUE] ← "Select for replacement";
toSecondaryMessages[TRUE][FALSE] ← "Select destination for move";
toSecondaryMessages[TRUE][TRUE] ← "Select destination for move onto";