<<-- TiogaMouseImpl.mesa Edited by Paxton on May 31, 1983 9:18 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]; newGrain _ char }; 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_endPos)=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 < 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 end0 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]; <> SELECT TiogaProfile.selectionCaret FROM before => RETURN[ <> 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[ <> 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.