-- TiogaMouseImpl.mesa Edited by Paxton on May 31, 1983 9:18 am
Last Edited by: Plass, April 22, 1983 10:33 am
DIRECTORY
EditSpan USING [CompareNodeOrder, InsertTextNode, NodeOrder],
NodeStyle USING [Alloc, ApplyAll, Free, Ref],
Rope USING [Size],
RopeEdit USING [AlphaNumericChar, BlankChar],
RopeReader USING [Backwards, Get, GetIndex, GetRopeReader, FreeRopeReader, Ref, SetCharForEndOfRope, SetPosition],
TextEdit USING [FetchChar, FetchLooks, GetRope, Offset, RefTextNode, Size],
TiogaLooks USING [Looks, noLooks],
TiogaNode USING [Location, Offset, Ref, RefTextNode],
TiogaNodeOps USING [EndPos, FirstChild, LastWithin, NarrowToTextNode, Parent, Root],
TiogaDocument USING [BeforeAfter, LineTable, PunctuationPosition,SelectionGrain, SelectionPoint, Selection, SelectionId, SelectionRec, TiogaDocumentData],
TiogaFormat USING [GetLineInfo, LineInfo],
TiogaInput USING [currentEvent],
TiogaProfile USING [selectionCaret, wordPunctuation, ySelectFudge],
TiogaSelection USING [Alloc, Free, Copy, Deselect, FixupSelection, LevelChange, LockSel, pSel, sSel, MakeSelection, SelectEverything, UnlockSel],
TiogaTouchup USING [LockAfterRefresh, UnlockAfterRefresh],
ViewerClasses USING [Viewer];
TiogaMouseImpl: CEDAR PROGRAM
IMPORTS EditSpan, NodeStyle, Rope, RopeEdit, RopeReader, TiogaInput, TiogaProfile, TiogaFormat, TiogaSelection, TiogaTouchup, TextEdit, TiogaNodeOps
EXPORTS TiogaSelection = BEGIN
OPEN TiogaDocument, TiogaSelection;
------ Mouse hit primitives ------
DoSelect:
PROC [
proc: PROC [tSel, refSel: Selection, rightOfLine: BOOL],
viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData, x, y: INTEGER, sel: SelectionId] = {
tSel: Selection;
refSel: Selection = IF sel=primary THEN pSel ELSE IF sel=secondary THEN sSel ELSE ERROR;
rightOfLine, lockSel, lockTdd: BOOL ← FALSE;
Cleanup:
PROC = {
IF lockTdd THEN TiogaTouchup.UnlockAfterRefresh[tdd];
IF lockSel THEN UnlockSel[sel] };
{ ENABLE UNWIND => Cleanup;
LockSel[sel, "DoSelect"]; lockSel ← TRUE;
IF TiogaTouchup.LockAfterRefresh[tdd, "DoSelect"]
THEN {
lockTdd ← TRUE;
tSel ← Alloc[];
rightOfLine ← ResolveToChar[tSel, viewer, tdd, x, y];
proc[tSel, refSel, rightOfLine];
Free[tSel] };
Cleanup[] }};
SelectPoint:
PUBLIC
PROC [
viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
DoSelectPoint:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
newInsertion: BeforeAfter;
IF refSel.viewer#viewer OR refSel.granularity#point THEN Deselect[selection: sel];
tSel.end ← tSel.start;
newInsertion ← SetInsertion[tSel, x, tSel.start.line, rightOfLine, tdd];
IF refSel.viewer#viewer
OR refSel.start#tSel.start
OR refSel.granularity#point
OR refSel.insertion#newInsertion
OR refSel.pendingDelete#pDel
THEN
BEGIN
tSel.granularity ← point;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.insertion ← newInsertion;
tSel.pendingDelete ← pDel;
SetSelLooks[tSel];
MakeSelection[tSel, sel, TRUE, TRUE, FALSE];
END;
};
DoSelect[DoSelectPoint, viewer, tdd, x, y, sel];
END;
SelectChar:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
DoSelectChar:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
newInsertion: BeforeAfter;
newGrain: SelectionGrain;
startValid, endValid: BOOLEAN ← TRUE;
tSel.end ← tSel.start;
IF rightOfLine
AND tdd.tsInfo#
NIL
AND tSel.end.line=tdd.lineTable.lastLine
AND tdd.lineTable.lines[tSel.end.line].end=eon
THEN {
-- make point selection at end
tSel.end.pos.where ← tSel.start.pos.where ←
TextEdit.Size[TiogaNodeOps.NarrowToTextNode[tSel.start.pos.node]];
newGrain ← point; newInsertion ← before; startValid ← endValid ← FALSE }
ELSE {
newInsertion ← SetInsertion[tSel, x, tSel.start.line, rightOfLine, tdd];
IF refSel.viewer#viewer
OR refSel.start#tSel.start
OR refSel.end#tSel.end
OR
refSel.granularity#newGrain OR refSel.pendingDelete#pDel OR
refSel.insertion#newInsertion
THEN
BEGIN
tSel.granularity ← newGrain;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.insertion ← newInsertion;
tSel.pendingDelete ← pDel;
SetSelLooks[tSel];
MakeSelection[tSel, sel, startValid, endValid, FALSE];
END;
};
DoSelect[DoSelectChar, viewer, tdd, x, y, sel];
END;
SelectWord:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
DoSelectWord:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
start, end: TextEdit.Offset;
punc: PunctuationPosition;
newInsertion: BeforeAfter;
newGrain: SelectionGrain;
startValid, endValid: BOOLEAN ← TRUE;
hitLine: INTEGER;
hitLine ← tSel.start.line;
[start, end, punc] ← ExpandToWord[tSel.start.pos];
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.start.pos.where ← start;
tSel.end.pos ← [tSel.start.pos.node, end];
FixupSelection[tSel, viewer];
IF rightOfLine
AND tdd.tsInfo#
NIL
AND tSel.end.line=tdd.lineTable.lastLine
AND tdd.lineTable.lines[tSel.end.line].end=eon
THEN {
-- make point selection at end of typescript
tSel.end.pos.where ← tSel.start.pos.where ←
TextEdit.Size[TiogaNodeOps.NarrowToTextNode[tSel.start.pos.node]];
newGrain ← point; newInsertion ← before; startValid ← endValid ← FALSE }
ELSE {
newInsertion ← SetInsertion[tSel, x, hitLine, rightOfLine, tdd];
newGrain ← word };
IF refSel.viewer#tSel.viewer
OR tSel.start.pos#refSel.start.pos
OR refSel.granularity#word
OR
tSel.end.pos#refSel.end.pos OR newInsertion#refSel.insertion OR
refSel.pendingDelete#pDel
THEN
BEGIN
tSel.granularity ← newGrain;
tSel.punctuation ← punc;
tSel.insertion ← newInsertion;
tSel.pendingDelete ← pDel;
SetSelLooks[tSel];
MakeSelection[tSel, sel, startValid, endValid, FALSE];
END;
};
DoSelect[DoSelectWord, viewer, tdd, x, y, sel];
END;
SelectNode:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
DoSelectNode:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
hitLine: INTEGER ← tSel.start.line;
newInsertion: BeforeAfter;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.start.pos ← [tSel.start.pos.node, 0];
tSel.end.pos ← [tSel.start.pos.node,
MAX[TextEdit.Size[TiogaNodeOps.NarrowToTextNode[tSel.start.pos.node]],1]-1];
FixupSelection[tSel, viewer];
newInsertion ← SetInsertion[tSel, x, hitLine, FALSE, tdd];
IF refSel.viewer#viewer
OR refSel.start.pos.node#tSel.start.pos.node
OR
refSel.granularity#node OR refSel.pendingDelete#pDel OR
refSel.insertion#newInsertion THEN BEGIN
tSel.granularity ← node;
tSel.pendingDelete ← pDel;
tSel.insertion ← newInsertion;
SetSelLooks[tSel];
MakeSelection[new: tSel, selection: sel, forkPaint: FALSE];
END;
};
DoSelect[DoSelectNode, viewer, tdd, x, y, sel];
END;
SelectBranch:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
DoSelectBranch:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
hitLine: INTEGER ← tSel.start.line;
newInsertion: BeforeAfter;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.start.pos ← [tSel.start.pos.node, 0];
tSel.end.pos.node ← TiogaNodeOps.LastWithin[tSel.start.pos.node];
tSel.end.pos.where ← TiogaNodeOps.EndPos[tSel.end.pos.node];
FixupSelection[tSel, viewer];
newInsertion ← SetInsertion[tSel, x, hitLine, FALSE, tdd];
IF refSel.viewer#viewer
OR refSel.start.pos.node#tSel.start.pos.node
OR
refSel.granularity#branch OR refSel.pendingDelete#pDel OR
refSel.insertion#newInsertion THEN BEGIN
tSel.granularity ← branch;
tSel.pendingDelete ← pDel;
tSel.insertion ← newInsertion;
SetSelLooks[tSel];
MakeSelection[new: tSel, selection: sel, forkPaint: FALSE];
END;
};
DoSelect[DoSelectBranch, viewer, tdd, x, y, sel];
END;
ComputeBeforeAfter:
PROC [sel: Selection, new: TiogaNode.Location, x, y:
INTEGER]
RETURNS [BeforeAfter] = BEGIN OPEN sel;
sOS: BOOLEAN = (start.line IN [0..LAST[INTEGER]));
eOS: BOOLEAN = (end.line IN [0..LAST[INTEGER]));
easy:
BOOLEAN = (start.pos.node=end.pos.node)
AND (start.pos.node=new.node);
RETURN[
SELECT
TRUE
FROM
~ (sOS
OR eOS) =>
SELECT
TRUE
FROM
end.line<0 => after,
start.line>0 => before,
ENDCASE => IF y>viewer.ch/2 THEN after ELSE before,
~sOS => after,
~eOS => before,
easy AND new.where<=start.pos.where => before,
easy AND new.where>=end.pos.where => after,
easy =>
IF Dist[sel, before, x, y] < Dist[sel, after, x, y]
THEN before ELSE after,
EditSpan.CompareNodeOrder[new.node, start.pos.node]#after => before,
EditSpan.CompareNodeOrder[new.node, end.pos.node]#before => after,
ENDCASE =>
IF Dist[sel, before, x, y] < Dist[sel, after, x, y]
THEN before ELSE after
];
END;
Dist:
PROC [sel: Selection, dir: BeforeAfter, x, y:
INTEGER]
RETURNS [LONG INTEGER] = --INLINE-- BEGIN
SQR: PROC [n: LONG INTEGER] RETURNS [LONG INTEGER] = INLINE {RETURN[n*n]};
RETURN[
IF dir=before
THEN
SQR[x-sel.start.x]+SQR[y-sel.start.y]
ELSE SQR[x-sel.end.x]+SQR[y-sel.end.y]];
END;
CompareLoc:
PROC [loc1, loc2: TiogaNode.Location]
RETURNS [order: EditSpan.NodeOrder] = {
IF loc1.node=loc2.node
THEN
RETURN [
SELECT loc1.where
FROM
< loc2.where => before,
= loc2.where => same,
ENDCASE => after];
RETURN [EditSpan.CompareNodeOrder[loc1.node,loc2.node]] };
initStart, initEnd: TiogaNode.Location;
initTDD: TiogaDocumentData;
Extend:
PUBLIC
PROC [viewer: ViewerClasses.Viewer,
tdd: TiogaDocumentData, x, y: INTEGER,
sel: SelectionId,
pDel: BOOLEAN,
changeLevel: LevelChange,
saveEnds: BOOLEAN] = BEGIN
DoExtend:
PROC [tSel, refSel: Selection, rightOfLine:
BOOL] = {
end: BeforeAfter;
ok: BOOLEAN;
sp: SelectionPoint; -- the place we're extending to
IF refSel.viewer=NIL THEN RETURN; -- no selection to extend
ok ← refSel.pendingDelete=pDel;
end ← refSel.insertion;
IF refSel.viewer#viewer
THEN {
refTDD: TiogaDocumentData = NARROW[refSel.viewer.data];
IF refTDD = NIL OR refTDD.text#tdd.text THEN RETURN; -- can't extend into another document
ok ← FALSE -- extending into a different viewer, so must recalculate both ends
};
IF refSel.granularity=point THEN ok ← FALSE;
IF saveEnds
OR tdd#initTDD
THEN {
initTDD ← tdd;
initStart ← refSel.start.pos;
initEnd ← refSel.end.pos };
sp ← tSel.start;
IF end=after
THEN {
-- changing the end of the selection
IF CompareLoc[tSel.start.pos,refSel.start.pos]=before
THEN {
-- switch ends
end ← before;
SELECT CompareLoc[refSel.start.pos,initEnd]
FROM
same, before => refSel.end.pos ← initEnd;
ENDCASE => initEnd ← refSel.end.pos; -- didn't get saved
ok ← FALSE }}
ELSE {
-- changing the start of the selection
IF CompareLoc[tSel.start.pos,refSel.end.pos]=after
THEN {
-- switch ends
end ← after;
SELECT CompareLoc[initStart,refSel.end.pos]
FROM
same, before => refSel.start.pos ← initStart;
ENDCASE => initStart ← refSel.start.pos; -- didn't get saved
ok ← FALSE }};
IF ok
AND changeLevel=same
AND
((end=before
AND sp.pos=refSel.start.pos)
OR (end=after
AND sp.pos=refSel.end.pos))
THEN RETURN; -- no change
Copy[source: refSel, dest: tSel];
tSel.viewer ← viewer; -- in case we're extending into a different one
IF tSel.granularity=point
AND end=before
AND tSel.end.pos.where > 0
AND
(sp.pos.node # tSel.end.pos.node OR sp.pos.where < tSel.end.pos.where) THEN {
tSel.end.pos.where ← tSel.end.pos.where-1; -- so don't include char after caret
ok ← FALSE };
SELECT changeLevel
FROM
same => NULL;
reduce => {
ok ← FALSE;
tSel.granularity ←
SELECT tSel.granularity
FROM
branch => node,
node => word,
word => char,
ENDCASE => char };
expand => {
ok ← FALSE;
tSel.granularity ←
SELECT tSel.granularity
FROM
point => char,
char => word,
word => node,
ENDCASE => branch };
ENDCASE => ERROR;
SELECT tSel.granularity FROM
branch, node =>
BEGIN
IF end=after
THEN
BEGIN
IF tSel.granularity=branch
THEN
sp.pos.node ← TiogaNodeOps.LastWithin[sp.pos.node];
IF ok AND sp.pos.node=tSel.end.pos.node THEN RETURN;
tSel.end.pos ← [sp.pos.node,
MAX[TextEdit.Size[TiogaNodeOps.NarrowToTextNode[sp.pos.node]],1]-1];
END
ELSE
BEGIN
IF ok AND sp.pos.node=tSel.start.pos.node THEN RETURN;
tSel.start.pos ← [sp.pos.node, 0];
END;
END;
word =>
BEGIN
prev, start, endPos: TextEdit.Offset;
node: TiogaNode.Ref = sp.pos.node;
prevNode: TiogaNode.Ref;
punc: PunctuationPosition;
[start, endPos, punc] ← ExpandToWord[sp.pos, end=before];
IF end=after
THEN
BEGIN
prevNode ← tSel.end.pos.node;
tSel.end.pos.node ← node;
IF tSel.punctuation # leading
THEN tSel.punctuation ← punc
ELSE IF punc = trailing THEN endPos ← endPos-1;
prev ← tSel.end.pos.where;
IF (tSel.end.pos.where𡤎ndPos)=prev AND ok AND node=prevNode THEN RETURN;
END
ELSE
BEGIN
prevNode ← tSel.start.pos.node;
tSel.start.pos.node ← node;
IF tSel.punctuation # trailing
THEN tSel.punctuation ← punc
ELSE IF punc = leading THEN start ← start+1;
prev ← tSel.start.pos.where;
IF (tSel.start.pos.where←start)=prev AND ok AND node=prevNode THEN RETURN;
END;
END;
char, point =>
BEGIN
IF end=after
THEN {
IF tSel.end=sp AND ok AND tSel.granularity=char THEN RETURN;
tSel.end ← sp }
ELSE {
IF tSel.start=sp AND ok AND tSel.granularity=char THEN RETURN;
tSel.start ← sp };
tSel.granularity ← char;
END;
ENDCASE => ERROR; -- no other selection flavors
tSel.insertion ← end;
tSel.pendingDelete ← pDel;
SetSelLooks[tSel];
MakeSelection[tSel, sel, end=after AND ok, end=before AND ok, FALSE] };
DoSelect[DoExtend, viewer, tdd, x, y, sel];
END;
SetSelLooks:
PUBLIC
PROC [sel: Selection] = {
loc: TiogaNode.Location ← IF sel.insertion = before THEN sel.start.pos ELSE sel.end.pos;
node: TiogaNode.RefTextNode ← TiogaNodeOps.NarrowToTextNode[loc.node];
size: INT ← TextEdit.Size[node];
sel.looks ←
IF node=
NIL
OR size <= 0
THEN TiogaLooks.noLooks
ELSE IF loc.where >= size THEN TextEdit.FetchLooks[node,size-1]
ELSE TextEdit.FetchLooks[node,loc.where] };
Update:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData,
x,y: INTEGER, sel: SelectionId, pDel: BOOLEAN] = BEGIN
refSel: Selection = IF sel=primary THEN pSel ELSE IF sel=secondary THEN sSel ELSE ERROR;
SELECT refSel.granularity
FROM
point => SelectPoint[viewer, tdd, x, y, changeSel, sel, changePDel, pDel];
word => SelectWord[viewer, tdd, x, y, sel, pDel];
node => SelectNode[viewer, tdd, x, y, sel, pDel];
branch => SelectBranch[viewer, tdd, x, y, sel, pDel];
ENDCASE => SelectChar[viewer, tdd, x, y, sel, pDel];
END;
ExpandToWord:
PROCEDURE [pos: TiogaNode.Location, frontOnly:
BOOLEAN ←
FALSE]
RETURNS [start, end: TextEdit.Offset, punc: PunctuationPosition ← none] =
BEGIN
refChar, char: CHARACTER;
alpha: BOOLEAN;
node: TiogaNode.RefTextNode = TiogaNodeOps.NarrowToTextNode[pos.node];
lastOffset: TextEdit.Offset ← TextEdit.Size[node]-1;
ropeReader: RopeReader.Ref ← RopeReader.GetRopeReader[];
start ← end ← pos.where;
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[node], end];
RopeReader.SetCharForEndOfRope[ropeReader, 15C]; -- so we get a return at the end
refChar ← RopeReader.Get[ropeReader];
IF refChar=15C THEN { RopeReader.FreeRopeReader[ropeReader]; RETURN }; -- CR is a word
alpha ← RopeEdit.AlphaNumericChar[refChar];
char ← RopeReader.Get[ropeReader];
WHILE ((alpha
AND RopeEdit.AlphaNumericChar[char])
OR char=refChar)
AND end<lastOffset
DO
char ← RopeReader.Get[ropeReader];
end ← end+1;
ENDLOOP;
IF TiogaProfile.wordPunctuation
AND ~frontOnly
AND alpha
AND char=40C
THEN
BEGIN
punc ← trailing;
end ← end+1;
END;
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[node], start];
char ← RopeReader.Backwards[ropeReader];
WHILE ((alpha
AND RopeEdit.AlphaNumericChar[char])
OR char=refChar)
AND start>0
DO
char ← RopeReader.Backwards[ropeReader];
start ← start-1;
ENDLOOP;
IF TiogaProfile.wordPunctuation
AND punc=none
AND alpha
AND char=40C
THEN
BEGIN
punc ← leading; start ← start-1;
END;
RopeReader.FreeRopeReader[ropeReader];
END;
SetInsertion:
PROC
[sel: Selection, x, line: INTEGER, rightOfLine: BOOLEAN, tdd: TiogaDocumentData]
RETURNS [BeforeAfter] = BEGIN
node: TiogaNode.RefTextNode ← TiogaNodeOps.NarrowToTextNode[sel.start.pos.node];
size: TiogaNode.Offset ← TextEdit.Size[node];
IF sel.start.line=sel.end.line
AND sel.start.pos.where>=size
THEN
RETURN [before];
This ensures caret before in empty nodes.
SELECT TiogaProfile.selectionCaret
FROM
before =>
RETURN[
caret goes before unless making single character selection to right of last character in node
IF sel.start.pos=sel.end.pos AND sel.granularity=char AND rightOfLine AND tdd.lineTable.lines[line].end=eon THEN after ELSE before];
after =>
RETURN[
caret goes after unless making single character selection to left of middle of first character in node
IF sel.start.pos.where=0 AND sel.start.pos=sel.end.pos AND sel.granularity=char AND x-sel.start.x <= sel.end.x+sel.end.w-x THEN before ELSE after];
ENDCASE;
IF sel.start.line=line
THEN
BEGIN
IF sel.end.line#line THEN RETURN [before];
IF sel.start.pos.where>=size THEN RETURN [before];
IF rightOfLine
THEN
RETURN [
IF sel.start.pos=sel.end.pos
-- single char selection
AND sel.start.pos.where+1 < size -- not last char in node
AND RopeEdit.BlankChar[TextEdit.FetchChar[node,sel.start.pos.where]]
THEN before ELSE after];
RETURN[IF x-sel.start.x <= sel.end.x+sel.end.w-x THEN before ELSE after];
END
ELSE IF sel.end.line=line THEN RETURN[after];
RETURN[IF line-sel.start.line <= sel.end.line-line THEN before ELSE after];
END;
------ Screen to viewbox transforms ------
ResolveToChar:
PROC [selection: Selection, viewer: ViewerClasses.Viewer,
tdd: TiogaDocumentData, x, y: INTEGER]
RETURNS [rightOfLine: BOOLEAN] = BEGIN
lines: TiogaDocument.LineTable;
line: INTEGER ← 0;
lastLine: INTEGER;
linePtr: INTEGER ← 0;
-- info is returned in selection.start
lines ← tdd.lineTable;
line ← lastLine ← lines.lastLine;
y ← y-TiogaProfile.ySelectFudge; -- for people who like to point below the target
IF y > (lines[lastLine].yOffset+lines[lastLine].descent)
THEN
BEGIN
-- off end of text
x ← LAST[INTEGER];
END
ELSE
FOR n:
INTEGER
IN (0..lastLine]
DO
-- horrible linear search!
IF lines[n].yOffset < y THEN LOOP;
line ←
IF lines[n-1].yOffset+lines[n-1].descent >= y
OR
lines[n].yOffset-lines[n].ascent > y THEN n-1 ELSE n;
EXIT;
ENDLOOP;
selection.start.y ← lines[line].yOffset-lines[line].ascent;
selection.start.pos ← lines[line].pos;
selection.start.h ← lines[line].ascent+lines[line].descent;
selection.start.line ← line;
GetLine[viewer, tdd, line];
IF lineInfo[0]>=
LAST[
INTEGER]
THEN
BEGIN
--nothing on that line
selection.start.x ← lines[line].xOffset;
selection.start.w ← 0;
rightOfLine ← TRUE;
END
ELSE
BEGIN
cx, cw, width: INTEGER;
cp: TextEdit.Offset ← selection.start.pos.where;
cx ← lines[line].xOffset;
cw ← width ← lineInfo[0];
rightOfLine ← FALSE;
UNTIL cx+cw >= x
DO
IF (width ← lineInfo[linePtr ← linePtr + 1]) >=
LAST[
INTEGER]
THEN
{rightOfLine ← TRUE; EXIT};
cp ← cp + 1;
cx ← cx + cw;
cw ← width;
ENDLOOP;
selection.start.x ← cx;
selection.start.w ← cw;
selection.start.pos.where ← cp;
IF ~rightOfLine
AND lineInfo[linePtr+1] >=
LAST[
INTEGER]
AND
cx+cw/2 <= x
THEN rightOfLine ←
TRUE;
-- selection right of center of last character on line
END;
END;
----------------
GrowSelectionToBlanks:
PUBLIC
PROC = {
Blank:
PROC [char:
CHAR]
RETURNS [
BOOLEAN] = {
RETURN [RopeEdit.BlankChar[char]] };
GrowSelectionToSomething[Blank, Blank] };
GrowSelectionToSomething:
PUBLIC
PROC [left, right:
PROC [
CHAR]
RETURNS [
BOOLEAN]] = {
tSel: Selection;
start, end: TiogaNode.RefTextNode;
startPos, endPos, endLen: TiogaNode.Offset;
ropeReader: RopeReader.Ref;
IF pSel=NIL OR pSel.viewer=NIL THEN RETURN;
tSel ← Alloc[];
Copy[source: pSel, dest: tSel];
start ← TiogaNodeOps.NarrowToTextNode[tSel.start.pos.node];
ropeReader ← RopeReader.GetRopeReader[];
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[start], tSel.start.pos.where];
DO
-- find first blank to left of start of selection
loc: TiogaNode.Offset = RopeReader.GetIndex[ropeReader];
IF loc <= 0 THEN { startPos ← 0; EXIT };
IF left[RopeReader.Backwards[ropeReader]] THEN { startPos ← loc; EXIT };
ENDLOOP;
tSel.start.pos.where ← startPos;
end ← TiogaNodeOps.NarrowToTextNode[tSel.end.pos.node];
endPos ← IF tSel.granularity=point THEN tSel.end.pos.where ELSE tSel.end.pos.where+1;
endLen ← Rope.Size[TextEdit.GetRope[end]];
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[end], endPos];
DO
-- find first blank to right of end of selection
loc: TiogaNode.Offset = RopeReader.GetIndex[ropeReader];
IF loc >= endLen THEN { endPos ← endLen; EXIT };
IF right[RopeReader.Get[ropeReader]] THEN { endPos ← loc; EXIT };
ENDLOOP;
RopeReader.FreeRopeReader[ropeReader];
tSel.end.pos.where ← endPos-1;
MakeSelection[new: tSel];
Free[tSel] };
GrowSelection:
PUBLIC
PROC = {
tSel: Selection;
IF pSel=NIL OR pSel.viewer=NIL THEN RETURN;
tSel ← Alloc[];
Copy[source: pSel, dest: tSel];
SELECT tSel.granularity FROM
point => {
tSel.granularity ← char;
};
char => {
start, end: TextEdit.Offset;
tSel.granularity ← word;
[start, end, ----] ← ExpandToWord[tSel.start.pos];
tSel.start.pos.where ← start;
tSel.end.pos ← [tSel.start.pos.node, end];
};
word => {
tSel.start.pos.where ← 0;
tSel.end.pos.where ← TiogaNodeOps.EndPos[tSel.end.pos.node];
tSel.granularity ←
IF tSel.start.pos.node=tSel.end.pos.node
AND
TiogaNodeOps.FirstChild[tSel.end.pos.node]=NIL THEN branch ELSE node;
};
node => {
tSel.granularity ← branch;
tSel.end.pos.node ← TiogaNodeOps.LastWithin[tSel.end.pos.node];
tSel.end.pos.where ← TiogaNodeOps.EndPos[tSel.end.pos.node];
};
branch => {
parent: TiogaNode.Ref ← TiogaNodeOps.Parent[tSel.start.pos.node];
IF TiogaNodeOps.Parent[parent]=
NIL
THEN {
-- at the root
SelectEverything; RETURN };
tSel.start.pos ← [parent, 0];
tSel.end.pos.node ← TiogaNodeOps.LastWithin[parent];
tSel.end.pos.where ← TiogaNodeOps.EndPos[tSel.end.pos.node];
};
ENDCASE => ERROR;
MakeSelection[new: tSel];
Free[tSel] };
----------------
InsertionPoint:
PUBLIC
PROC [s: Selection ← pSel]
RETURNS [ip: TiogaNode.Location] = {
nodeSel: BOOLEAN ← s.granularity = node OR s.granularity = branch;
node: TiogaNode.RefTextNode;
dataRefAny: REF = s.data;
viewer: ViewerClasses.Viewer = s.viewer;
IF viewer = NIL OR dataRefAny # viewer.data THEN RETURN [[NIL, 0]]; -- in case of vanishing viewers.
IF s.insertion=before
THEN
IF nodeSel
AND (node ← TiogaNodeOps.NarrowToTextNode[s.start.pos.node])=
NIL
THEN {
-- must create an insertion point
new: TiogaNode.RefTextNode ← EditSpan.InsertTextNode[
TiogaNodeOps.Root[s.start.pos.node],
s.start.pos.node,before,TRUE,TiogaInput.currentEvent];
ip ← [new, 0] }
ELSE ip ← s.start.pos
ELSE
IF nodeSel
AND (node ← TiogaNodeOps.NarrowToTextNode[s.end.pos.node])=
NIL
THEN {
-- must create an insertion point
new: TiogaNode.RefTextNode ← EditSpan.InsertTextNode[
TiogaNodeOps.Root[s.end.pos.node],
s.end.pos.node,after,TRUE,TiogaInput.currentEvent];
ip ← [new, 0] }
ELSE ip ← [s.end.pos.node, s.end.pos.where+1] };
GetSelectionGrain:
PUBLIC
PROC [sel: Selection]
RETURNS [SelectionGrain] =
BEGIN
IF sel.granularity = node OR sel.granularity = branch THEN RETURN [node];
IF sel.granularity = point AND sel.start.pos = sel.end.pos THEN RETURN [point];
RETURN [IF sel.granularity=word THEN word ELSE char];
END;
------ Misc functions ------
-- a one line cache for the current line being resolved
lineInfoViewer: ViewerClasses.Viewer ← NIL;
lineInfoLine: INTEGER;
ascent, descent: INTEGER;
lineChars: INTEGER;
nextPos: TiogaNode.Location;
lineInfo: TiogaFormat.LineInfo;
InvalidateLineCache: PUBLIC PROCEDURE = { lineInfoViewer ← NIL };
GetLine:
PROCEDURE [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData, line:
INTEGER] =
BEGIN
lines: TiogaDocument.LineTable = tdd.lineTable;
IF viewer#lineInfoViewer
OR line#lineInfoLine
THEN
BEGIN
style: NodeStyle.Ref ← NodeStyle.Alloc[];
NodeStyle.ApplyAll[style,lines[line].pos.node];
[lineInfo, ascent, descent, nextPos, lineChars] ←
TiogaFormat.GetLineInfo[viewer, tdd, lines[line].pos, style];
lineInfoViewer ← viewer;
lineInfoLine ← line;
NodeStyle.Free[style];
END;
END;
GetCachedLineInfo:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData, line:
INTEGER]
RETURNS [TiogaFormat.LineInfo, INTEGER] = BEGIN
GetLine[viewer, tdd, line];
RETURN[lineInfo, lineChars];
END;
END.