<<>> <> <> <> <> <> <> <> <> <<>> DIRECTORY Ascii USING [CR, LF], Char USING [XCHAR], CharOps USING [AlphaNumeric, Blank, XBlank], EditSpan USING [CompareNodeOrder, InsertTextNode, NodeOrder, Place], NodeStyle USING [Ref], NodeStyleOps USING [Alloc, ApplyAll, Free, OfStyle], Rope USING [Size], RopeReader USING [Backwards, Get, GetIndex, GetRopeReader, FreeRopeReader, Ref, SetCharForEndOfRope, SetPosition], Scaled USING [FromInt], TextEdit USING [FetchChar, FetchLooks, Size], TextEditBogus USING [GetRope], TextLooks USING [Looks, noLooks], TextNode USING [EndPos, FirstChild, LastWithin, Location, Parent, Ref, RefTextNode, Root], TEditDocument USING [BeforeAfter, LineTable, PunctuationPosition, SelectionGrain, SelectionPoint, Selection, SelectionId, 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 [], TEditSelectionPrivateExtras USING [], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], ViewerClasses USING [Viewer], ViewerGroupLocks USING [CallRootUnderWriteLock], ViewerOps USING [FetchProp]; TEditMouseImpl: CEDAR MONITOR IMPORTS CharOps, EditSpan, NodeStyleOps, Rope, RopeReader, Scaled, TEditInput, TEditProfile, TEditFormat, TEditSelection, TEditTouchup, TextEdit, TextEditBogus, TextNode, ViewerGroupLocks, ViewerOps EXPORTS TEditSelection, TEditSelectionPrivate, TEditSelectionPrivateExtras = BEGIN <<---- Mouse hit primitives ------>> DoSelect: PUBLIC 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] = INLINE --gfi saver-- { 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: INT; 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]; }; <> <> <> <> <> <> <> < SELECT TRUE FROM>> < after,>> <0 => before,>> < IF y>viewer.ch/2 THEN after ELSE before,>> < after,>> < before,>> < before,>> <=end.pos.where => after,>> < IF Dist[sel, before, x, y] < Dist[sel, after, x, y] THEN before ELSE after,>> < before,>> < after,>> < 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: TextNode.Location; 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: INT; 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: INT, punc: TEditDocument.PunctuationPosition ¬ none] = { refChar, char: CHARACTER; alpha: BOOLEAN; node: TextNode.RefTextNode = pos.node; lastOffset: INT ¬ TextEdit.Size[node]-1; ropeReader: RopeReader.Ref ¬ RopeReader.GetRopeReader[]; start ¬ end ¬ pos.where; RopeReader.SetPosition[ropeReader, TextEditBogus.GetRope[node], end]; RopeReader.SetCharForEndOfRope[ropeReader, Ascii.CR]; -- so we get a return at the end refChar ¬ RopeReader.Get[ropeReader]; SELECT refChar FROM Ascii.CR, Ascii.LF => { RopeReader.FreeRopeReader[ropeReader]; RETURN }; -- line break is a word ENDCASE; alpha ¬ CharOps.AlphaNumeric[refChar]; char ¬ RopeReader.Get[ropeReader]; WHILE ((alpha AND CharOps.AlphaNumeric[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: INT ¬ 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 { RETURN [IF sel.start.pos=sel.end.pos -- single char selection AND sel.start.pos.where+1 < size -- not last char in node AND CharOps.XBlank[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]; } 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 ¬ viewer.cw+10; } 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 [CharOps.Blank[char]] }; GrowSelectionToSomething[Blank, Blank] }; GrowSelectionToSomething: PUBLIC PROC [left, right: PROC [CHAR] RETURNS [BOOLEAN]] = { tSel, cSel: TEditDocument.Selection; selection: TEditDocument.SelectionId; start, end: TextNode.RefTextNode; startPos, endPos, endLen: INT; ropeReader: RopeReader.Ref; cSel ¬ TEditSelection.sSel; selection ¬ secondary; IF cSel=NIL OR TEditSelection.sSel.viewer=NIL THEN { cSel ¬ TEditSelection.pSel; selection ¬ primary; }; IF cSel=NIL OR cSel.viewer=NIL THEN RETURN; tSel ¬ TEditSelection.Alloc[]; TEditSelection.Copy[source: cSel, dest: tSel]; start ¬ tSel.start.pos.node; ropeReader ¬ RopeReader.GetRopeReader[]; RopeReader.SetPosition[ropeReader, TextEditBogus.GetRope[start], tSel.start.pos.where]; DO -- find first blank to left of start of selection loc: INT = 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[TextEditBogus.GetRope[end]]; RopeReader.SetPosition[ropeReader, TextEditBogus.GetRope[end], endPos]; DO -- find first blank to right of end of selection loc: INT = 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; IF endPos-startPos > 1 AND tSel.granularity = point THEN tSel.granularity ¬ char; IF tSel.granularity # point THEN tSel.insertion ¬ after; TEditSelection.MakeSelection[new: tSel, selection: selection]; 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: INT; 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 ¬ 0; 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] ]; 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.