<> <> <> <> <> <> <<>> DIRECTORY EditSpan USING [CompareNodeOrder, InsertTextNode, NodeOrder, Place], NodeStyle USING [Ref], NodeStyleOps USING [Alloc, ApplyAll, Free, OfStyle], Rope USING [Size], RopeEdit USING [AlphaNumericChar, BlankChar], RopeReader USING [Backwards, Get, GetIndex, GetRopeReader, FreeRopeReader, Ref, SetCharForEndOfRope, SetPosition], Scaled USING [FromInt], TextEdit USING [FetchChar, FetchLooks, GetRope, Offset, RefTextNode, Size], TextLooks USING [Looks, noLooks], TextNode USING [EndPos, FirstChild, LastWithin, Location, Offset, Parent, Ref, RefTextNode, Root], TEditDocument USING [BeforeAfter, LineTable, PunctuationPosition, SelectionGrain, SelectionPoint, Selection, SelectionId, SelectionRec, TEditDocumentData], TEditFormat USING [Allocate, CharPosition, FormatLine, LineInfo, Resolve], TEditInput USING [currentEvent], TEditProfile USING [selectionCaret, wordPunctuation, ySelectFudge], TEditSelection USING [Alloc, Free, Copy, Deselect, FixupSelection, fSel, LevelChange, LockSel, pSel, sSel, MakeSelection, SelectEverything, UnlockSel], TEditSelectionPrivate USING [], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], ViewerClasses USING [Viewer], ViewerGroupLocks USING [CallRootUnderWriteLock], ViewerOps USING [FetchProp]; TEditMouseImpl: CEDAR MONITOR IMPORTS EditSpan, NodeStyleOps, Rope, RopeEdit, RopeReader, Scaled, TEditInput, TEditProfile, TEditFormat, TEditSelection, TEditTouchup, TextEdit, TextNode, ViewerGroupLocks, ViewerOps EXPORTS TEditSelection, TEditSelectionPrivate = BEGIN <<---- Mouse hit primitives ------>> DoSelect: PROC [ proc: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL], viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x, y: INTEGER, sel: TEditDocument.SelectionId] = { action: PROC = { refSel: TEditDocument.Selection = SELECT sel FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; tSel: TEditDocument.Selection; rightOfLine: BOOL _ FALSE; tSel _ TEditSelection.Alloc[]; rightOfLine _ ResolveToChar[tSel, viewer, tdd, x, y]; proc[tSel, refSel, rightOfLine]; TEditSelection.Free[tSel] }; WithProperLocks[action, sel, viewer, tdd]; }; WithProperLocks: PROC [action: PROC, sel: TEditDocument.SelectionId, viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData] = { inner: PROC = { WITH viewer.data SELECT FROM vtdd: TEditDocument.TEditDocumentData => IF vtdd = tdd THEN { IF TEditTouchup.LockAfterRefresh[tdd, "DoSelect"] THEN { action[ ! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]]; TEditTouchup.UnlockAfterRefresh[tdd]; }; }; ENDCASE; }; TEditSelection.LockSel[sel, "DoSelect"]; ViewerGroupLocks.CallRootUnderWriteLock[inner, viewer ! UNWIND => TEditSelection.UnlockSel[sel]]; TEditSelection.UnlockSel[sel] ; }; SelectPoint: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x,y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { DoSelectPoint: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { newInsertion: TEditDocument.BeforeAfter; IF refSel.viewer#viewer OR refSel.granularity#point THEN TEditSelection.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 { tSel.granularity _ point; tSel.viewer _ viewer; tSel.data _ tdd; tSel.insertion _ newInsertion; tSel.pendingDelete _ pDel; SetSelLooks[tSel]; TEditSelection.MakeSelection[tSel, sel, TRUE, TRUE, FALSE]; }; }; DoSelect[DoSelectPoint, viewer, tdd, x, y, sel]; }; SelectChar: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x, y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { DoSelectChar: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { newInsertion: TEditDocument.BeforeAfter; newGrain: TEditDocument.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[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 { tSel.granularity _ newGrain; tSel.viewer _ viewer; tSel.data _ tdd; tSel.insertion _ newInsertion; tSel.pendingDelete _ pDel; SetSelLooks[tSel]; TEditSelection.MakeSelection[tSel, sel, startValid, endValid, FALSE]; }; }; DoSelect[DoSelectChar, viewer, tdd, x, y, sel]; }; SelectWord: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x, y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { DoSelectWord: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { start, end: TextEdit.Offset; punc: TEditDocument.PunctuationPosition; newInsertion: TEditDocument.BeforeAfter; newGrain: TEditDocument.SelectionGrain; startValid, endValid: BOOLEAN _ TRUE; hitLine: INTEGER _ 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]; TEditSelection.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 { <> tSel.end.pos.where _ tSel.start.pos.where _ TextEdit.Size[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 refSel.granularity#newGrain OR tSel.start.pos#refSel.start.pos OR tSel.end.pos#refSel.end.pos OR newInsertion#refSel.insertion OR refSel.pendingDelete#pDel THEN { tSel.granularity _ newGrain; tSel.punctuation _ punc; tSel.insertion _ newInsertion; tSel.pendingDelete _ pDel; SetSelLooks[tSel]; TEditSelection.MakeSelection[tSel, sel, startValid, endValid, FALSE]; }; }; DoSelect[DoSelectWord, viewer, tdd, x, y, sel]; }; SelectNode: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x,y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { DoSelectNode: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { hitLine: INTEGER _ tSel.start.line; newInsertion: TEditDocument.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[tSel.start.pos.node],1]-1]; TEditSelection.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 { tSel.granularity _ node; tSel.pendingDelete _ pDel; tSel.insertion _ newInsertion; SetSelLooks[tSel]; TEditSelection.MakeSelection[new: tSel, selection: sel, forkPaint: FALSE]; }; }; DoSelect[DoSelectNode, viewer, tdd, x, y, sel]; }; SelectBranch: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x,y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { DoSelectBranch: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { hitLine: INTEGER _ tSel.start.line; newInsertion: TEditDocument.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]; TEditSelection.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 { tSel.granularity _ branch; tSel.pendingDelete _ pDel; tSel.insertion _ newInsertion; SetSelLooks[tSel]; TEditSelection.MakeSelection[new: tSel, selection: sel, forkPaint: FALSE]; }; }; DoSelect[DoSelectBranch, viewer, tdd, x, y, sel]; }; ComputeBeforeAfter: PROC [sel: TEditDocument.Selection, new: TextNode.Location, x, y: INTEGER] RETURNS [TEditDocument.BeforeAfter] = { 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 NOT (sOS OR eOS) => SELECT TRUE FROM end.line<0 => after, start.line>0 => before, ENDCASE => IF y>viewer.ch/2 THEN after ELSE before, NOT sOS => after, NOT 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 ]; }; Dist: PROC [sel: TEditDocument.Selection, dir: TEditDocument.BeforeAfter, x, y: INTEGER] RETURNS [LONG INTEGER] = { 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]]; }; 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: TEditDocument.TEditDocumentData; Extend: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x, y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN, changeLevel: TEditSelection.LevelChange, saveEnds: BOOLEAN] = { DoExtend: PROC [tSel, refSel: TEditDocument.Selection, rightOfLine: BOOL] = { end: TEditDocument.BeforeAfter; ok: BOOLEAN; sp: TEditDocument.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: TEditDocument.TEditDocumentData = NARROW[refSel.viewer.data]; IF refTDD = NIL OR refTDD.text#tdd.text THEN RETURN; <> 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 { <> IF CompareLoc[tSel.start.pos,refSel.start.pos]=before THEN { <> 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 { <> IF CompareLoc[tSel.start.pos,refSel.end.pos]=after THEN { <> 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 TEditSelection.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 => { IF end=after THEN { 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[sp.pos.node],1]-1]; } ELSE { IF ok AND sp.pos.node=tSel.start.pos.node THEN RETURN; tSel.start.pos _ [sp.pos.node, 0]; }; }; word => { prev, start, endPos: TextEdit.Offset; node: TextNode.Ref = sp.pos.node; prevNode: TextNode.Ref; punc: TEditDocument.PunctuationPosition; [start, endPos, punc] _ ExpandToWord[sp.pos, end=before]; IF end=after THEN { 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; } ELSE { 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; }; }; char, point => { 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; }; ENDCASE => ERROR; -- no other selection flavors tSel.insertion _ end; tSel.pendingDelete _ pDel; SetSelLooks[tSel]; TEditSelection.MakeSelection[tSel, sel, end=after AND ok, end=before AND ok, FALSE] }; DoSelect[DoExtend, viewer, tdd, x, y, sel]; }; SetSelLooks: PUBLIC PROC [sel: TEditDocument.Selection] = { loc: TextNode.Location ~ IF sel.insertion = before THEN sel.start.pos ELSE sel.end.pos; size: INT _ TextEdit.Size[loc.node]; sel.looks _ ( IF size <= 0 THEN TextLooks.noLooks ELSE IF loc.where >= size THEN TextEdit.FetchLooks[loc.node, size-1] ELSE TextEdit.FetchLooks[loc.node, loc.where] ) }; Update: PUBLIC PROC [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x,y: INTEGER, sel: TEditDocument.SelectionId, pDel: BOOLEAN] = { refSel: TEditDocument.Selection = IF sel=primary THEN TEditSelection.pSel ELSE IF sel=secondary THEN TEditSelection.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]; }; ExpandToWord: PROC [pos: TextNode.Location, frontOnly: BOOLEAN _ FALSE] RETURNS [start, end: TextEdit.Offset, punc: TEditDocument.PunctuationPosition _ none] = { refChar, char: CHARACTER; alpha: BOOLEAN; node: TextNode.RefTextNode = 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 { punc _ leading; start _ start-1; }; RopeReader.FreeRopeReader[ropeReader]; }; SetInsertion: PROC [sel: TEditDocument.Selection, x, line: INTEGER, rightOfLine: BOOLEAN, tdd: TEditDocument.TEditDocumentData] RETURNS [TEditDocument.BeforeAfter] = { node: TextNode.RefTextNode _ 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 { IF sel.end.line#line THEN RETURN [before]; IF sel.start.pos.where>=size THEN RETURN [before]; IF rightOfLine THEN { BlankAt: PROC [offset: INT] RETURNS [BOOL] ~ { set: NAT; char: CHAR; [set, char] _ TextEdit.FetchChar[node, offset]; RETURN [set=0 AND RopeEdit.BlankChar[char]]; }; RETURN [IF sel.start.pos=sel.end.pos -- single char selection AND sel.start.pos.where+1 < size -- not last char in node AND BlankAt[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]; } ELSE IF sel.end.line=line THEN RETURN[after]; RETURN[IF line-sel.start.line <= sel.end.line-line THEN before ELSE after]; }; <<---- Screen to viewbox transforms ------>> selectionDescentLimit: NAT _ 7; <> <<>> ResolveToChar: PUBLIC ENTRY PROC [selection: TEditDocument.Selection, viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, x, y: INTEGER] RETURNS [rightOfLine: BOOLEAN] = TRUSTED { lines: TEditDocument.LineTable; line: INTEGER _ 0; lastLine: INTEGER; <> 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 { -- off end of text x _ LAST[INTEGER]; } ELSE FOR n: INTEGER IN (0..lastLine] DO -- horrible linear search! IF lines[n].yOffset >= y THEN { bottomOfUpper: INTEGER _ lines[n-1].yOffset+lines[n-1].descent; topOfLower: INTEGER _ lines[n].yOffset-lines[n].ascent; break: INTEGER _ MIN[MAX[(bottomOfUpper+topOfLower)/2, lines[n-1].yOffset], lines[n].yOffset-1]; line _ IF y < break THEN n-1 ELSE n; EXIT; }; ENDLOOP; selection.start.y _ lines[line].yOffset-lines[line].ascent; selection.start.h _ lines[line].ascent+MIN[lines[line].descent, selectionDescentLimit]; selection.start.line _ line; GetLine[viewer, line]; [selection.start.pos, selection.start.x, selection.start.w, rightOfLine] _ TEditFormat.Resolve[lineInfo, x]; }; <<-------------->> 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: TEditDocument.Selection; start, end: TextNode.RefTextNode; startPos, endPos, endLen: TextNode.Offset; ropeReader: RopeReader.Ref; IF TEditSelection.pSel=NIL OR TEditSelection.pSel.viewer=NIL THEN RETURN; tSel _ TEditSelection.Alloc[]; TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]; start _ 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 _ 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; TEditSelection.MakeSelection[new: tSel]; TEditSelection.Free[tSel] }; GrowSelection: PUBLIC PROC = { tSel: TEditDocument.Selection; IF TEditSelection.pSel=NIL OR TEditSelection.pSel.viewer=NIL THEN RETURN; tSel _ TEditSelection.Alloc[]; TEditSelection.Copy[source: TEditSelection.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 TEditSelection.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; TEditSelection.MakeSelection[new: tSel]; TEditSelection.Free[tSel] }; <<-------------->> InsertionPoint: PUBLIC PROC [s: TEditDocument.Selection _ TEditSelection.pSel] RETURNS [ip: TextNode.Location] = { nodeSel: BOOLEAN ~ s.granularity = node OR s.granularity = branch; viewer: ViewerClasses.Viewer ~ s.viewer; IF viewer = NIL OR s.data # viewer.data THEN RETURN [[NIL, 0]]; -- in case of vanishing viewers. IF nodeSel AND s.start.pos.node=NIL THEN { <> place: EditSpan.Place ~ IF s.insertion=before THEN before ELSE after; node: TextNode.Ref ~ IF place = before THEN s.start.pos.node ELSE s.end.pos.node; ip _ [EditSpan.InsertTextNode[root: TextNode.Root[node], old: node, where: place, inherit: TRUE, event: TEditInput.currentEvent], 0]; } ELSE { IF s.insertion=before THEN {ip _ s.start.pos} ELSE { size: INT ~ TextEdit.Size[s.end.pos.node]; ip _ [s.end.pos.node, MIN[MAX[s.end.pos.where+1, 0], size]]; }; }; }; GetSelectionGrain: PUBLIC PROC [sel: TEditDocument.Selection] RETURNS [TEditDocument.SelectionGrain] = { 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]; }; <<---- Misc functions ------>> <> lineInfoViewer: ViewerClasses.Viewer _ NIL; lineInfoLine: INTEGER; lineInfo: TEditFormat.LineInfo _ TEditFormat.Allocate[]; InvalidateLineCache: PUBLIC ENTRY PROC = { lineInfoViewer _ NIL }; GetLine: INTERNAL PROC [viewer: ViewerClasses.Viewer, line: INTEGER] = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { lines: TEditDocument.LineTable = tdd.lineTable; IF viewer#lineInfoViewer OR line#lineInfoLine THEN { style: NodeStyle.Ref _ NodeStyleOps.Alloc[]; pos: TextNode.Location = lines[line].pos; kind: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; NodeStyleOps.ApplyAll[style, lines[line].pos.node, kind]; TEditFormat.FormatLine[ lineInfo: lineInfo, node: pos.node, startOffset: pos.where, nodeStyle: style, lineWidth: Scaled.FromInt[viewer.cw], kind: kind ]; lineInfoViewer _ viewer; lineInfoLine _ line; NodeStyleOps.Free[style]; }; }; ENDCASE => NULL; }; CharPositionInCachedLine: PUBLIC ENTRY PROC [viewer: ViewerClasses.Viewer, line: INTEGER, pos: TextNode.Location] RETURNS [x, width: INTEGER] = { GetLine[viewer, line]; [x, width] _ TEditFormat.CharPosition[lineInfo, pos.where]; }; END.