TEditSelectionImpl.mesa
Copyright Ó 1985, 1987, 1988 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) June 10, 1985 11:52:15 pm PDT
Michael Plass, October 20, 1987 5:06:49 pm PDT
Doug Wyatt, February 17, 1988 6:12:42 pm PST
DIRECTORY
Carets USING [StartCaret, StopCaret],
Convert USING [Error, IntFromRope],
EditSpan USING [CompareNodeOrder],
Imager USING [Color, Context, MaskRectangleI, SetColor],
ImagerBackdoor USING [MakeStipple],
InputFocus USING [GetInputFocus, SetInputFocus],
MessageWindow USING [Append, Blink, Clear],
NodeProps USING [GetProp],
Process USING [Detach, GetCurrent],
Rope USING [Concat, IsEmpty, ROPE, Size, Substr],
TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, SelectionRec, TEditDocumentData],
TEditInput USING [CloseEvent, interrupt],
TEditLocks USING [LockDocAndTdd, LockRef, Unlock, UnlockDocAndTdd],
TEditOps USING [GetSelContents, RememberCurrentPosition],
TEditProfile USING [selectionCaret],
TEditRefresh USING [ScrollToEndOfSel],
TEditSelection USING [FindWhere, ForceDown, IsDown, SetSelLooks],
TEditSelectionOps USING [],
TEditSelectionPrivate USING [CharPositionInCachedLine],
TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh],
TextEdit USING [FetchChar, Size],
TextFind USING [MalformedPattern],
TextNode USING [EndPos, FirstChild, ForwardClipped, LastWithin, Level, Location, LocWithin, Parent, Ref, Root, Span, StepBackward, StepForward],
TreeFind USING [CreateFromRope, Finder, Try, TryBackwards],
ViewerClasses USING [ModifyProc, Viewer],
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, BlinkDisplay, FetchProp, PaintViewer];
TEditSelectionImpl: CEDAR MONITOR
IMPORTS Carets, Convert, EditSpan, Imager, ImagerBackdoor, InputFocus, MessageWindow, NodeProps, Process, Rope, TEditInput, TEditLocks, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TEditSelectionPrivate, TEditTouchup, TextEdit, TextFind, TextNode, TreeFind, ViewerLocks, ViewerOps
EXPORTS TEditSelection, TEditSelectionOps
= BEGIN
Node: TYPE = TextNode.Ref;
ROPE: TYPE = Rope.ROPE;
Selection: TYPE = TEditDocument.Selection;
SelectionId: TYPE ~ TEditDocument.SelectionId;
FindWhere: TYPE ~ TEditSelection.FindWhere;
Viewer: TYPE = ViewerClasses.Viewer;
Selection Locks
LockRec: TYPE = RECORD [
process: PROCESSNIL, -- the process holding the lock
whoLast, whoFirst: ROPE, -- the last lockers
count, maxCount: [0..255] ← 0 -- number of times inside the lock
];
LockRef: TYPE = REF LockRec;
selLock: ARRAY SelectionId OF LockRef ← [NEW[LockRec], NEW[LockRec], NEW[LockRec]];
unlocked: CONDITION;
UnlockDocAndPSel: PUBLIC PROC [root: Node] = {
TEditLocks.Unlock[root]; UnlockSel[primary]
};
LockBothSelections: PUBLIC PROC [who: ROPE] = {
LockSel[primary, who]; LockSel[secondary, who]
};
UnlockBothSelections: PUBLIC PROC = { UnlockSel[primary]; UnlockSel[secondary] };
LockSel: PUBLIC ENTRY PROC [selection: SelectionId, who: ROPE] = {
lock the selection so that no other process can change it
ENABLE UNWIND => NULL;
myProcess: PROCESS;
lock: LockRef = selLock[selection];
TRUSTED {myProcess ← LOOPHOLE[Process.GetCurrent[]]};
IF myProcess#lock.process THEN {
WHILE lock.count>0 DO WAIT unlocked; ENDLOOP;
lock.process ← myProcess;
};
IF lock.count=0 THEN lock.whoFirst ← who ELSE lock.whoLast ← who;
lock.count ← lock.count+1;
lock.maxCount ← MAX[lock.maxCount, lock.count];
};
UnlockSel: PUBLIC ENTRY PROC [selection: SelectionId ← primary] = {
give up lock on the selection
ENABLE UNWIND => NULL;
lock: LockRef = selLock[selection];
TRUSTED { IF lock.process # Process.GetCurrent[] THEN ERROR };
IF (lock.count ← lock.count-1) > 0 THEN RETURN;
lock.whoLast ← lock.whoFirst ← NIL;
BROADCAST unlocked
};
Selection Allocation
pSel: PUBLIC Selection ← Create[];
sSel: PUBLIC Selection ← Create[];
fSel: PUBLIC Selection ← Create[];
oldSel: PUBLIC Selection ← Create[];
nilSel: PUBLIC Selection ← Create[];
tSel1, tSel2, tSel3: Selection ← NIL; -- cache of temporary selection records
Create: PUBLIC PROC RETURNS [Selection] = {
RETURN [NEW[TEditDocument.SelectionRec]]
};
Alloc: PUBLIC ENTRY PROC RETURNS [tSel: Selection] = {
ENABLE UNWIND => NULL;
IF tSel3 # NIL THEN { tSel ← tSel3; tSel3 ← NIL }
ELSE IF tSel2 # NIL THEN { tSel ← tSel2; tSel2 ← NIL }
ELSE IF tSel1 # NIL THEN { tSel ← tSel1; tSel1 ← NIL }
ELSE tSel ← Create[];
};
Free: PUBLIC ENTRY PROC [tSel: Selection] = {
ENABLE UNWIND => NULL;
IF tSel3 = tSel OR tSel2 = tSel OR tSel1 = tSel THEN ERROR;
IF tSel3 = NIL THEN tSel3 ← tSel
ELSE IF tSel2 = NIL THEN tSel2 ← tSel
ELSE IF tSel1 = NIL THEN tSel1 ← tSel;
};
Copy: PUBLIC ENTRY PROC [source, dest: Selection] = {
ENABLE UNWIND => NULL;
dest^ ← source^;
};
Selection Display and Control
MakePointSelection: PUBLIC PROC [selection: Selection, pos: TextNode.Location] = {
make a point selection at pos out the the current primary selection
tSel: Selection ← Alloc[];
Copy[source: selection, dest: tSel];
tSel.start.pos ← tSel.end.pos ← pos;
tSel.granularity ← point;
tSel.pendingDelete ← FALSE;
tSel.insertion ← before;
MakeSelection[selection: primary, new: tSel];
Free[tSel];
};
ChangeSelections: PROC [proc: PROC [tSel: Selection], sel: Selection] = {
tSel: Selection ← Alloc[];
LockBothSelections["ChangeBothSelections"];
{ ENABLE UNWIND => UnlockBothSelections[];
Copy[source: sel, dest: tSel]; proc[tSel]
};
UnlockBothSelections[]; Free[tSel]
};
PushOrExchangeSelections: PUBLIC PROC = {
DoPush: PROC [tSel: Selection] = {
MakeSelection[IF sSel.viewer=NIL THEN NIL ELSE sSel, primary];
MakeSelection[IF tSel.viewer=NIL THEN NIL ELSE tSel, secondary]
};
ChangeSelections[DoPush, pSel]
};
MakePrimary: PUBLIC PROC = {
make secondary selection be the primary
DoMakePrimary: PROC [tSel: Selection] = {
Deselect[secondary]; MakeSelection[tSel, primary]
};
ChangeSelections[DoMakePrimary, sSel]
};
MakeSecondary: PUBLIC PROC = {
make secondary selection be the primary
DoMakeSecondary: PROC [tSel: Selection] = {
Deselect[primary];
MakeSelection[tSel, secondary] ;
};
ChangeSelections[DoMakeSecondary, pSel]
};
CancelPrimary: PUBLIC PROC = {
MakeSelection[NIL, primary];
};
CancelSecondary: PUBLIC PROC = {
MakeSelection[NIL, secondary];
};
CancelFeedback: PUBLIC PROC = {
MakeSelection[NIL, feedback];
};
FakeSecondary: PUBLIC PROC [sel: Selection] = {
inner: PROC = {
IF sSel.viewer # NIL THEN Deselect[secondary];
Copy[source: sel, dest: sSel];
};
WithLockedSelection[secondary, inner, "FakeSecondary"];
};
Deselect: PUBLIC PROC [selection: SelectionId ← primary] = {
Take down the selection without changing the input focus.
inner: PROC = {
sel: Selection = SELECT selection FROM
primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR;
viewer: Viewer = sel.viewer;
op: ATOM = SELECT selection FROM
primary => $TakeDownPSel,
secondary => $TakeDownSSel,
feedback => $TakeDownFSel,
ENDCASE => ERROR;
down: BOOL = TEditSelection.IsDown[selection];
Copy[source: nilSel, dest: sel];
IF viewer#NIL AND NOT down THEN {
take the selection down from the screen
ViewerOps.PaintViewer[viewer, client, FALSE, op];
IF NOT TEditSelection.IsDown[selection] THEN TEditSelection.ForceDown[selection];
If the selection was not in a visible viewer, PaintViewer didn't call our paint proc, so the state didn't change. Force change directly.
}
};
WithLockedSelection[selection, inner, "Deselect"];
};
MakeSelection: PUBLIC PROC [new: Selection ← NIL, selection: SelectionId ← primary, startValid, endValid: BOOLFALSE, forkPaint: BOOLTRUE] = {
Changes input focus as well as the selection.
inner: PROC = {
sel: Selection;
op: ATOM;
SELECT selection FROM
primary => {
if: Viewer = InputFocus.GetInputFocus[].owner;
sel ← pSel;
op ← $ShowPSel;
IF new=NIL
THEN {IF if#NIL THEN InputFocus.SetInputFocus[NIL]}
ELSE {IF if#new.viewer THEN InputFocus.SetInputFocus[new.viewer]};
};
feedback => {
sel ← fSel;
op ← $ShowFSel;
};
secondary => {
sel ← sSel;
op ← $ShowSSel;
};
ENDCASE => ERROR;
IF new=NIL OR new.viewer # sel.viewer THEN Deselect[selection];
IF new#NIL THEN {
IF selection#feedback AND fSel.viewer=new.viewer THEN Deselect[feedback];
When called to make a non-feedback selection is the viewer containing the feedback selection, automatically cancel the feedback selection.
Copy[source: new, dest: sel];
sel.start.metricsValid ← startValid;
sel.end.metricsValid ← endValid;
IF sel.granularity=point THEN sel.pendingDelete ← FALSE;
IF forkPaint
THEN {
showSel: REF ShowSelRec = SELECT selection FROM
primary => showPSel, secondary => showSSel, ENDCASE => showFSel;
IF showSel.process = NIL THEN TRUSTED {
Process.Detach[showSel.process ← FORK ShowSel[showSel, op ! ABORTED => CONTINUE]]
}
}
ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op];
};
};
WithLockedSelection[selection, inner, "MakeSelection"];
};
ShowSelRec: TYPE = RECORD [ process: PROCESS, selection: SelectionId ];
showPSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, primary]];
showSSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, secondary]];
showFSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, feedback]];
ShowSel: PROC [my: REF ShowSelRec, op: ATOM] = {
selection: TEditDocument.SelectionId ← my.selection;
sel: Selection ← SELECT selection FROM
primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR;
inner: PROC = {
viewer: Viewer ← sel.viewer;
inner: PROC = {
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
IF NOT TEditTouchup.LockAfterRefresh[tdd, "ShowSel"] THEN RETURN;
ViewerOps.PaintViewer[viewer, client, FALSE, op
! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]];
TEditTouchup.UnlockAfterRefresh[tdd];
};
ENDCASE;
};
IF viewer#NIL THEN ViewerLocks.CallUnderWriteLock[inner, viewer];
my.process ← NIL;
};
WithLockedSelection[selection, inner, "ShowSel"];
};
lightGrey: Imager.Color ← ImagerBackdoor.MakeStipple[stipple: 0208H, xor: TRUE];
darkGrey: Imager.Color ← ImagerBackdoor.MakeStipple[stipple: 0A5A5H, xor: TRUE];
veryDarkGrey: Imager.Color ← ImagerBackdoor.MakeStipple[stipple: 0EBEBH, xor: TRUE];
black: Imager.Color ← ImagerBackdoor.MakeStipple[stipple: 0FFFFH, xor: TRUE];
SelColor: TYPE = {black, lightGrey, darkGrey, veryDarkGrey} ← black;
SelBound: TYPE = {solid, line} ← solid;
selectionLineWidth: NAT ← 2;
feedbackLineWidth: NAT ← 4;
feedbackLineRaise: INTEGER ← 1;
selectionDescentLimit: NAT ← 7;
N.B. Also in TEditSelection
MarkSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, selection: Selection, id: SelectionId] = {
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
lines: TEditDocument.LineTable = tdd.lineTable;
vHeight: INTEGER = selection.viewer.ch;
selBound: SelBound = IF selection.pendingDelete AND id#feedback THEN solid ELSE line;
selColor: SelColor = IF id=primary THEN black ELSE IF id=feedback THEN veryDarkGrey ELSE IF selection.pendingDelete THEN lightGrey ELSE darkGrey;
EffectSelect: PROC [x, y, w, h: INTEGER] = {
lineHeight: NAT ← selectionLineWidth;
bottom: INTEGER ← vHeight-y-h;
IF id=feedback THEN { lineHeight ← feedbackLineWidth; bottom ← bottom - feedbackLineRaise };
SELECT selBound FROM
line => Imager.MaskRectangleI[dc, x, bottom, w, lineHeight];
solid => Imager.MaskRectangleI[dc, x, bottom, w, vHeight-y-bottom];
ENDCASE => ERROR;
};
IF selection.end.line<0 OR selection.start.line=LAST[INTEGER] OR (selection.start.line=selection.end.line AND selection.end.clipped) THEN RETURN; -- not visible
SELECT selColor FROM
black => Imager.SetColor[dc, black];
lightGrey => Imager.SetColor[dc, lightGrey];
darkGrey => Imager.SetColor[dc, darkGrey];
veryDarkGrey=> Imager.SetColor[dc, veryDarkGrey];
ENDCASE => ERROR;
SELECT TRUE FROM
selection.start.line = selection.end.line OR (selection.start.line < 0 AND selection.end.line = 0) => {
one liner
x, y: INTEGER;
IF selection.start.line < 0 OR selection.start.clipped
THEN {
select from beginning of line
line: INTEGERMAX[0, selection.start.line];
x ← lines[line].xOffset;
y ← lines[line].yOffset-lines[line].ascent;
}
ELSE { x ← selection.start.x; y ← selection.start.y };
IF selection.end.clipped THEN ERROR; -- previous tests imply NOT end.clipped
EffectSelect[x, y, selection.end.x-x+selection.end.w, selection.end.h]
};
selection.start.line=lines.lastLine AND selection.end.line>lines.lastLine => {
EffectSelect[selection.start.x, selection.start.y, lines[selection.start.line].width + lines[selection.start.line].xOffset - selection.start.x, selection.start.h]
};
ENDCASE => {
sl: INTEGER = MAX[0, MIN[lines.lastLine, selection.start.line]];
el: INTEGER = MAX[0, MIN[lines.lastLine, selection.end.line]];
EffectSelAll: PROC [n: INTEGER] = {
EffectSelect[lines[n].xOffset, lines[n].yOffset-lines[n].ascent, lines[n].width,
lines[n].ascent+lines[n].descent]
};
IF sl=selection.start.line AND NOT selection.start.clipped
THEN {
select end portion of sl
EffectSelect[selection.start.x, selection.start.y, (lines[sl].width + lines[sl].xOffset - selection.start.x), selection.start.h]
}
ELSE {
select all of sl
EffectSelAll[sl];
};
FOR n: INTEGER IN (sl..el) DO
select all of the intermediate lines
EffectSelAll[n];
ENDLOOP;
SELECT TRUE FROM
selection.end.clipped => {}; -- end.line is not actually part of the selection
el=selection.end.line => {
EffectSelect[lines[el].xOffset, selection.end.y, selection.end.x - lines[el].xOffset + selection.end.w, selection.end.h];
};
ENDCASE => {
EffectSelAll[el];-- end.line is off screen, so select all of el
};
};
};
ENDCASE;
};
ExtendSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, old, new: Selection, id: SelectionId, updateEnd: BOOL] = {
when done, old is valid (it's = pSel, e.g.) and new^ = old^
TooHard: PROC = {
MarkSelection[dc, viewer, old, id]; -- take it down
FixupSelection[new, viewer]; -- get position info about the new selection
MarkSelection[dc, viewer, new, id]; -- put it up
Copy[source: new, dest: old];
};
IF updateEnd
THEN {
update right end
BumpLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = {
n: Node = loc.node;
where: INT ← loc.where+1;
IF where >= TextEdit.Size[n] THEN RETURN [[TextNode.StepForward[n],0]];
RETURN [[n,where]];
};
tooHard: BOOL = old.end.line NOT IN [0..LAST[INTEGER]);
sameNode: BOOL = (new.end.pos.node = old.end.pos.node);
IF tooHard THEN {TooHard; RETURN};
SELECT TRUE FROM
new.end.pos=old.end.pos => {};
(sameNode AND new.end.pos.where>old.end.pos.where) OR (NOT sameNode AND EditSpan.CompareNodeOrder[new.end.pos.node, old.end.pos.node]=after) => {
new end after old end
new.start ← old.end;
new.start.pos ← BumpLoc[new.start.pos];
IF sameNode AND new.start.pos.node # new.end.pos.node THEN
new.start.pos ← new.end.pos;
This weird case can happen if old ended with final CR of node and new is in the null line displayed after the CR.
FixupSelection[new, viewer];
MarkSelection[dc, viewer, new, id];
old.end ← new.end;
}
ENDCASE => {
new end before old end
save: TextNode.Location = new.end.pos;
new.start ← new.end;
new.start.pos ← BumpLoc[save];
IF sameNode AND new.start.pos.node # new.end.pos.node THEN
new.start.pos ← old.end.pos;
old ended in null line after final CR, new ends with the final CR
new.end ← old.end;
FixupSelection[new, viewer, TRUE, FALSE]; -- fix start
MarkSelection[dc, viewer, new, id];
old.end ← new.start;
old.end.pos ← save;
FixupSelection[old, viewer, FALSE, TRUE];
};
}
ELSE {
update start
DecLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = {
n: Node;
IF loc.where > 0 THEN RETURN [[loc.node, loc.where-1]];
n ← TextNode.StepBackward[loc.node];
RETURN [[n,MAX[TextEdit.Size[n],1]-1]]
};
tooHard: BOOL = old.start.line NOT IN [0..LAST[INTEGER]);
sameNode: BOOL = (new.start.pos.node = old.start.pos.node);
IF tooHard THEN {TooHard; RETURN};
SELECT TRUE FROM
new.start.pos=old.start.pos => {};
(sameNode AND new.start.pos.where<old.start.pos.where) OR (NOT sameNode AND EditSpan.CompareNodeOrder[new.start.pos.node, old.start.pos.node]=before) => {
new start comes before old start
new.end.pos ← DecLoc[old.start.pos];
FixupSelection[new, viewer];
MarkSelection[dc, viewer, new, id];
old.start ← new.start;
}
ENDCASE => {
new start comes after old start
save: TextNode.Location = new.start.pos;
new.end.pos ← DecLoc[save];
new.start ← old.start;
FixupSelection[new, viewer, FALSE, TRUE];
MarkSelection[dc, viewer, new, id];
old.start.pos ← save;
FixupSelection[old, viewer, TRUE, FALSE];
};
};
Copy[source: old, dest: new];
};
CannotFindIt: PUBLIC ERROR = CODE;
ComputeSpanLines: PUBLIC PROC [viewer: Viewer, span: TextNode.Span] RETURNS [start, end: INTEGER, startClipped, endClipped: BOOL] = {
[start, startClipped] ← ComputePosLine[viewer, span.start];
SELECT TRUE FROM
span.start=span.end => { end ← start; endClipped ← startClipped };
start=LAST[INTEGER] => end ← LAST[INTEGER]; -- scrolled off bottom
ENDCASE => {
firstLine: INTEGER = IF start<=0 THEN 0 ELSE IF startClipped THEN start-1 ELSE start;
[end, endClipped] ← ComputePosLine[viewer, span.end, firstLine];
};
};
ComputePosLine: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER ← 0] RETURNS [line: INTEGER, clipped: BOOL] = {
sp: TEditDocument.SelectionPoint ← ComputePosPoint[viewer, pos, firstLine, TRUE];
RETURN [sp.line, sp.clipped];
};
ComputePosPoint: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER ← 0, lineOnly: BOOLFALSE] RETURNS [sp: TEditDocument.SelectionPoint] = {
this must work correctly even if the node was filtered out for some reason
if pos was filtered, return line where it would have been and set clipped=TRUE
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
foundPos: BOOLFALSE;
IsPosPastLine: PROC [pos: TextNode.Location, line: INTEGER] RETURNS [BOOL] = {
next: INT;
next ← lines[line].pos.where+lines[line].nChars;
IF pos.where >= next AND lines[line].end # eon THEN RETURN [TRUE]; -- it is past this line
RETURN [FALSE]
};
FindPos: PROC [pos: TextNode.Location] RETURNS [found: BOOL, line: INTEGER] = {
found ← FALSE;
FOR n: INTEGER IN [firstLine..lines.lastLine] DO
SELECT TRUE FROM
lines[n].pos.node=pos.node => {
IF pos.where < lines[n].pos.where THEN EXIT; -- have gone past it
found ← TRUE;
line ← n
};
found => EXIT;
ENDCASE;
ENDLOOP
};
lines: TEditDocument.LineTable ← tdd.lineTable;
sp.pos ← pos;
IF lines[0].pos.node = pos.node THEN {
IF lines[0].pos.where > pos.where THEN {
before beginning of text
sp.y ← sp.line ← -LAST[INTEGER];
RETURN;
};
};
IF lines[lines.lastLine].pos.node=pos.node AND IsPosPastLine[pos,lines.lastLine] THEN {
sp.y ← sp.line ← LAST[INTEGER]; -- after end of text
RETURN;
};
firstLine ← MIN[lines.lastLine,MAX[firstLine,0]];
[foundPos, sp.line] ← FindPos[pos]; sp.clipped ← FALSE;
IF NOT foundPos THEN
SELECT EditSpan.CompareNodeOrder[pos.node, lines[0].pos.node] FROM
same, before => { sp.line ← sp.y ← -LAST[INTEGER]; RETURN }; -- before beginning
disjoint => ERROR CannotFindIt;
ENDCASE => {
-- pos.node comes after first line node
IF tdd.clipLevel = TEditDocument.maxClip AND tdd.commentFilter=includeComments
THEN {
nothing special going on, so would have found it if not after end
sp.line ← sp.y ← LAST[INTEGER];
RETURN
} -- after end
ELSE {
must think about this harder
SELECT EditSpan.CompareNodeOrder[pos.node, lines[lines.lastLine].pos.node] FROM
same, after => {
it really is after the end
sp.line ← sp.y ← LAST[INTEGER];
RETURN
};
disjoint => ERROR CannotFindIt; -- may have been deleted or moved
ENDCASE => { -- it got clipped
n: Node ← pos.node;
delta: INTEGER ← TextNode.Level[n]-tdd.clipLevel;
firstLine ← 0; -- need to let FindPos look at all the previous lines
FOR i:INTEGER IN [0..delta) DO n ← TextNode.Parent[n]; ENDLOOP;
n ← TextNode.ForwardClipped[n,tdd.clipLevel].nx;
[foundPos, sp.line] ← FindPos[[n,0]];
IF NOT foundPos THEN ERROR CannotFindIt;
may have been restored by Undo
sp.clipped ← TRUE;
};
};
};
IF lineOnly OR sp.clipped THEN RETURN; -- otherwise, compute metrics
sp.y ← lines[sp.line].yOffset-lines[sp.line].ascent;
sp.h ← lines[sp.line].ascent+MIN[lines[sp.line].descent, selectionDescentLimit];
[sp.x, sp.w] ← TEditSelectionPrivate.CharPositionInCachedLine[viewer, sp.line, pos];
IF sp.x = LAST[INTEGER] THEN {
past this line
IF sp.line=lines.lastLine
THEN {sp.line ← sp.y ← LAST[INTEGER]}
ELSE {sp.x ← lines[sp.line].width; sp.w ← 0};
};
sp.metricsValid ← TRUE;
};
ENDCASE;
};
FixupSelection: PUBLIC PROC [selection: Selection, viewer: Viewer, start, end: BOOLTRUE] = {
recompute the xywh fields in the selection
fixes selection screen info assuming start.pos and end.pos are correct
IF start THEN selection.start ← ComputePosPoint[viewer, selection.start.pos];
IF end THEN
SELECT TRUE FROM
selection.start.pos=selection.end.pos => {
no need to re-compute
selection.end ← selection.start;
};
selection.start.line=LAST[INTEGER] => {
scrolled off bottom
selection.end.line ← LAST[INTEGER];
selection.end.metricsValid ← TRUE;
};
ENDCASE => {
firstLine: INTEGER = (
IF selection.start.line<=0 THEN 0 ELSE IF selection.start.clipped THEN selection.start.line-1 ELSE selection.start.line
);
selection.end ← ComputePosPoint[viewer, selection.end.pos, firstLine];
};
};
FixupCaret: PUBLIC PROC [selection: Selection] = {
recompute the xy fields in the caret
lines: TEditDocument.LineTable = selection.data.lineTable;
node: Node ← selection.end.pos.node;
size: INT ← TextEdit.Size[node];
PutCaretOnNextLine: PROC = {
SELECT selection.end.line FROM
IN [0..lines.lastLine) => {
nextLine: INTEGER ← selection.end.line+1;
selection.caretX ← lines[nextLine].xOffset;
selection.caretY ← selection.viewer.ch - lines[nextLine].yOffset - lines[nextLine].descent;
};
= lines.lastLine => {
selection.caretX ← lines[selection.end.line].xOffset;
selection.caretY ← selection.viewer.ch - selection.end.y - selection.end.h - selection.end.h;
};
ENDCASE => ERROR;
};
IF selection.insertion=before
THEN {
IF selection.start.clipped
THEN selection.caretY ← LAST[INTEGER] -- caret not visible
ELSE {
selection.caretX ← selection.start.x;
selection.caretY ← IF selection.start.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.start.y - selection.start.h;
};
}
ELSE {
SELECT TRUE FROM
selection.end.clipped => {
caret not visible
selection.caretY ← LAST[INTEGER];
};
selection.end.pos.where IN [0..size) AND selection.end.line IN [0..lines.lastLine] AND TextEdit.FetchChar[node,selection.end.pos.where]=[0,15B] => {
PutCaretOnNextLine[];
};
ENDCASE => {
selection.caretX ← selection.end.x+selection.end.w;
selection.caretY ← (
IF selection.end.line NOT IN [0..lines.lastLine]
THEN LAST[INTEGER]
ELSE selection.viewer.ch - selection.end.y - selection.end.h
);
};
};
};
KillSelection: PUBLIC PROC = {
selection actually get taken down in our InputModify Proc
IF InputFocus.GetInputFocus[]#NIL THEN InputFocus.SetInputFocus[];
};
ModifyPSel: PROC [proc: PROC [root: Node, tSel: Selection]] = {
inner: PROC = {
root: Node ← SelectionRoot[pSel];
IF root#NIL THEN {
tSel: Selection ← Alloc[];
Copy[source: pSel, dest: tSel];
proc[root, tSel ! UNWIND => Free[tSel]];
MakeSelection[new: tSel];
Free[tSel];
};
};
WithLockedSelection[primary, inner, "CallWithSelLock"];
};
SelectEverything: PUBLIC PROC = {
expand to include everything
DoSelEverything: PROC [root: Node, tSel: Selection] = {
root ← TextNode.Root[tSel.start.pos.node];
tSel.start.pos ← [TextNode.FirstChild[root], 0];
tSel.end.pos.node ← TextNode.LastWithin[root];
tSel.end.pos.where ← TextEdit.Size[tSel.end.pos.node]-1;
tSel.granularity ← branch;
};
ModifyPSel[DoSelEverything]
};
PendingDeleteSelection: PUBLIC PROC = {
DoPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete ← TRUE };
ModifyPSel[DoPDel]
};
NotPendingDeleteSelection: PUBLIC PROC = {
DoNotPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete ← FALSE };
ModifyPSel[DoNotPDel]
};
CaretBeforeSelection: PUBLIC PROC = {
DoCaretBeforeSelection: PROC [root: Node, tSel: Selection] = {tSel.insertion ← before };
ModifyPSel[DoCaretBeforeSelection]
};
CaretAfterSelection: PUBLIC PROC = {
DoCaretAfterSelection: PROC [root: Node, tSel: Selection] = { IF tSel.granularity # point THEN tSel.insertion ← after };
ModifyPSel[DoCaretAfterSelection]
};
Misc functions
WithLockedSelection: PROC [which: SelectionId, inner: PROC, why: ROPE] = {
LockSel[which, why];
inner[ ! UNWIND => UnlockSel[which]];
UnlockSel[which];
};
SelectionRoot: PUBLIC PROC [s: Selection ← pSel] RETURNS [root: Node] = {
IF s.viewer=NIL THEN RETURN [TextNode.Root[s.start.pos.node]];
WITH s.viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => RETURN [tdd.text];
ENDCASE => RETURN [NIL];
};
InputModify: PUBLIC ViewerClasses.ModifyProc = {
WITH self.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
SELECT change FROM
set, pop => Carets.StartCaret[self, pSel.caretX, pSel.caretY, primary];
kill => {
inner: PROC = {Copy[source: pSel, dest: prop]; Deselect[primary]};
prop: Selection ← NARROW[ViewerOps.FetchProp[self, $SelectionHistory]];
Carets.StopCaret[primary];
IF prop=NIL THEN ViewerOps.AddProp[self, $SelectionHistory, prop ← Create[]];
WithLockedSelection[primary, inner, "InputModify"];
};
push => Carets.StopCaret[primary];
ENDCASE => ERROR;
};
ENDCASE;
};
SelectionOps
CallWithSelAndDocAndTddLocks: PUBLIC PROC [viewer: Viewer, id: SelectionId ← primary,
proc: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection]] = {
IF viewer # NIL THEN WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
tSel: Selection ← NIL;
lockRef: TEditLocks.LockRef ← NIL;
Cleanup: PROC = {
UnlockSel[id];
IF lockRef # NIL THEN TEditLocks.UnlockDocAndTdd[tdd];
IF tSel # NIL THEN Free[tSel]
};
IF tdd # NIL THEN {
ENABLE UNWIND => Cleanup[];
LockSel[id, "CallWithSelAndDocAndTddLocks"];
lockRef ← TEditLocks.LockDocAndTdd[tdd, "CallWithSelAndDocAndTddLocks", read];
IF lockRef#NIL THEN {
sel: Selection = SELECT id FROM
primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR;
tSel ← Alloc[];
Copy[source: sel, dest: tSel];
proc[tdd, tSel]
};
Cleanup[];
};
};
ENDCASE;
};
ShowPosition: PUBLIC PROC[viewer: Viewer, skipCommentNodes: BOOLTRUE] = {
count: INT ← -1;
sel: ROPE ← TEditOps.GetSelContents[];
count ← Convert.IntFromRope[sel ! Convert.Error => CONTINUE];
IF count>=0
THEN ShowGivenPosition[viewer, count, skipCommentNodes]
ELSE {
MessageWindow.Append["Select character count for position.", TRUE];
MessageWindow.Blink[]
};
};
Position: PUBLIC PROC[viewer: Viewer] ~ {
ShowPosition[viewer: viewer, skipCommentNodes: TRUE];
};
ShowGivenPosition: PUBLIC PROC [viewer: Viewer, pos: INT, skipCommentNodes: BOOLTRUE] = {
Exported to TEditSelectionOps (new for 6.0)
DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
tiogaFile: BOOL ← (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes);
IF NOT tiogaFile THEN pos ← pos+1; -- hack to compensate for leading CR
tSel.start.pos ← tSel.end.pos ← TextNode.LocWithin[tdd.text, pos, 1, skipCommentNodes];
tSel.end.pos.where ← MIN[tSel.end.pos.where+3, TextNode.EndPos[tSel.end.pos.node]];
tSel.start.pos.where ← MIN[tSel.end.pos.where, tSel.start.pos.where];
IF tSel.start.pos.where=tSel.end.pos.where AND tSel.start.pos.where > 0 THEN
tSel.start.pos.where ← tSel.start.pos.where-1;
tSel.granularity ← char;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.pendingDelete ← FALSE;
tSel.insertion ← IF TEditProfile.selectionCaret=before THEN before ELSE after;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
MakeSelection[new: tSel, selection: feedback];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, feedback];
sets bit to cause scroll after refresh
};
CallWithSelAndDocAndTddLocks[viewer, feedback, DoPosition];
};
ShowGivenPositionRange: PUBLIC PROC [viewer: Viewer, selectionId: SelectionId, posI, posF: INT, skipCommentNodes, pendingDelete: BOOL] = {
Should join or replace ShowGivenPosition, which is currently in TEditSelectionOps
DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
tiogaFile: BOOL = (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes);
IF posF < posI THEN {p: INT ← posI; posI ← posF; posF ← p};
IF NOT tiogaFile THEN {posI ← posI + 1; posF ← posF+1}; -- hack to compensate for leading CR
tSel.start.pos ← TextNode.LocWithin[tdd.text, posI, 1, skipCommentNodes];
tSel.end.pos ← TextNode.LocWithin[tdd.text, posF, 1, skipCommentNodes];
tSel.granularity ← char;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.pendingDelete ← pendingDelete;
tSel.insertion ← IF TEditProfile.selectionCaret=before THEN before ELSE after;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
MakeSelection[new: tSel, selection: selectionId];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, selectionId];
sets bit to cause scroll after refresh
};
CallWithSelAndDocAndTddLocks[viewer, selectionId, DoPosition];
};
targetOfLastSearch: ROPENIL;
Find: PUBLIC PROC [viewer: Viewer,
findWhere: FindWhere ← anywhere,
def, word: BOOLFALSE,
id: SelectionId ← primary,
case: BOOLTRUE -- case => case of characters is significant --
] ~ {
rope: ROPE ← TEditOps.GetSelContents[];
IF Rope.IsEmpty[rope] THEN rope ← targetOfLastSearch ELSE targetOfLastSearch ← rope;
IF Rope.IsEmpty[rope] THEN {
MessageWindow.Append["Select target for search.", TRUE];
MessageWindow.Blink[];
}
ELSE FindRope[viewer, rope, findWhere, def, word, id, case];
};
FindRope: PUBLIC PROC [viewer: Viewer, rope: ROPE,
findWhere: FindWhere ← anywhere,
def, word: BOOLFALSE,
id: SelectionId ← primary,
case: BOOLTRUE -- case => case of characters is significant --
] = {
IF NOT DoFind[viewer, rope, findWhere, def, word, id, case] THEN {
target: ROPE ← rope;
IF Rope.Size[target]>50 THEN target ← Rope.Concat[Rope.Substr[target, 0, 47], "..."];
MessageWindow.Append[Rope.Concat[target, " not found."], TRUE];
ViewerOps.BlinkDisplay[];
}
ELSE MessageWindow.Clear[];
};
DoFind: PUBLIC PROC [viewer: Viewer, rope: ROPE,
findWhere: FindWhere ← anywhere,
def, word: BOOLFALSE,
id: SelectionId ← primary,
case: BOOLTRUE -- case => case of characters is significant --
] RETURNS [found: BOOL] = {
finder: TreeFind.Finder ← NIL;
where: Node;
first: Node;
start, at, atEnd: INT;
DoFindIt: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
SelectionVisibleInViewer: PROC RETURNS [BOOL] = {
RETURN [tSel.viewer=viewer AND tSel.end.line IN [0..tdd.lineTable.lastLine]]
};
visible: BOOL ~ SelectionVisibleInViewer[];
interrupt: REF BOOL ~ TEditInput.interrupt;
Forward: PROC = {
IF visible THEN { first ← tSel.end.pos.node; start ← tSel.end.pos.where+1 }
ELSE { first ← tdd.lineTable.lines[0].pos.node; start ← tdd.lineTable.lines[0].pos.where };
[found,where,at,atEnd,,] ← TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt];
};
Backward: PROC = {
IF visible THEN { first ← tSel.start.pos.node; start ← tSel.start.pos.where }
ELSE { first ← tdd.lineTable.lines[0].pos.node; start ← tdd.lineTable.lines[0].pos.where };
[found,where,at,atEnd,,] ← TreeFind.TryBackwards[finder: finder, first: first, len: start, interrupt: interrupt]
};
FromStart: PROC = {
first ← TextNode.FirstChild[tdd.text]; start ← 0;
[found,where,at,atEnd,,] ← TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt]
};
interrupt^ ← FALSE;
SELECT findWhere FROM
forwards => Forward[];
backwards => Backward[];
anywhere => { Forward[]; IF NOT found THEN FromStart[] };
ENDCASE => ERROR;
IF NOT found THEN RETURN;
IF def THEN atEnd ← atEnd-1; -- skip the trailing :
tSel.start.pos ← [where,at];
tSel.end.pos ← [where,MAX[0,atEnd-1]];
tSel.granularity ← IF word THEN word ELSE char;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.insertion ← IF TEditProfile.selectionCaret=before THEN before ELSE after;
tSel.insertion ← after;
tSel.pendingDelete ← FALSE;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
MakeSelection[new: tSel, selection: id];
TEditInput.CloseEvent[];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, id];
};
found ← FALSE;
IF Rope.Size[rope] = 0 THEN RETURN;
IF def THEN rope ← Rope.Concat[rope,":"];
finder ← TreeFind.CreateFromRope[pattern: rope,
literal: TRUE, ignoreCase: NOT case, word: word !
TextFind.MalformedPattern => IF ec=toobig THEN GOTO TooBig
since literal=TRUE, no other PatternErrorCode should occur
];
IF finder#NIL THEN CallWithSelAndDocAndTddLocks[viewer, id, DoFindIt];
EXITS
TooBig => {
MessageWindow.Append["Pattern too long", TRUE];
MessageWindow.Blink[];
};
};
END.