<> <> <> <> <> <<>> DIRECTORY Carets USING [StartCaret, StopCaret], Convert USING [Error, IntFromRope], EditSpan USING [CompareNodeOrder], Imager USING [Color, Context, MaskRectangleI, SetColor], ImagerBackdoor USING [MakeStipple], InputFocus USING [GetInputFocus, SetInputFocus], MessageWindow USING [Append, Blink, Clear], NodeProps USING [GetProp], Process USING [Detach, GetCurrent], Rope USING [Concat, IsEmpty, ROPE, Size, Substr], TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, SelectionRec, TEditDocumentData], TEditInput USING [CloseEvent, interrupt], TEditLocks USING [LockDocAndTdd, LockRef, Unlock, UnlockDocAndTdd], TEditOps USING [GetSelContents, RememberCurrentPosition], TEditProfile USING [selectionCaret], TEditRefresh USING [ScrollToEndOfSel], TEditSelection USING [FindWhere, ForceDown, IsDown, SetSelLooks], TEditSelectionOps USING [], TEditSelectionPrivate USING [CharPositionInCachedLine], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], TextEdit USING [FetchChar, Size], TextFind USING [MalformedPattern], TextNode USING [EndPos, FirstChild, ForwardClipped, LastWithin, Level, Location, LocWithin, Parent, Ref, Root, Span, StepBackward, StepForward], TreeFind USING [CreateFromRope, Finder, Try, TryBackwards], ViewerClasses USING [ModifyProc, Viewer], ViewerLocks USING [CallUnderWriteLock], ViewerOps USING [AddProp, BlinkDisplay, FetchProp, PaintViewer]; TEditSelectionImpl: CEDAR MONITOR IMPORTS Carets, Convert, EditSpan, Imager, ImagerBackdoor, InputFocus, MessageWindow, NodeProps, Process, Rope, TEditInput, TEditLocks, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TEditSelectionPrivate, TEditTouchup, TextEdit, TextFind, TextNode, TreeFind, ViewerLocks, ViewerOps EXPORTS TEditSelection, TEditSelectionOps = BEGIN Node: TYPE = TextNode.Ref; ROPE: TYPE = Rope.ROPE; Selection: TYPE = TEditDocument.Selection; SelectionId: TYPE ~ TEditDocument.SelectionId; FindWhere: TYPE ~ TEditSelection.FindWhere; Viewer: TYPE = ViewerClasses.Viewer; <> LockRec: TYPE = RECORD [ process: PROCESS _ NIL, -- the process holding the lock whoLast, whoFirst: ROPE, -- the last lockers count, maxCount: [0..255] _ 0 -- number of times inside the lock ]; LockRef: TYPE = REF LockRec; selLock: ARRAY SelectionId OF LockRef _ [NEW[LockRec], NEW[LockRec], NEW[LockRec]]; unlocked: CONDITION; UnlockDocAndPSel: PUBLIC PROC [root: Node] = { TEditLocks.Unlock[root]; UnlockSel[primary] }; LockBothSelections: PUBLIC PROC [who: ROPE] = { LockSel[primary, who]; LockSel[secondary, who] }; UnlockBothSelections: PUBLIC PROC = { UnlockSel[primary]; UnlockSel[secondary] }; LockSel: PUBLIC ENTRY PROC [selection: SelectionId, who: ROPE] = { <> ENABLE UNWIND => NULL; myProcess: PROCESS; lock: LockRef = selLock[selection]; TRUSTED {myProcess _ LOOPHOLE[Process.GetCurrent[]]}; IF myProcess#lock.process THEN { WHILE lock.count>0 DO WAIT unlocked; ENDLOOP; lock.process _ myProcess; }; IF lock.count=0 THEN lock.whoFirst _ who ELSE lock.whoLast _ who; lock.count _ lock.count+1; lock.maxCount _ MAX[lock.maxCount, lock.count]; }; UnlockSel: PUBLIC ENTRY PROC [selection: SelectionId _ primary] = { <> ENABLE UNWIND => NULL; lock: LockRef = selLock[selection]; TRUSTED { IF lock.process # Process.GetCurrent[] THEN ERROR }; IF (lock.count _ lock.count-1) > 0 THEN RETURN; lock.whoLast _ lock.whoFirst _ NIL; BROADCAST unlocked }; <> pSel: PUBLIC Selection _ Create[]; sSel: PUBLIC Selection _ Create[]; fSel: PUBLIC Selection _ Create[]; oldSel: PUBLIC Selection _ Create[]; nilSel: PUBLIC Selection _ Create[]; tSel1, tSel2, tSel3: Selection _ NIL; -- cache of temporary selection records Create: PUBLIC PROC RETURNS [Selection] = { RETURN [NEW[TEditDocument.SelectionRec]] }; Alloc: PUBLIC ENTRY PROC RETURNS [tSel: Selection] = { ENABLE UNWIND => NULL; IF tSel3 # NIL THEN { tSel _ tSel3; tSel3 _ NIL } ELSE IF tSel2 # NIL THEN { tSel _ tSel2; tSel2 _ NIL } ELSE IF tSel1 # NIL THEN { tSel _ tSel1; tSel1 _ NIL } ELSE tSel _ Create[]; }; Free: PUBLIC ENTRY PROC [tSel: Selection] = { ENABLE UNWIND => NULL; IF tSel3 = tSel OR tSel2 = tSel OR tSel1 = tSel THEN ERROR; IF tSel3 = NIL THEN tSel3 _ tSel ELSE IF tSel2 = NIL THEN tSel2 _ tSel ELSE IF tSel1 = NIL THEN tSel1 _ tSel; }; Copy: PUBLIC ENTRY PROC [source, dest: Selection] = { ENABLE UNWIND => NULL; dest^ _ source^; }; <> MakePointSelection: PUBLIC PROC [selection: Selection, pos: TextNode.Location] = { <> tSel: Selection _ Alloc[]; Copy[source: selection, dest: tSel]; tSel.start.pos _ tSel.end.pos _ pos; tSel.granularity _ point; tSel.pendingDelete _ FALSE; tSel.insertion _ before; MakeSelection[selection: primary, new: tSel]; Free[tSel]; }; ChangeSelections: PROC [proc: PROC [tSel: Selection], sel: Selection] = { tSel: Selection _ Alloc[]; LockBothSelections["ChangeBothSelections"]; { ENABLE UNWIND => UnlockBothSelections[]; Copy[source: sel, dest: tSel]; proc[tSel] }; UnlockBothSelections[]; Free[tSel] }; PushOrExchangeSelections: PUBLIC PROC = { DoPush: PROC [tSel: Selection] = { MakeSelection[IF sSel.viewer=NIL THEN NIL ELSE sSel, primary]; MakeSelection[IF tSel.viewer=NIL THEN NIL ELSE tSel, secondary] }; ChangeSelections[DoPush, pSel] }; MakePrimary: PUBLIC PROC = { <> DoMakePrimary: PROC [tSel: Selection] = { Deselect[secondary]; MakeSelection[tSel, primary] }; ChangeSelections[DoMakePrimary, sSel] }; MakeSecondary: PUBLIC PROC = { <> DoMakeSecondary: PROC [tSel: Selection] = { Deselect[primary]; MakeSelection[tSel, secondary] ; }; ChangeSelections[DoMakeSecondary, pSel] }; CancelPrimary: PUBLIC PROC = { MakeSelection[NIL, primary]; }; CancelSecondary: PUBLIC PROC = { MakeSelection[NIL, secondary]; }; CancelFeedback: PUBLIC PROC = { MakeSelection[NIL, feedback]; }; FakeSecondary: PUBLIC PROC [sel: Selection] = { inner: PROC = { IF sSel.viewer # NIL THEN Deselect[secondary]; Copy[source: sel, dest: sSel]; }; WithLockedSelection[secondary, inner, "FakeSecondary"]; }; Deselect: PUBLIC PROC [selection: SelectionId _ primary] = { <> inner: PROC = { sel: Selection = SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; viewer: Viewer = sel.viewer; op: ATOM = SELECT selection FROM primary => $TakeDownPSel, secondary => $TakeDownSSel, feedback => $TakeDownFSel, ENDCASE => ERROR; down: BOOL = TEditSelection.IsDown[selection]; Copy[source: nilSel, dest: sel]; IF viewer#NIL AND NOT down THEN { <> ViewerOps.PaintViewer[viewer, client, FALSE, op]; IF NOT TEditSelection.IsDown[selection] THEN TEditSelection.ForceDown[selection]; <> } }; WithLockedSelection[selection, inner, "Deselect"]; }; MakeSelection: PUBLIC PROC [new: Selection _ NIL, selection: SelectionId _ primary, startValid, endValid: BOOL _ FALSE, forkPaint: BOOL _ TRUE] = { <> inner: PROC = { sel: Selection; op: ATOM; SELECT selection FROM primary => { if: Viewer = InputFocus.GetInputFocus[].owner; sel _ pSel; op _ $ShowPSel; IF new=NIL THEN {IF if#NIL THEN InputFocus.SetInputFocus[NIL]} ELSE {IF if#new.viewer THEN InputFocus.SetInputFocus[new.viewer]}; }; feedback => { sel _ fSel; op _ $ShowFSel; }; secondary => { sel _ sSel; op _ $ShowSSel; }; ENDCASE => ERROR; IF new=NIL OR new.viewer # sel.viewer THEN Deselect[selection]; IF new#NIL THEN { IF selection#feedback AND fSel.viewer=new.viewer THEN Deselect[feedback]; <> Copy[source: new, dest: sel]; sel.start.metricsValid _ startValid; sel.end.metricsValid _ endValid; IF sel.granularity=point THEN sel.pendingDelete _ FALSE; IF forkPaint THEN { showSel: REF ShowSelRec = SELECT selection FROM primary => showPSel, secondary => showSSel, ENDCASE => showFSel; IF showSel.process = NIL THEN TRUSTED { Process.Detach[showSel.process _ FORK ShowSel[showSel, op ! ABORTED => CONTINUE]] } } ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op]; }; }; WithLockedSelection[selection, inner, "MakeSelection"]; }; ShowSelRec: TYPE = RECORD [ process: PROCESS, selection: SelectionId ]; showPSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, primary]]; showSSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, secondary]]; showFSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, feedback]]; ShowSel: PROC [my: REF ShowSelRec, op: ATOM] = { selection: TEditDocument.SelectionId _ my.selection; sel: Selection _ SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; inner: PROC = { viewer: Viewer _ sel.viewer; inner: PROC = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF NOT TEditTouchup.LockAfterRefresh[tdd, "ShowSel"] THEN RETURN; ViewerOps.PaintViewer[viewer, client, FALSE, op ! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]]; TEditTouchup.UnlockAfterRefresh[tdd]; }; ENDCASE; }; IF viewer#NIL THEN ViewerLocks.CallUnderWriteLock[inner, viewer]; my.process _ NIL; }; WithLockedSelection[selection, inner, "ShowSel"]; }; lightGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0208H, xor: TRUE]; darkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0A5A5H, xor: TRUE]; veryDarkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0EBEBH, xor: TRUE]; black: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0FFFFH, xor: TRUE]; SelColor: TYPE = {black, lightGrey, darkGrey, veryDarkGrey} _ black; SelBound: TYPE = {solid, line} _ solid; selectionLineWidth: NAT _ 2; feedbackLineWidth: NAT _ 4; feedbackLineRaise: INTEGER _ 1; selectionDescentLimit: NAT _ 7; <> MarkSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, selection: Selection, id: SelectionId] = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { lines: TEditDocument.LineTable = tdd.lineTable; vHeight: INTEGER = selection.viewer.ch; selBound: SelBound = IF selection.pendingDelete AND id#feedback THEN solid ELSE line; selColor: SelColor = IF id=primary THEN black ELSE IF id=feedback THEN veryDarkGrey ELSE IF selection.pendingDelete THEN lightGrey ELSE darkGrey; EffectSelect: PROC [x, y, w, h: INTEGER] = { lineHeight: NAT _ selectionLineWidth; bottom: INTEGER _ vHeight-y-h; IF id=feedback THEN { lineHeight _ feedbackLineWidth; bottom _ bottom - feedbackLineRaise }; SELECT selBound FROM line => Imager.MaskRectangleI[dc, x, bottom, w, lineHeight]; solid => Imager.MaskRectangleI[dc, x, bottom, w, vHeight-y-bottom]; ENDCASE => ERROR; }; IF selection.end.line<0 OR selection.start.line=LAST[INTEGER] OR (selection.start.line=selection.end.line AND selection.end.clipped) THEN RETURN; -- not visible SELECT selColor FROM black => Imager.SetColor[dc, black]; lightGrey => Imager.SetColor[dc, lightGrey]; darkGrey => Imager.SetColor[dc, darkGrey]; veryDarkGrey=> Imager.SetColor[dc, veryDarkGrey]; ENDCASE => ERROR; SELECT TRUE FROM selection.start.line = selection.end.line OR (selection.start.line < 0 AND selection.end.line = 0) => { <> x, y: INTEGER; IF selection.start.line < 0 OR selection.start.clipped THEN { <> EffectSelect[selection.start.x, selection.start.y, (lines[sl].width + lines[sl].xOffset - selection.start.x), selection.start.h] } ELSE { <> EffectSelAll[n]; ENDLOOP; SELECT TRUE FROM selection.end.clipped => {}; -- end.line is not actually part of the selection el=selection.end.line => { EffectSelect[lines[el].xOffset, selection.end.y, selection.end.x - lines[el].xOffset + selection.end.w, selection.end.h]; }; ENDCASE => { EffectSelAll[el];-- end.line is off screen, so select all of el }; }; }; ENDCASE; }; ExtendSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, old, new: Selection, id: SelectionId, updateEnd: BOOL] = { <> TooHard: PROC = { MarkSelection[dc, viewer, old, id]; -- take it down FixupSelection[new, viewer]; -- get position info about the new selection MarkSelection[dc, viewer, new, id]; -- put it up Copy[source: new, dest: old]; }; IF updateEnd THEN { <> BumpLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: Node = loc.node; where: INT _ loc.where+1; IF where >= TextEdit.Size[n] THEN RETURN [[TextNode.StepForward[n],0]]; RETURN [[n,where]]; }; tooHard: BOOL = old.end.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.end.pos.node = old.end.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.end.pos=old.end.pos => {}; (sameNode AND new.end.pos.where>old.end.pos.where) OR (NOT sameNode AND EditSpan.CompareNodeOrder[new.end.pos.node, old.end.pos.node]=after) => { <> new.start _ old.end; new.start.pos _ BumpLoc[new.start.pos]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ new.end.pos; <> FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.end _ new.end; } ENDCASE => { <> save: TextNode.Location = new.end.pos; new.start _ new.end; new.start.pos _ BumpLoc[save]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ old.end.pos; <> new.end _ old.end; FixupSelection[new, viewer, TRUE, FALSE]; -- fix start MarkSelection[dc, viewer, new, id]; old.end _ new.start; old.end.pos _ save; FixupSelection[old, viewer, FALSE, TRUE]; }; } ELSE { <> DecLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: Node; IF loc.where > 0 THEN RETURN [[loc.node, loc.where-1]]; n _ TextNode.StepBackward[loc.node]; RETURN [[n,MAX[TextEdit.Size[n],1]-1]] }; tooHard: BOOL = old.start.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.start.pos.node = old.start.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.start.pos=old.start.pos => {}; (sameNode AND new.start.pos.where { <> new.end.pos _ DecLoc[old.start.pos]; FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.start _ new.start; } ENDCASE => { <> save: TextNode.Location = new.start.pos; new.end.pos _ DecLoc[save]; new.start _ old.start; FixupSelection[new, viewer, FALSE, TRUE]; MarkSelection[dc, viewer, new, id]; old.start.pos _ save; FixupSelection[old, viewer, TRUE, FALSE]; }; }; Copy[source: old, dest: new]; }; CannotFindIt: PUBLIC ERROR = CODE; ComputeSpanLines: PUBLIC PROC [viewer: Viewer, span: TextNode.Span] RETURNS [start, end: INTEGER, startClipped, endClipped: BOOL] = { [start, startClipped] _ ComputePosLine[viewer, span.start]; SELECT TRUE FROM span.start=span.end => { end _ start; endClipped _ startClipped }; start=LAST[INTEGER] => end _ LAST[INTEGER]; -- scrolled off bottom ENDCASE => { firstLine: INTEGER = IF start<=0 THEN 0 ELSE IF startClipped THEN start-1 ELSE start; [end, endClipped] _ ComputePosLine[viewer, span.end, firstLine]; }; }; ComputePosLine: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0] RETURNS [line: INTEGER, clipped: BOOL] = { sp: TEditDocument.SelectionPoint _ ComputePosPoint[viewer, pos, firstLine, TRUE]; RETURN [sp.line, sp.clipped]; }; ComputePosPoint: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0, lineOnly: BOOL _ FALSE] RETURNS [sp: TEditDocument.SelectionPoint] = { <> <> WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { foundPos: BOOL _ FALSE; IsPosPastLine: PROC [pos: TextNode.Location, line: INTEGER] RETURNS [BOOL] = { next: INT; next _ lines[line].pos.where+lines[line].nChars; IF pos.where >= next AND lines[line].end # eon THEN RETURN [TRUE]; -- it is past this line RETURN [FALSE] }; FindPos: PROC [pos: TextNode.Location] RETURNS [found: BOOL, line: INTEGER] = { found _ FALSE; FOR n: INTEGER IN [firstLine..lines.lastLine] DO SELECT TRUE FROM lines[n].pos.node=pos.node => { IF pos.where < lines[n].pos.where THEN EXIT; -- have gone past it found _ TRUE; line _ n }; found => EXIT; ENDCASE; ENDLOOP }; lines: TEditDocument.LineTable _ tdd.lineTable; sp.pos _ pos; IF lines[0].pos.node = pos.node THEN { IF lines[0].pos.where > pos.where THEN { <> sp.y _ sp.line _ -LAST[INTEGER]; RETURN; }; }; IF lines[lines.lastLine].pos.node=pos.node AND IsPosPastLine[pos,lines.lastLine] THEN { sp.y _ sp.line _ LAST[INTEGER]; -- after end of text RETURN; }; firstLine _ MIN[lines.lastLine,MAX[firstLine,0]]; [foundPos, sp.line] _ FindPos[pos]; sp.clipped _ FALSE; IF NOT foundPos THEN SELECT EditSpan.CompareNodeOrder[pos.node, lines[0].pos.node] FROM same, before => { sp.line _ sp.y _ -LAST[INTEGER]; RETURN }; -- before beginning disjoint => ERROR CannotFindIt; ENDCASE => { -- pos.node comes after first line node IF tdd.clipLevel = TEditDocument.maxClip AND tdd.commentFilter=includeComments THEN { <> sp.line _ sp.y _ LAST[INTEGER]; RETURN } -- after end ELSE { <> SELECT EditSpan.CompareNodeOrder[pos.node, lines[lines.lastLine].pos.node] FROM same, after => { <> sp.line _ sp.y _ LAST[INTEGER]; RETURN }; disjoint => ERROR CannotFindIt; -- may have been deleted or moved ENDCASE => { -- it got clipped n: Node _ pos.node; delta: INTEGER _ TextNode.Level[n]-tdd.clipLevel; firstLine _ 0; -- need to let FindPos look at all the previous lines FOR i:INTEGER IN [0..delta) DO n _ TextNode.Parent[n]; ENDLOOP; n _ TextNode.ForwardClipped[n,tdd.clipLevel].nx; [foundPos, sp.line] _ FindPos[[n,0]]; IF NOT foundPos THEN ERROR CannotFindIt; <> sp.clipped _ TRUE; }; }; }; IF lineOnly OR sp.clipped THEN RETURN; -- otherwise, compute metrics sp.y _ lines[sp.line].yOffset-lines[sp.line].ascent; sp.h _ lines[sp.line].ascent+MIN[lines[sp.line].descent, selectionDescentLimit]; [sp.x, sp.w] _ TEditSelectionPrivate.CharPositionInCachedLine[viewer, sp.line, pos]; IF sp.x = LAST[INTEGER] THEN { <> IF sp.line=lines.lastLine THEN {sp.line _ sp.y _ LAST[INTEGER]} ELSE {sp.x _ lines[sp.line].width; sp.w _ 0}; }; sp.metricsValid _ TRUE; }; ENDCASE; }; FixupSelection: PUBLIC PROC [selection: Selection, viewer: Viewer, start, end: BOOL _ TRUE] = { <> <> IF start THEN selection.start _ ComputePosPoint[viewer, selection.start.pos]; IF end THEN SELECT TRUE FROM selection.start.pos=selection.end.pos => { <> selection.end _ selection.start; }; selection.start.line=LAST[INTEGER] => { <> selection.end.line _ LAST[INTEGER]; selection.end.metricsValid _ TRUE; }; ENDCASE => { firstLine: INTEGER = ( IF selection.start.line<=0 THEN 0 ELSE IF selection.start.clipped THEN selection.start.line-1 ELSE selection.start.line ); selection.end _ ComputePosPoint[viewer, selection.end.pos, firstLine]; }; }; FixupCaret: PUBLIC PROC [selection: Selection] = { <> lines: TEditDocument.LineTable = selection.data.lineTable; node: Node _ selection.end.pos.node; size: INT _ TextEdit.Size[node]; PutCaretOnNextLine: PROC = { SELECT selection.end.line FROM IN [0..lines.lastLine) => { nextLine: INTEGER _ selection.end.line+1; selection.caretX _ lines[nextLine].xOffset; selection.caretY _ selection.viewer.ch - lines[nextLine].yOffset - lines[nextLine].descent; }; = lines.lastLine => { selection.caretX _ lines[selection.end.line].xOffset; selection.caretY _ selection.viewer.ch - selection.end.y - selection.end.h - selection.end.h; }; ENDCASE => ERROR; }; IF selection.insertion=before THEN { IF selection.start.clipped THEN selection.caretY _ LAST[INTEGER] -- caret not visible ELSE { selection.caretX _ selection.start.x; selection.caretY _ IF selection.start.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.start.y - selection.start.h; }; } ELSE { SELECT TRUE FROM selection.end.clipped => { <> selection.caretY _ LAST[INTEGER]; }; selection.end.pos.where IN [0..size) AND selection.end.line IN [0..lines.lastLine] AND TextEdit.FetchChar[node,selection.end.pos.where]=[0,15B] => { PutCaretOnNextLine[]; }; ENDCASE => { selection.caretX _ selection.end.x+selection.end.w; selection.caretY _ ( IF selection.end.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.end.y - selection.end.h ); }; }; }; KillSelection: PUBLIC PROC = { <> IF InputFocus.GetInputFocus[]#NIL THEN InputFocus.SetInputFocus[]; }; ModifyPSel: PROC [proc: PROC [root: Node, tSel: Selection]] = { inner: PROC = { root: Node _ SelectionRoot[pSel]; IF root#NIL THEN { tSel: Selection _ Alloc[]; Copy[source: pSel, dest: tSel]; proc[root, tSel ! UNWIND => Free[tSel]]; MakeSelection[new: tSel]; Free[tSel]; }; }; WithLockedSelection[primary, inner, "CallWithSelLock"]; }; SelectEverything: PUBLIC PROC = { <> DoSelEverything: PROC [root: Node, tSel: Selection] = { root _ TextNode.Root[tSel.start.pos.node]; tSel.start.pos _ [TextNode.FirstChild[root], 0]; tSel.end.pos.node _ TextNode.LastWithin[root]; tSel.end.pos.where _ TextEdit.Size[tSel.end.pos.node]-1; tSel.granularity _ branch; }; ModifyPSel[DoSelEverything] }; PendingDeleteSelection: PUBLIC PROC = { DoPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete _ TRUE }; ModifyPSel[DoPDel] }; NotPendingDeleteSelection: PUBLIC PROC = { DoNotPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete _ FALSE }; ModifyPSel[DoNotPDel] }; CaretBeforeSelection: PUBLIC PROC = { DoCaretBeforeSelection: PROC [root: Node, tSel: Selection] = {tSel.insertion _ before }; ModifyPSel[DoCaretBeforeSelection] }; CaretAfterSelection: PUBLIC PROC = { DoCaretAfterSelection: PROC [root: Node, tSel: Selection] = { IF tSel.granularity # point THEN tSel.insertion _ after }; ModifyPSel[DoCaretAfterSelection] }; <> WithLockedSelection: PROC [which: SelectionId, inner: PROC, why: ROPE] = { LockSel[which, why]; inner[ ! UNWIND => UnlockSel[which]]; UnlockSel[which]; }; SelectionRoot: PUBLIC PROC [s: Selection _ pSel] RETURNS [root: Node] = { IF s.viewer=NIL THEN RETURN [TextNode.Root[s.start.pos.node]]; WITH s.viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => RETURN [tdd.text]; ENDCASE => RETURN [NIL]; }; InputModify: PUBLIC ViewerClasses.ModifyProc = { WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { SELECT change FROM set, pop => Carets.StartCaret[self, pSel.caretX, pSel.caretY, primary]; kill => { inner: PROC = {Copy[source: pSel, dest: prop]; Deselect[primary]}; prop: Selection _ NARROW[ViewerOps.FetchProp[self, $SelectionHistory]]; Carets.StopCaret[primary]; IF prop=NIL THEN ViewerOps.AddProp[self, $SelectionHistory, prop _ Create[]]; WithLockedSelection[primary, inner, "InputModify"]; }; push => Carets.StopCaret[primary]; ENDCASE => ERROR; }; ENDCASE; }; <> CallWithSelAndDocAndTddLocks: PUBLIC PROC [viewer: Viewer, id: SelectionId _ primary, proc: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection]] = { IF viewer # NIL THEN WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { tSel: Selection _ NIL; lockRef: TEditLocks.LockRef _ NIL; Cleanup: PROC = { UnlockSel[id]; IF lockRef # NIL THEN TEditLocks.UnlockDocAndTdd[tdd]; IF tSel # NIL THEN Free[tSel] }; IF tdd # NIL THEN { ENABLE UNWIND => Cleanup[]; LockSel[id, "CallWithSelAndDocAndTddLocks"]; lockRef _ TEditLocks.LockDocAndTdd[tdd, "CallWithSelAndDocAndTddLocks", read]; IF lockRef#NIL THEN { sel: Selection = SELECT id FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; tSel _ Alloc[]; Copy[source: sel, dest: tSel]; proc[tdd, tSel] }; Cleanup[]; }; }; ENDCASE; }; ShowPosition: PUBLIC PROC[viewer: Viewer, skipCommentNodes: BOOL _ TRUE] = { count: INT _ -1; sel: ROPE _ TEditOps.GetSelContents[]; count _ Convert.IntFromRope[sel ! Convert.Error => CONTINUE]; IF count>=0 THEN ShowGivenPosition[viewer, count, skipCommentNodes] ELSE { MessageWindow.Append["Select character count for position.", TRUE]; MessageWindow.Blink[] }; }; Position: PUBLIC PROC[viewer: Viewer] ~ { ShowPosition[viewer: viewer, skipCommentNodes: TRUE]; }; ShowGivenPosition: PUBLIC PROC [viewer: Viewer, pos: INT, skipCommentNodes: BOOL _ TRUE] = { <> DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { tiogaFile: BOOL _ (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes); IF NOT tiogaFile THEN pos _ pos+1; -- hack to compensate for leading CR tSel.start.pos _ tSel.end.pos _ TextNode.LocWithin[tdd.text, pos, 1, skipCommentNodes]; tSel.end.pos.where _ MIN[tSel.end.pos.where+3, TextNode.EndPos[tSel.end.pos.node]]; tSel.start.pos.where _ MIN[tSel.end.pos.where, tSel.start.pos.where]; IF tSel.start.pos.where=tSel.end.pos.where AND tSel.start.pos.where > 0 THEN tSel.start.pos.where _ tSel.start.pos.where-1; tSel.granularity _ char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.pendingDelete _ FALSE; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: feedback]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, feedback]; <> }; CallWithSelAndDocAndTddLocks[viewer, feedback, DoPosition]; }; ShowGivenPositionRange: PUBLIC PROC [viewer: Viewer, selectionId: SelectionId, posI, posF: INT, skipCommentNodes, pendingDelete: BOOL] = { <> DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { tiogaFile: BOOL = (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes); IF posF < posI THEN {p: INT _ posI; posI _ posF; posF _ p}; IF NOT tiogaFile THEN {posI _ posI + 1; posF _ posF+1}; -- hack to compensate for leading CR tSel.start.pos _ TextNode.LocWithin[tdd.text, posI, 1, skipCommentNodes]; tSel.end.pos _ TextNode.LocWithin[tdd.text, posF, 1, skipCommentNodes]; tSel.granularity _ char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.pendingDelete _ pendingDelete; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: selectionId]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, selectionId]; <> }; CallWithSelAndDocAndTddLocks[viewer, selectionId, DoPosition]; }; targetOfLastSearch: ROPE _ NIL; Find: PUBLIC PROC [viewer: Viewer, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] ~ { rope: ROPE _ TEditOps.GetSelContents[]; IF Rope.IsEmpty[rope] THEN rope _ targetOfLastSearch ELSE targetOfLastSearch _ rope; IF Rope.IsEmpty[rope] THEN { MessageWindow.Append["Select target for search.", TRUE]; MessageWindow.Blink[]; } ELSE FindRope[viewer, rope, findWhere, def, word, id, case]; }; FindRope: PUBLIC PROC [viewer: Viewer, rope: ROPE, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] = { IF NOT DoFind[viewer, rope, findWhere, def, word, id, case] THEN { target: ROPE _ rope; IF Rope.Size[target]>50 THEN target _ Rope.Concat[Rope.Substr[target, 0, 47], "..."]; MessageWindow.Append[Rope.Concat[target, " not found."], TRUE]; ViewerOps.BlinkDisplay[]; } ELSE MessageWindow.Clear[]; }; DoFind: PUBLIC PROC [viewer: Viewer, rope: ROPE, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] RETURNS [found: BOOL] = { finder: TreeFind.Finder _ NIL; where: Node; first: Node; start, at, atEnd: INT; DoFindIt: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { SelectionVisibleInViewer: PROC RETURNS [BOOL] = { RETURN [tSel.viewer=viewer AND tSel.end.line IN [0..tdd.lineTable.lastLine]] }; visible: BOOL ~ SelectionVisibleInViewer[]; interrupt: REF BOOL ~ TEditInput.interrupt; Forward: PROC = { IF visible THEN { first _ tSel.end.pos.node; start _ tSel.end.pos.where+1 } ELSE { first _ tdd.lineTable.lines[0].pos.node; start _ tdd.lineTable.lines[0].pos.where }; [found,where,at,atEnd,,] _ TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt]; }; Backward: PROC = { IF visible THEN { first _ tSel.start.pos.node; start _ tSel.start.pos.where } ELSE { first _ tdd.lineTable.lines[0].pos.node; start _ tdd.lineTable.lines[0].pos.where }; [found,where,at,atEnd,,] _ TreeFind.TryBackwards[finder: finder, first: first, len: start, interrupt: interrupt] }; FromStart: PROC = { first _ TextNode.FirstChild[tdd.text]; start _ 0; [found,where,at,atEnd,,] _ TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt] }; interrupt^ _ FALSE; SELECT findWhere FROM forwards => Forward[]; backwards => Backward[]; anywhere => { Forward[]; IF NOT found THEN FromStart[] }; ENDCASE => ERROR; IF NOT found THEN RETURN; IF def THEN atEnd _ atEnd-1; -- skip the trailing : tSel.start.pos _ [where,at]; tSel.end.pos _ [where,MAX[0,atEnd-1]]; tSel.granularity _ IF word THEN word ELSE char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; tSel.insertion _ after; tSel.pendingDelete _ FALSE; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: id]; TEditInput.CloseEvent[]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, id]; }; found _ FALSE; IF Rope.Size[rope] = 0 THEN RETURN; IF def THEN rope _ Rope.Concat[rope,":"]; finder _ TreeFind.CreateFromRope[pattern: rope, literal: TRUE, ignoreCase: NOT case, word: word ! TextFind.MalformedPattern => IF ec=toobig THEN GOTO TooBig <> ]; IF finder#NIL THEN CallWithSelAndDocAndTddLocks[viewer, id, DoFindIt]; EXITS TooBig => { MessageWindow.Append["Pattern too long", TRUE]; MessageWindow.Blink[]; }; }; END.