DIRECTORY
Ascii USING [CR, LF],
Carets USING [StartCaret, StopCaret],
Char USING [XCHAR, Set, Narrow],
Convert,
EditSpan USING [CompareNodeOrder],
Imager USING [Color, Context, MaskRectangleI, SetColor],
ImagerBackdoor USING [MakeStipple],
InputFocus USING [GetInputFocus, SetInputFocus],
MessageWindow USING [Append, Blink, Clear],
Process USING [Detach, GetCurrent],
Rope,
TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, SelectionRec, TEditDocumentData],
TEditInput,
TEditInputOps,
TEditLocks USING [LockDocAndTdd, LockRef, Unlock, UnlockDocAndTdd],
TEditOps,
TEditProfile USING [selectionCaret],
TEditRefresh USING [ScrollToEndOfSel],
TEditSelection USING [FindWhere, ForceDown, InsertionPoint, IsDown, SetSelLooks],
TEditSelectionOps USING [],
TEditSelectionOpsExtras USING [],
TEditSelectionPrivate USING [CharPositionInCachedLine],
TEditSelectionPrivateExtras USING [],
TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh],
TextEdit,
TextEditExtras,
TextNode,
Tioga,
TiogaFind,
ViewerClasses USING [ModifyProc, Viewer],
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, BlinkDisplay, FetchProp, PaintViewer];
Selection Display and Control
MakePointSelection:
PUBLIC
PROC [selection: Selection, pos: 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:
BOOL ¬
FALSE, forkPaint:
BOOL ¬
TRUE] = {
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.processRunning
THEN {
showSel.processRunning ¬ TRUE;
TRUSTED { Process.Detach[FORK ShowSel[showSel, op]] }
}
}
ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op];
};
};
WithLockedSelection[selection, inner, "MakeSelection"];
};
ShowSelRec: TYPE = RECORD [ processRunning: BOOL ¬ FALSE, selection: SelectionId ];
showPSel: REF ShowSelRec ¬ NEW[ShowSelRec ¬ [FALSE, primary]];
showSSel: REF ShowSelRec ¬ NEW[ShowSelRec ¬ [FALSE, secondary]];
showFSel: REF ShowSelRec ¬ NEW[ShowSelRec ¬ [FALSE, 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.processRunning ¬ FALSE;
};
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: INTEGER ¬ MAX[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: Location]
RETURNS [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: 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: Location]
RETURNS [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: 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: Tioga.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: 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: Location, firstLine:
INTEGER ¬ 0, lineOnly:
BOOL ¬
FALSE]
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: BOOL ¬ FALSE;
IsPosPastLine:
PROC [pos: 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: 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;
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:
BOOL ¬
TRUE] = {
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];
IsEOL:
PROC [node: Node, index:
INT]
RETURNS [
BOOL] ~
INLINE {
char: Char.XCHAR ~ TextEdit.FetchChar[node, index];
IF Char.Set[char]=0
THEN
SELECT Char.Narrow[char]
FROM
Ascii.CR, Ascii.LF => RETURN[TRUE];
ENDCASE;
RETURN[FALSE];
};
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 IsEOL[node, selection.end.pos.where] => {
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:
PUBLIC
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]
};
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:
BOOL ¬
TRUE] = {
countI, countF: INT ¬ -1;
sel: ROPE = TEditOps.GetSelContents[];
selLen: INT = Rope.Size[sel];
sepStart: INT = Rope.Index[s1: sel, s2: ".."];
countI ¬ Convert.IntFromRope[sel.Substr[len: sepStart] ! Convert.Error => GOTO Bitch];
IF countI < 0 THEN GOTO Bitch;
IF sepStart < selLen
THEN countF ¬ Convert.IntFromRope[sel.Substr[sepStart+2] ! Convert.Error => GOTO Bitch]
ELSE countF ¬ countI + 3;
IF countF < 0 THEN GOTO Bitch;
ShowGivenPositionRange[viewer, feedback, countI, countF, skipCommentNodes, FALSE];
EXITS
Bitch => {
OPEN MessageWindow;
Append["Select character count or count..count for position.", TRUE];
Blink[];
};
};
Position:
PUBLIC
PROC[viewer: Viewer] ~ {
ShowPosition[viewer: viewer, skipCommentNodes: TRUE];
};
Range:
TYPE =
RECORD [start, end:
INT];
FmtRange:
PROC [r: Range, skipCommentNodes:
BOOL]
RETURNS [rope:
ROPE] = {
IF r.start = r.end
THEN rope ¬ Convert.RopeFromInt[r.start]
ELSE rope ¬ Rope.Cat[
Convert.RopeFromInt[r.start],
"..",
Convert.RopeFromInt[r.end]];
rope ¬ rope.Concat[IF skipCommentNodes THEN " (excluding comment nodes)" ELSE " (including comment nodes)"];
RETURN
};
CurrentPositionMessage:
PUBLIC
PROC [viewer: Viewer, skipCommentNodes:
BOOL]
RETURNS [
ROPE]= {
tSel: Selection = TEditOps.GetSelData[];
pos: Range ¬ [
start: TextNode.LocNumber[at: tSel.start.pos, skipCommentNodes: skipCommentNodes],
end: TextNode.LocNumber[at: tSel.end.pos, skipCommentNodes: skipCommentNodes]];
RETURN [FmtRange[pos, skipCommentNodes]];
};
ShowGivenPosition:
PUBLIC
PROC [viewer: Viewer, pos:
INT, skipCommentNodes:
BOOL ¬
TRUE] = {
Exported to TEditSelectionOps (new for 6.0)
DoPosition:
PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
first: TextNode.Location ~ [TextNode.FirstChild[tdd.text], 0];
pos ¬ MAX[pos, TextNode.LocNumber[first, 1, skipCommentNodes]];
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] = {
pos0: INT ~ TextNode.LocNumber[[TextNode.FirstChild[tdd.text], 0], 1, skipCommentNodes];
IF posF < posI THEN { p: INT ~ posI; posI ¬ posF; posF ¬ p };
tSel.start.pos ¬ TextNode.LocWithin[tdd.text, MAX[pos0, posI], 1, skipCommentNodes];
tSel.end.pos ¬ TextNode.LocWithin[tdd.text, MAX[pos0, 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];
};
GoToEndOfNode:
PUBLIC
PROC [n:
INT ¬ 1] ~ {
DoIt:
PROC [root: Node, tSel: TEditDocument.Selection] = {
Move caret to end of node, or if already there, go forward one node. The argument is the repeat count; Negative arguments go backward.
pointSel: BOOL ¬ tSel.granularity=point;
pos: Location ¬ TEditSelection.InsertionPoint[tSel];
WHILE pos.node #
NIL
AND n # 0
DO
IF n > 0
THEN {
IF pointSel
AND pos.where = Rope.Size[pos.node.rope]
THEN {
node: Node ~ TextNode.StepForward[pos.node];
IF node = NIL OR TextNode.Parent[node]=NIL THEN EXIT;
pos.node ¬ node;
};
pos.where ¬ Rope.Size[pos.node.rope];
pointSel ¬ TRUE;
n ¬ n - 1;
}
ELSE {
IF pointSel
AND pos.where = 0
THEN {
node: Node ~ TextNode.StepBackward[pos.node];
IF node = NIL OR TextNode.Parent[node]=NIL THEN EXIT;
pos.node ¬ node;
};
pos.where ¬ 0;
pointSel ¬ TRUE;
n ¬ n + 1;
};
ENDLOOP;
MakePointSelection[tSel, pos];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
};
IF n # 0 THEN TEditInputOps.CallWithLocks[DoIt, read];
};
TargetFromSel:
PROC [tSel: Selection]
RETURNS [target: Node ¬
NIL] ~ {
loc1, loc2: Location;
[loc1, loc2] ¬ LocationsFromSelection[tSel];
IF loc1=loc2 THEN RETURN[NIL] ELSE target ¬ TextNode.NewTextNode[];
FOR node: Node ← loc1.node, TextNode.StepForward[node]
UNTIL node=
NIL
DO
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE TextEdit.Size[node];
IF node#loc1.node THEN [] ¬ TextEdit.ReplaceByRope[root: NIL,
dest: target, start: TextEdit.Size[target], len: 0,
rope: TextEdit.GetNewlineDelimiter[TextNode.Root[node]]];
IF i1>i0 THEN [] ¬ TextEdit.ReplaceText[destRoot: NIL, sourceRoot: NIL,
dest: target, destStart: TextEdit.Size[target], destLen: 0,
source: node, sourceStart: i0, sourceLen: i1-i0];
IF node=loc2.node THEN EXIT;
ENDLOOP;
};
targetOfLastSearch: Node ¬
NIL;
FindFeedback:
PROC [found:
BOOL, rope:
ROPE] ~ {
IF found THEN MessageWindow.Clear[]
ELSE
IF Rope.Size[rope]=0
THEN {
MessageWindow.Append["Select target for search.", TRUE];
MessageWindow.Blink[];
}
ELSE {
IF Rope.Size[rope]>50 THEN rope ¬ Rope.Concat[Rope.Substr[rope, 0, 47], "..."];
MessageWindow.Append[Rope.Concat[rope, " not found."], TRUE];
ViewerOps.BlinkDisplay[];
};
};
Find:
PUBLIC
PROC [viewer: Viewer,
findWhere: FindWhere ¬ anywhere,
def, word:
BOOL ¬
FALSE,
id: SelectionId ¬ primary,
case:
BOOL ¬
TRUE
-- case => case of characters is significant --
] ~ {
found: BOOL ¬ FALSE;
target: Node ¬ NIL;
LockedFind:
PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
target ¬ TargetFromSel[tSel];
IF target=NIL THEN target ¬ targetOfLastSearch ELSE targetOfLastSearch ¬ target;
IF target#NIL THEN found ¬ FindTarget[viewer: viewer, tdd: tdd,
tSel: tSel, id: id, findWhere: findWhere, target: target,
def: def, word: word, case: case];
};
CallWithSelAndDocAndTddLocks[viewer, id, LockedFind];
FindFeedback[found, TextEditExtras.GetString[target]];
};
FindRope:
PUBLIC
PROC [viewer: Viewer, rope:
ROPE,
findWhere: FindWhere ¬ anywhere,
def, word:
BOOL ¬
FALSE,
id: SelectionId ¬ primary,
case:
BOOL ¬
TRUE
-- case => case of characters is significant --
] = {
found: BOOL ~ DoFind[viewer, rope, findWhere, def, word, id, case];
FindFeedback[found, rope];
};
DoFind:
PUBLIC
PROC [viewer: Viewer, rope:
ROPE,
findWhere: FindWhere ¬ anywhere,
def, word:
BOOL ¬
FALSE,
id: SelectionId ¬ primary,
case:
BOOL ¬
TRUE
-- case => case of characters is significant --
]
RETURNS [found:
BOOL ¬
FALSE] = {
LockedDoFind:
PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = {
target: Node ~ TextEdit.FromRope[rope];
found ¬ FindTarget[viewer: viewer, tdd: tdd,
tSel: tSel, id: id, findWhere: findWhere,
target: target, def: def, word: word, case: case];
};
CallWithSelAndDocAndTddLocks[viewer, id, LockedDoFind];
};
LocationsFromSelection:
PROC [tSel: Selection]
RETURNS [loc1, loc2: Location] ~ {
loc1 ¬ tSel.start.pos; loc2 ¬ tSel.end.pos;
IF loc1.node=NIL OR loc2.node=NIL THEN RETURN[[NIL, 0], [NIL, 0]];
IF loc1.where=Tioga.NodeItself THEN loc1.where ¬ 0;
IF tSel.granularity=point THEN loc2 ¬ loc1
ELSE IF loc2.where=Tioga.NodeItself THEN loc2.where ¬ TextEdit.Size[loc2.node]
ELSE loc2.where ¬ loc2.where+1;
};
FindTarget:
PROC [viewer: Viewer, tdd: TEditDocument.TEditDocumentData,
tSel: Selection, id: SelectionId, findWhere: FindWhere,
target: Node, targetStart:
INT ¬ 0, targetLen:
INT ¬
INT.
LAST,
def, word, case:
BOOL]
RETURNS [found:
BOOL ¬
FALSE] ~ {
match: TiogaFind.Match ~ IF def THEN def ELSE IF word THEN word ELSE any;
visible: BOOL ~ tSel.viewer=viewer AND tSel.end.line IN [0..tdd.lineTable.lastLine];
viewPos: Location ~ tdd.lineTable.lines[0].pos;
interrupt: REF BOOL ~ TEditInput.interrupt;
loc1, loc2: Location;
where: Node; at, atEnd: INT ¬ 0;
[loc1, loc2] ¬ LocationsFromSelection[tSel];
interrupt ¬ FALSE;
SELECT findWhere
FROM
forwards, anywhere => {
pos: Location ~ IF visible THEN loc2 ELSE viewPos;
[where, at, atEnd] ¬ TiogaFind.LiteralSearch[
direction: forward, loc1: pos, loc2: [NIL, 0],
target: target, targetStart: targetStart, targetLen: targetLen,
case: case, match: match, interrupt: interrupt];
};
backwards => {
pos: Location ~ IF visible THEN loc1 ELSE viewPos;
[where, at, atEnd] ¬ TiogaFind.LiteralSearch[
direction: backward, loc1: [NIL, 0], loc2: pos,
target: target, targetStart: targetStart, targetLen: targetLen,
case: case, match: match, interrupt: interrupt];
};
ENDCASE;
IF where=
NIL
AND findWhere=anywhere
AND
NOT interrupt
THEN {
pos: Location ~ [TextNode.FirstChild[tdd.text], 0];
[where, at, atEnd] ¬ TiogaFind.LiteralSearch[
direction: forward, loc1: pos, loc2: [NIL, 0],
target: target, targetStart: targetStart, targetLen: targetLen,
case: case, match: match, interrupt: interrupt];
};
found ¬ (where#NIL);
IF found
THEN {
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];
};
};