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], TextLooks USING [Looks, noLooks], TextNode USING [EndPos, FirstChild, LastWithin, Location, NarrowToTextNode, Offset, Parent, Ref, RefTextNode, Root], TEditDocument USING [BeforeAfter, LineTable, PunctuationPosition,SelectionGrain, SelectionPoint, Selection, SelectionId, SelectionRec, TEditDocumentData], TEditFormat USING [GetLineInfo, LineInfo], TEditInput USING [currentEvent], TEditProfile USING [selectionCaret, wordPunctuation, ySelectFudge], TEditSelection USING [Alloc, Free, Copy, Deselect, FixupSelection, LevelChange, LockSel, pSel, sSel, MakeSelection, SelectEverything, UnlockSel], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], ViewerClasses USING [Viewer]; TEditMouseImpl: CEDAR PROGRAM IMPORTS EditSpan, NodeStyle, Rope, RopeEdit, RopeReader, TEditInput, TEditProfile, TEditFormat, TEditSelection, TEditTouchup, TextEdit, TextNode EXPORTS TEditSelection = BEGIN OPEN TEditDocument, TEditSelection; ------ Mouse hit primitives ------ DoSelect: PROC [ proc: PROC [tSel, refSel: Selection, rightOfLine: BOOL], viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, 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 TEditTouchup.UnlockAfterRefresh[tdd]; IF lockSel THEN UnlockSel[sel] }; { ENABLE UNWIND => Cleanup; LockSel[sel, "DoSelect"]; lockSel _ TRUE; IF TEditTouchup.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: TEditDocumentData, 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: TEditDocumentData, 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[TextNode.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: TEditDocumentData, 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[TextNode.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: TEditDocumentData, 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[TextNode.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: TEditDocumentData, 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 _ TextNode.LastWithin[tSel.start.pos.node]; tSel.end.pos.where _ TextNode.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: TextNode.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: TextNode.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: TextNode.Location; initTDD: TEditDocumentData; Extend: PUBLIC PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, 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: TEditDocumentData = 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 _ TextNode.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[TextNode.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: TextNode.Ref = sp.pos.node; prevNode: TextNode.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: TextNode.Location _ IF sel.insertion = before THEN sel.start.pos ELSE sel.end.pos; node: TextNode.RefTextNode _ TextNode.NarrowToTextNode[loc.node]; size: INT _ TextEdit.Size[node]; sel.looks _ IF node=NIL OR size <= 0 THEN TextLooks.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: TEditDocumentData, 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 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: TextNode.Location, frontOnly: BOOLEAN _ FALSE] RETURNS [start, end: TextEdit.Offset, punc: PunctuationPosition _ none] = BEGIN refChar, char: CHARACTER; alpha: BOOLEAN; node: TextNode.RefTextNode = TextNode.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 TEditProfile.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: TEditDocumentData] RETURNS [BeforeAfter] = BEGIN node: TextNode.RefTextNode _ TextNode.NarrowToTextNode[sel.start.pos.node]; size: TextNode.Offset _ TextEdit.Size[node]; IF sel.start.line=sel.end.line AND sel.start.pos.where>=size THEN RETURN [before]; SELECT TEditProfile.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: TEditDocumentData, x, y: INTEGER] RETURNS [rightOfLine: BOOLEAN] = BEGIN lines: TEditDocument.LineTable; line: INTEGER _ 0; lastLine: INTEGER; linePtr: INTEGER _ 0; lines _ tdd.lineTable; line _ lastLine _ lines.lastLine; y _ y-TEditProfile.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; 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: TextNode.RefTextNode; startPos, endPos, endLen: TextNode.Offset; ropeReader: RopeReader.Ref; IF pSel=NIL OR pSel.viewer=NIL THEN RETURN; tSel _ Alloc[]; Copy[source: pSel, dest: tSel]; start _ TextNode.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: TextNode.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 _ TextNode.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: TextNode.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 _ TextNode.EndPos[tSel.end.pos.node]; tSel.granularity _ IF tSel.start.pos.node=tSel.end.pos.node AND TextNode.FirstChild[tSel.end.pos.node]=NIL THEN branch ELSE node; }; node => { tSel.granularity _ branch; tSel.end.pos.node _ TextNode.LastWithin[tSel.end.pos.node]; tSel.end.pos.where _ TextNode.EndPos[tSel.end.pos.node]; }; branch => { parent: TextNode.Ref _ TextNode.Parent[tSel.start.pos.node]; IF TextNode.Parent[parent]=NIL THEN { -- at the root SelectEverything; RETURN }; tSel.start.pos _ [parent, 0]; tSel.end.pos.node _ TextNode.LastWithin[parent]; tSel.end.pos.where _ TextNode.EndPos[tSel.end.pos.node]; }; ENDCASE => ERROR; MakeSelection[new: tSel]; Free[tSel] }; ---------------- InsertionPoint: PUBLIC PROC [s: Selection _ pSel] RETURNS [ip: TextNode.Location] = { nodeSel: BOOLEAN _ s.granularity = node OR s.granularity = branch; node: TextNode.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 _ TextNode.NarrowToTextNode[s.start.pos.node])=NIL THEN { -- must create an insertion point new: TextNode.RefTextNode _ EditSpan.InsertTextNode[ TextNode.Root[s.start.pos.node], s.start.pos.node,before,TRUE,TEditInput.currentEvent]; ip _ [new, 0] } ELSE ip _ s.start.pos ELSE IF nodeSel AND (node _ TextNode.NarrowToTextNode[s.end.pos.node])=NIL THEN { new: TextNode.RefTextNode _ EditSpan.InsertTextNode[ TextNode.Root[s.end.pos.node], s.end.pos.node,after,TRUE,TEditInput.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 ------ lineInfoViewer: ViewerClasses.Viewer _ NIL; lineInfoLine: INTEGER; ascent, descent: INTEGER; lineChars: INTEGER; nextPos: TextNode.Location; lineInfo: TEditFormat.LineInfo; InvalidateLineCache: PUBLIC PROCEDURE = { lineInfoViewer _ NIL }; GetLine: PROCEDURE [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, line: INTEGER] = BEGIN lines: TEditDocument.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] _ TEditFormat.GetLineInfo[viewer, tdd, lines[line].pos, style]; lineInfoViewer _ viewer; lineInfoLine _ line; NodeStyle.Free[style]; END; END; GetCachedLineInfo: PUBLIC PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, line: INTEGER] RETURNS [TEditFormat.LineInfo, INTEGER] = BEGIN GetLine[viewer, tdd, line]; RETURN[lineInfo, lineChars]; END; END. h-- TEditMouseImpl.mesa Edited by Paxton on May 31, 1983 9:18 am Last Edited by: Plass, April 22, 1983 10:33 am point => SelectPoint[viewer, tdd, x, y, changeSel, sel, changePDel, pDel]; This ensures caret before in empty nodes. caret goes before unless making single character selection to right of last character in node caret goes after unless making single character selection to left of middle of first character in node -- info is returned in selection.start -- selection right of center of last character on line -- must create an insertion point -- a one line cache for the current line being resolved ʬ˜JšÏc+œ™?J™.šÏk ˜ Jšœ žœ/˜=Jšœ žœ˜-Jšœžœ˜Jšœ žœ˜-Jšœ žœb˜rJšœ žœ=˜KJšœ žœ˜!Jšœ žœf˜tJšœžœ‡˜šJšœ žœ˜*Jšœ žœ˜ Jšœ žœ1˜CJšœžœ}˜‘Jšœ žœ(˜:Jšœžœ ˜J˜—Jšœž ˜J˜Jšžœ‰˜Jšžœž˜J˜Jšžœ˜#J˜Jš"˜"J˜šÏnœžœ˜Jšœžœ(žœ˜8Jšœ<žœ˜ZJšœ˜Jšœžœ žœžœžœžœžœžœ˜XJšœžœžœ˜,šŸœžœ˜Jšžœ žœ&˜5Jšžœ žœ˜!—Jšœžœžœ ˜Jšœ$žœ˜)šžœ0žœ˜8Jšœ žœ˜Jšœ˜J˜5Jšœ ˜ J˜ —Jšœ ˜ J˜—šŸ œžœžœ˜J˜5Jšœžœžœž˜6J˜šŸ œžœ(žœ˜DJ˜J˜Jšžœžœžœ˜RJ˜J˜HJ˜šžœžœžœ˜Nšžœžœžœž˜HJ˜J˜J˜J˜J˜J˜Jšœžœžœžœ˜,Jšžœ˜——J˜J˜—Jšœ0˜0Jšžœ˜J˜—šŸ œžœžœ7˜NJšœžœžœž˜6J˜šŸ œžœ(žœ˜CJ˜J˜Jšœžœžœ˜%J˜J˜šžœ žœ žœžœ%˜Jšžœ,žœ˜T˜+J˜>—JšœAžœ˜H——šžœ˜J˜Hšœ˜J˜——šžœžœžœž˜LJšœžœž˜;šœžœž˜(J˜J˜J˜J˜J˜J˜Jšœ/žœ˜6Jšžœ˜——J˜—J˜Jšœ/˜/Jšžœ˜J˜—šŸ œžœžœ7˜NJšœžœžœž˜6J˜šŸ œžœ(žœ˜CJ˜J˜J˜J˜Jšœžœžœ˜%Jšœ žœ˜J˜J˜J˜2J˜J˜J˜J˜*J˜J˜šžœ žœ žœžœ%˜Jšžœ,žœ,˜b˜+J˜>—JšœAžœ˜H——šžœ˜J˜@Jšœ˜J˜—šžœžœ!žœž˜]Jšœžœž˜?šœžœž˜$Jšœ˜J˜J˜J˜J˜Jšœ/žœ˜6Jšžœ˜——J˜—J˜Jšœ/˜/Jšžœ˜J˜—šŸ œžœžœ7˜NJšœžœžœž˜6J˜šŸ œžœ(žœ˜CJšœ žœ˜#J˜J˜J˜J˜J˜*˜$JšžœE˜H—J˜Jšœ.žœ˜:J˜šžœžœ+ž˜GJšœžœž˜7Jšœžœž˜(J˜J˜J˜J˜Jšœ4žœ˜;Jšžœ˜—J˜—J˜Jšœ/˜/Jšžœ˜J˜—šŸ œžœžœ7˜PJšœžœžœž˜6J˜šŸœžœ(žœ˜EJšœ žœ˜#J˜J˜J˜J˜J˜*J˜=Jšœ8˜8J˜Jšœ.žœ˜:J˜šžœžœ+ž˜GJšœžœž˜9Jšœžœž˜(J˜J˜J˜J˜Jšœ4žœ˜;Jšžœ˜—J˜—J˜Jšœ1˜1Jšžœ˜J˜—šŸœžœ0žœ˜PJšžœžœžœ˜'J˜Jš œžœžœžœžœ˜2Jš œžœ žœžœžœ˜0šœžœ ˜-Jšžœ˜J˜—šžœžœžœž˜šœžœ žœžœž˜"J˜J˜Jšžœžœžœžœ˜4—J˜J˜Jšœžœ&˜.Jšœžœ#˜+šœžœ1˜;Jšžœžœ˜—J˜DJ˜Bšžœžœ1˜>Jšžœžœ˜—J˜J˜—Jšžœ˜J˜—šŸœžœ*žœ˜Jšœ˜—J˜Jšžœ˜J˜—Jšžœžœ˜/J˜—J˜J˜J˜Jšœ#žœžœžœ˜G—J˜Jšœ+˜+Jšžœ˜J˜—šŸ œžœžœ˜-Jšœžœžœžœ ˜WJ˜AJšœžœ˜ š œ žœžœžœ žœ˜žœž˜`J˜/J˜šžœžœžœž˜8J˜)J˜/˜1J˜=—J˜J˜J˜Jšžœ˜—Jšžœ˜J˜—šŸœžœžœ>žœ˜dJšžœžœž˜/J˜Jšžœ˜Jšžœ˜J˜—Jšžœ˜J˜J˜—…—Z t4