<<>> <> <> <> <> <> <> <> <<>> DIRECTORY Buttons USING [Create], Convert USING [RopeFromInt], ConvertUnsafe USING [ToRope], EditSpanSupport USING [Apply], IO USING [Error], Menus USING [AppendMenuEntry, ChangeNumberOfLines, ClickProc, CreateEntry, CreateMenu, GetLine, GetNumberOfLines, Menu, MenuEntry, MenuLine, MenuProc, SetLine], MessageWindow USING [Append, Blink, Clear], NodeProps USING [GetProp, PutProp], PFS, PFSNames USING [StripVersionNumber], Process USING [Detach, GetCurrent], Rope USING [Cat, Concat, Equal, Flatten, FromRefText, Length, ROPE, Substr], TEditCompile USING [minAvgLineLeading], TEditDocument USING [ForgetViewer, LineTable, LineTableRec, RecordViewerForRoot, Selection, TEditDocumentData, TEditDocumentDataRec], TEditDocumentPrivate USING [AllLevels, Clear, FewerLevels, Find, FindDef, FindWord, FirstLevelOnly, Get, GetImpl, JumpToPrevious, KillSelections, MoreLevels, NewButton, Normalize, OpenButton, Position, PreReset, PreStore, PreviousFile, RecordUnsavedDocument, Reselect, Reset, Save, Split, Store, Time], TEditInput USING [CommandProc, FreeTree, InterpretAtom, Register], TEditInputOps USING [CallWithLocks], TEditLocks USING [Lock, LockDocAndTdd, Unlock, UnlockDocAndTdd], TEditPrivate USING [PaintTEditDocument, TEditNotifyProc], TEditProfile USING [DoList], TEditRefresh USING [ScrollToEndOfSel], TEditScrolling USING [ScrollTEditDocument], TEditSelection USING [Alloc, Free, InputModify, MakeSelection], TEditTouchup USING [fullUpdate], TextEdit USING [FromRope, GetNewlineDelimiter], TextEditBogus USING [GetRope], TextLooks USING [noLooks], TextNode USING [FirstChild, LastLocWithin, Location, LocNumber, LocOffset, LocRelative, LocWithin, Ref, RefTextNode], TiogaIO, TiogaMenuOps USING [], TIPLinking USING [Append], TIPTypes USING [TIPTable], TIPUser USING [InstantiateNewTIPTable, InvalidTable], ViewerBLT USING [ChangeNumberOfLines], ViewerClasses USING [AdjustProc, DestroyProc, GetProc, InitProc, SaveAborted, SaveProc, SetProc, Viewer, ViewerClass, ViewerClassRec], ViewerForkers USING [ForkPaint], ViewerLocks USING [CallUnderViewerTreeLock], ViewerOps USING [AddProp, EnumerateChildren, EnumerateViewers, FetchProp, FetchViewerClass, PaintHint, PaintViewer, RegisterViewerClass, SetMenu], ViewerTools USING [EnableUserEdits, SelPos, SelPosRec, TiogaContents, TiogaContentsRec]; TEditDocumentsImpl: CEDAR MONITOR IMPORTS Buttons, Convert, ConvertUnsafe, EditSpanSupport, IO, Menus, MessageWindow, NodeProps, PFS, PFSNames, Process, Rope, TEditDocument, TEditDocumentPrivate, TEditInput, TEditInputOps, TEditLocks, TEditPrivate, TEditProfile, TEditRefresh, TEditScrolling, TEditSelection, TEditTouchup, TextEdit, TextEditBogus, TextNode, TiogaIO, TIPLinking, TIPUser, ViewerBLT, ViewerClasses, ViewerForkers, ViewerLocks, ViewerOps, ViewerTools EXPORTS TEditDocument, TEditDocumentPrivate, TEditPrivate, TiogaMenuOps SHARES Menus, ViewerClasses = BEGIN ROPE: TYPE ~ Rope.ROPE; TIPTable: TYPE ~ TIPTypes.TIPTable; Stats: TYPE ~ RECORD [ next: NAT ¬ 0, seq: SEQUENCE size: NAT OF RECORD [file: ROPE, estimated, actual: INT] ]; stats: REF Stats ~ NEW[Stats[20]]; fatalTiogaError: PUBLIC ERROR = CODE; -- for RETURN WITH ERROR monitor clearing punts InitTEditDocument: PUBLIC ViewerClasses.InitProc = { data: REF ANY ¬ self.data; self.data ¬ NIL; InitViewerDoc[self, data] }; DocumentFromRope: PROC [rope: ROPE, topLevel: BOOL] RETURNS [TextNode.Ref] ~ { RETURN [TiogaIO.SimpleDocFromRope[rope]] }; InitViewerDoc: PUBLIC PROC [self: ViewerClasses.Viewer, data: REF ANY] = { InitViewerDocInternal[self: self, file: PFS.nullOpenFile, data: data]; }; InitViewerDocInternal: PUBLIC PROC [self: ViewerClasses.Viewer, file: PFS.OpenFile, data: REF ANY] = { tdd: TEditDocument.TEditDocumentData ¬ NARROW[self.data]; needInitTddText: BOOL ¬ FALSE; IF self.link#NIL THEN { IF tdd=NIL THEN self.data ¬ tdd ¬ NARROW[data]; -- someday I should really find out how to fix this in a less horrible manner } ELSE { WITH data SELECT FROM d: TEditDocument.TEditDocumentData => { self.data ¬ tdd ¬ d }; ENDCASE => IF tdd=NIL THEN { tdd ¬ AllocateDataForNewViewer[self] }; }; [] ¬ SpinAndLock[tdd, "InitViewerDoc"]; IF self.link#NIL THEN { <> <> otherInit: ViewerClasses.Viewer ¬ NIL; FOR v: ViewerClasses.Viewer ¬ self.link, v.link UNTIL v=self DO IF NOT v.newVersion THEN {otherInit ¬ v; EXIT}; ENDLOOP; IF otherInit=NIL THEN {needInitTddText ¬ TRUE} ELSE { <> otherTdd: TEditDocument.TEditDocumentData ¬ NARROW[otherInit.data]; tdd.text ¬ otherTdd.text; }; } ELSE { WITH data SELECT FROM d: TEditDocument.TEditDocumentData => { needInitTddText ¬ TRUE }; root: TextNode.RefTextNode => { tdd.text ¬ root }; r: Rope.ROPE => { tdd.text ¬ DocumentFromRope[r, self.parent=NIL] }; t: REF TEXT => { tdd.text ¬ DocumentFromRope[Rope.FromRefText[t], self.parent=NIL] }; ENDCASE => { IF data = NIL THEN { needInitTddText ¬ TRUE } ELSE {--bad data-- tdd.text ¬ TiogaIO.SimpleDocFromRope["*ERROR*"] } }; }; IF needInitTddText THEN { ENABLE { PFS.Error => { self.newFile ¬ TRUE; MessageWindow.Append[Rope.Concat["TEditDocumentsImpl: ", error.explanation], TRUE]; MessageWindow.Blink[]; CONTINUE; }; TiogaIO.Error => { MessageWindow.Append["TEditDocumentsImpl: Error in Tioga formatting of ", TRUE]; MessageWindow.Append[self.file, FALSE]; MessageWindow.Blink[]; CONTINUE; }; }; fileName: PFS.PATH ¬ NIL; wantedUniqueID: PFS.UniqueID ¬ PFS.nullUniqueID; IF tdd.text # NIL THEN { TEditInput.FreeTree[tdd.text]; tdd.text ¬ NIL }; IF file=PFS.nullOpenFile THEN { IF self.file#NIL THEN fileName ¬ PFS.PathFromRope[self.file] } ELSE [fullFName: fileName, uniqueID: wantedUniqueID] ¬ PFS.GetInfo[file]; IF fileName#NIL THEN { fullFName: PFS.PATH; uniqueID: PFS.UniqueID; [fullFName: fullFName, uniqueID: uniqueID, root: tdd.text] ¬ TiogaIO.FromFile[fileName: fileName, wantedUniqueID: wantedUniqueID]; self.file ¬ PFS.RopeFromPath[fullFName]; }; }; IF tdd.text=NIL THEN tdd.text ¬ DocumentFromRope["", self.parent=NIL]; IF NodeProps.GetProp[tdd.text, $OpenFirstLevelOnly]#NIL THEN tdd.clipLevel ¬ 1; tdd.lineTable.lastLine ¬ 0; tdd.lineTable[0].pos ¬ [TextNode.FirstChild[tdd.text], 0]; TEditDocument.RecordViewerForRoot[self, tdd.text]; <> ViewerTools.EnableUserEdits[self]; Unlock[tdd]; }; AllocateDataForNewViewer: PROC [self: ViewerClasses.Viewer] RETURNS [tdd: TEditDocument.TEditDocumentData] = INLINE { tdd ¬ NEW[TEditDocument.TEditDocumentDataRec]; { -- for now, just hack in a conservative size line table maxLines: NAT ~ MAX[2, (self.ch/TEditCompile.minAvgLineLeading)+1]; lineTable: TEditDocument.LineTable ~ NEW[TEditDocument.LineTableRec[maxLines]]; lineTable.lastLine ¬ 0; lineTable.lastY ¬ 0; tdd.lineTable ¬ lineTable; }; IF self.column#static AND self.parent=NIL THEN ViewerOps.SetMenu[self, tiogaMenu, FALSE]; self.data ¬ tdd; }; number: INT ¬ 0; SaveTEditDocument: PUBLIC ViewerClasses.SaveProc = { tdd: TEditDocument.TEditDocumentData ¬ NARROW[self.data]; root: TextNode.Ref; name: ROPE ¬ self.file; errorMessage: ROPE ¬ NIL; IF name.Length = 0 THEN { IF ~force THEN ERROR ViewerClasses.SaveAborted["no file name!"]; name ¬ Rope.Flatten[Rope.Cat[ "SaveAllEdits-", Convert.RopeFromInt[number ¬ number + 1], ".tioga" ]]; }; IF tdd.readOnly THEN ERROR ViewerClasses.SaveAborted["read only document!"]; IF ~force THEN { <> MessageWindow.Clear[]; TEditDocumentPrivate.KillSelections[self]; <> [] ¬ SpinAndLock[tdd, "SaveTEditDocument"]; -- may have other operation in progress root ¬ tdd.text; Unlock[tdd]; -- don't need to keep this locked during rest of Save [] ¬ TEditLocks.Lock[root, "SaveTEditDocument", read]; } ELSE root ¬ tdd.text; <> { ENABLE { PFS.Error => { errorMessage ¬ error.explanation; CONTINUE }; IO.Error => { errorMessage ¬ IF msg#NIL THEN msg ELSE "IO.Error"; CONTINUE }; }; <> fileName: PFS.PATH ~ PFSNames.StripVersionNumber[PFS.PathFromRope[name]]; fullFName: PFS.PATH ~ TiogaIO.ToFile[fileName, root].fullFName; newFile: ROPE ~ PFS.RopeFromPath[fullFName]; newName: ROPE ~ PFS.RopeFromPath[PFSNames.StripVersionNumber[fullFName]]; FOR v: ViewerClasses.Viewer ¬ self, v.link DO v.name ¬ newName; v.file ¬ newFile; SELECT v.link FROM NIL, self => EXIT; ENDCASE; ENDLOOP; }; <<>> <> IF ~force THEN TEditLocks.Unlock[root]; IF errorMessage#NIL THEN ERROR ViewerClasses.SaveAborted[errorMessage]; }; FileIsMoreRecent: PUBLIC PROC [root: TextNode.Ref, file: Rope.ROPE] RETURNS [BOOL] = { <> RETURN [FALSE]; }; nullNode: TextNode.RefTextNode = TextEdit.FromRope[NIL]; SetTEditDocument: ViewerClasses.SetProc = { <<[self: ViewerClasses.Viewer, data: REF ANY, finalise: BOOL _ TRUE, op: ATOM _ NIL]>> IF NOT self.destroyed THEN WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { SELECT op FROM NIL, $TiogaContents, $TiogaDocument, $KeepRootStyleProps => { hint: ViewerOps.PaintHint ¬ client; rope: Rope.ROPE; IF self.link#NIL THEN ERROR; -- not yet implemented TEditDocumentPrivate.KillSelections[self]; [] ¬ SpinAndLock[tdd, "SetTEditDocument"]; SELECT op FROM NIL, $KeepRootStyleProps => { root: TextNode.RefTextNode = tdd.text; node: TextNode.RefTextNode = root; format: ATOM = root.format; child: TextNode.RefTextNode = IF node=NIL THEN NIL ELSE node.child; rope ¬ IF data=NIL THEN "" ELSE NARROW[data]; << -- DKW: this first case clobbers the $NewlineDelimiter property IF child#NIL AND child.last AND child.child=NIL THEN { <> RemoveProps: PROC [name: ATOM, value: REF] RETURNS [BOOL] = { IF name#$DocumentLock AND name#$Viewer AND ~(op=$KeepRootStyleProps AND (name=$Prefix OR name=$Postfix)) THEN NodeProps.PutProp[root, name, NIL]; RETURN [FALSE] }; rootProps: TextNode.NodeProps; <> child­ ¬ nullNode­; rootProps ¬ root.props; root­ ¬ nullNode­; root.props ¬ rootProps; root.child ¬ child; child.next ¬ root; root.last ¬ child.last ¬ TRUE; child.rope ¬ rope; IF op = $KeepRootStyleProps THEN root.formatName ¬ formatName; [] ¬ NodeProps.MapProps[root, RemoveProps, FALSE, FALSE] } ELSE >> { prefix, postfix: REF; IF op=$KeepRootStyleProps THEN { prefix ¬ NodeProps.GetProp[root, $Prefix]; postfix ¬ NodeProps.GetProp[root, $Postfix] }; TEditInput.FreeTree[root]; tdd.text ¬ DocumentFromRope[rope, self.parent = NIL]; IF op=$KeepRootStyleProps THEN { IF prefix#NIL THEN NodeProps.PutProp[tdd.text, $Prefix, prefix]; IF postfix#NIL THEN NodeProps.PutProp[tdd.text, $Postfix, postfix]; tdd.text.format ¬ format; }; }; }; $TiogaContents => { info: ViewerTools.TiogaContents ¬ NARROW[data]; TEditInput.FreeTree[tdd.text]; tdd.text ¬ TiogaIO.FromPair[[info.contents, info.formatting]]; }; $TiogaDocument => { root: TextNode.RefTextNode ¬ NARROW[data]; TEditInput.FreeTree[tdd.text]; tdd.text ¬ root }; ENDCASE; tdd.lineTable.lastLine ¬ 0; tdd.lineTable[0].pos ¬ [TextNode.FirstChild[tdd.text], 0]; TEditDocument.RecordViewerForRoot[self,tdd.text]; self.newVersion ¬ FALSE; -- clear edited bit Unlock[tdd]; IF finalise THEN { <> ViewerForkers.ForkPaint[self, hint, TRUE, TEditTouchup.fullUpdate, TRUE]; }; }; $SelPos => IF NOT self.iconic AND NOT self.destroyed THEN { <> tSel: TEditDocument.Selection ¬ TEditSelection.Alloc[]; sel: ViewerTools.SelPos ¬ NARROW[data]; start, length: INT; [] ¬ TEditLocks.LockDocAndTdd[tdd, "SetTEditDocument", read]; IF sel=NIL THEN { loc1: TextNode.Location ~ [TextNode.FirstChild[tdd.text], 0]; loc2: TextNode.Location ~ TextNode.LastLocWithin[tdd.text]; offset: INT ~ TextNode.LocOffset[loc1: loc1, loc2: loc2, skipCommentNodes: TRUE]; IF tdd.tsInfo#NIL THEN { start ¬ offset; length ¬ 0 } -- for typescripts, caret at end ELSE { start ¬ 0; length ¬ offset }; -- else entire contents } ELSE { start ¬ sel.start; length ¬ sel.length }; tSel.start.pos ¬ TextNode.LocWithin[n: tdd.text, count: start, skipCommentNodes: TRUE]; tSel.end.pos ¬ TextNode.LocRelative[ location: tSel.start.pos, count: MAX[length-1, 0], skipCommentNodes: TRUE]; TEditLocks.UnlockDocAndTdd[tdd]; tSel.granularity ¬ IF length=0 THEN point ELSE char; tSel.viewer ¬ self; tSel.data ¬ tdd; tSel.insertion ¬ IF length=0 THEN before ELSE after; tSel.pendingDelete ¬ TRUE; tSel.looks ¬ TextLooks.noLooks; TEditSelection.MakeSelection[new: tSel]; TEditSelection.Free[tSel]; TEditRefresh.ScrollToEndOfSel[self, FALSE]; }; $ReadOnly => { [] ¬ TEditLocks.LockDocAndTdd[tdd, "SetTEditDocument", read]; self.tipTable ¬ readonlyTIP; tdd.readOnly ¬ TRUE; TEditLocks.UnlockDocAndTdd[tdd]; }; $ReadWrite => { [] ¬ TEditLocks.LockDocAndTdd[tdd, "SetTEditDocument", read]; self.tipTable ¬ tiogaTIP; tdd.readOnly ¬ FALSE; TEditLocks.UnlockDocAndTdd[tdd]; }; ENDCASE; }; ENDCASE; }; GetTEditDocument: ViewerClasses.GetProc = { <<[self: ViewerClasses.Viewer, op: ATOM _ NIL] RETURNS [data: REF ANY]>> data ¬ NIL; WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => SELECT op FROM NIL => data ¬ TiogaIO.WritePlainToRope[tdd.text]; $TiogaContents => { pair: TiogaIO.Pair ~ TiogaIO.ToPair[tdd.text]; data ¬ NEW[ViewerTools.TiogaContentsRec ¬ [pair.contents, pair.formatting]]; }; $SelChars => { rope: Rope.ROPE ¬ NIL; DoSelChars: PROC [root: TextNode.RefTextNode, tSel: TEditDocument.Selection] = { IF tSel.viewer # NIL AND tSel.granularity # point THEN { newline: ROPE ~ TextEdit.GetNewlineDelimiter[root]; SelConcat: PROC [node: TextNode.RefTextNode, start, len: INT] RETURNS [stop: BOOL] = { nrope: Rope.ROPE ~ Rope.Substr[TextEditBogus.GetRope[node], start, len]; rope ¬ IF rope=NIL THEN nrope ELSE Rope.Cat[rope, newline, nrope]; RETURN [FALSE]; }; EditSpanSupport.Apply[[tSel.start.pos, tSel.end.pos], SelConcat]; }; }; TEditInputOps.CallWithLocks[DoSelChars, read]; IF rope=NIL THEN rope ¬ ""; data ¬ rope; }; $SelPos => { sel: ViewerTools.SelPos ¬ NEW[ViewerTools.SelPosRec]; DoSelPos: PROC [root: TextNode.RefTextNode, tSel: TEditDocument.Selection] = { sel.start ¬ TextNode.LocNumber[at: tSel.start.pos, skipCommentNodes: TRUE]; sel.length ¬ TextNode.LocOffset[ loc1: tSel.start.pos, loc2: tSel.end.pos, skipCommentNodes: TRUE] }; TEditInputOps.CallWithLocks[DoSelPos, read]; data ¬ sel; } ENDCASE; ENDCASE; }; DestroyTEditDocument: ViewerClasses.DestroyProc = { TEditDocument.ForgetViewer[self]; < Viewer mapping if necessary>> IF self.link # NIL THEN RETURN; -- linked, so not finished with document yet TRUSTED {Process.Detach[FORK CleanupAfterDestroy[self, self.file, NARROW[self.data]]] } }; CleanupAfterDestroy: PROC [self: ViewerClasses.Viewer, file: Rope.ROPE, tdd: TEditDocument.TEditDocumentData] = { <> root: TextNode.Ref; IF tdd = NIL THEN RETURN; -- anybody remember why this is here (SM)? [] ¬ SpinAndLock[tdd, "DestroyTEditDocument"]; root ¬ tdd.text; IF self.newVersion AND ~self.saveInProgress AND file # NIL THEN TEditDocumentPrivate.RecordUnsavedDocument[file, root] ELSE TEditInput.FreeTree[root]; Unlock[tdd] }; ChangeMenu: PROC [viewer: ViewerClasses.Viewer, subMenu: Menus.MenuEntry] = { menu: Menus.Menu ¬ viewer.menu; found: BOOL ¬ FALSE; numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu]; newLines: Menus.MenuLine ¬ numLines; FOR i: Menus.MenuLine IN [1..numLines) DO <> IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name] THEN { -- yes, so remove it FOR j: Menus.MenuLine IN (i..numLines) DO Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]]; ENDLOOP; <> newLines ¬ newLines-1; found ¬ TRUE; EXIT }; ENDLOOP; IF NOT found THEN { <> GoesBefore: PROC [m1, m2: Menus.MenuEntry] RETURNS [BOOL] = INLINE --gfi saver-- { Priority: PROC [m: Menus.MenuEntry] RETURNS [INTEGER] = { <> <> RETURN [SELECT TRUE FROM Rope.Equal[m.name, "Find"] => 1, Rope.Equal[m.name, "FirstLevelOnly"] => 0, ENDCASE => -1 -- unknown menu goes at bottom -- ] }; RETURN [Priority[m1] > Priority[m2]] }; newLast: Menus.MenuLine = MIN[numLines, LAST[Menus.MenuLine]]; newLines ¬ newLines+1; FOR i: Menus.MenuLine IN [1..numLines) DO IF GoesBefore[subMenu, Menus.GetLine[menu, i]] THEN { <> FOR j: Menus.MenuLine DECREASING IN (i..newLast] DO Menus.SetLine[menu, j, Menus.GetLine[menu, j-1]]; ENDLOOP; Menus.SetLine[menu, i, subMenu]; found ¬ TRUE; EXIT }; ENDLOOP; IF NOT found THEN Menus.SetLine[menu, newLast, subMenu]; }; ViewerBLT.ChangeNumberOfLines[viewer, newLines]; }; LevelMenu: PUBLIC Menus.MenuProc = { ChangeMenu[NARROW[parent], levelMenu] }; FindMenu: PUBLIC Menus.MenuProc = { ChangeMenu[NARROW[parent], findMenu] }; unlocked: CONDITION; SpinAndLock: PUBLIC ENTRY PROC [tdd: TEditDocument.TEditDocumentData, who: Rope.ROPE, interrupt, defer: BOOL ¬ FALSE] RETURNS [ok: BOOL] = TRUSTED { ENABLE UNWIND => NULL; myProcess: PROCESS = LOOPHOLE[Process.GetCurrent[]]; IF myProcess#tdd.lockProcess THEN { IF interrupt THEN { IF defer AND tdd.interrupt > 0 THEN RETURN [FALSE]; tdd.interrupt ¬ tdd.interrupt+1 }; WHILE tdd.lock>0 DO WAIT unlocked; ENDLOOP; tdd.lockProcess ¬ myProcess; tdd.who ¬ who; -- save the first guy who got the lock IF interrupt THEN tdd.interrupt ¬ tdd.interrupt-1; }; tdd.lock ¬ tdd.lock+1; RETURN [TRUE]; }; Unlock: PUBLIC ENTRY PROC [tdd: TEditDocument.TEditDocumentData] = { ENABLE UNWIND => NULL; IF tdd.lockProcess # Process.GetCurrent[] THEN ERROR; IF (tdd.lock ¬ tdd.lock-1) = 0 THEN BROADCAST unlocked }; systemDir: ROPE ~ PFS.RopeFromPath[PFS.GetWDir[]]; SystemTip: PROC [base: ROPE] RETURNS [ROPE] ~ { RETURN [Rope.Cat[systemDir, base, ".tip"]]; }; ReloadTipTable: PUBLIC PROC = { newTIP: TIPTable ¬ ReloadTable[tiogaTIP, "TiogaTIP", SystemTip["Tioga"]]; IF newTIP # NIL THEN tiogaClass.tipTable ¬ tiogaTIP ¬ newTIP; }; ReloadReadonlyTipTable: PUBLIC PROC = { newTIP: TIPTable ¬ ReloadTable[readonlyTIP, "ReadonlyTiogaTIP", SystemTip["ReadonlyTioga"]]; IF newTIP # NIL THEN readonlyTIP ¬ newTIP; }; readonlyTIP: TIPTable; tiogaTIP: PUBLIC TIPTable; ChangeTipTables: PROC [newTIP, oldTIP: TIPTable] = { WithViewerTreeLocked: PROC ~ { changeTip: PROC [v: ViewerClasses.Viewer] RETURNS [BOOL ¬ TRUE] = { WITH v.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF v.tipTable = oldTIP THEN v.tipTable ¬ newTIP; }; ENDCASE => NULL; ViewerOps.EnumerateChildren[v, changeTip]; }; typescriptClass: ViewerClasses.ViewerClass ~ ViewerOps.FetchViewerClass[$Typescript]; ViewerOps.EnumerateViewers[changeTip]; <> IF tiogaClass.tipTable = oldTIP THEN tiogaClass.tipTable ¬ newTIP; IF tiogaTIP = oldTIP THEN tiogaTIP ¬ newTIP; IF readonlyTIP = oldTIP THEN readonlyTIP ¬ newTIP; IF typescriptClass#NIL AND typescriptClass.tipTable = oldTIP THEN typescriptClass.tipTable ¬ newTIP; }; ViewerLocks.CallUnderViewerTreeLock[WithViewerTreeLocked]; }; ReloadTable: PUBLIC PROC [oldTIP: TIPTable, profileKey, default: Rope.ROPE] RETURNS [newTIP: TIPTable] = { ok: BOOL ¬ TRUE; AddTable: PROC [r: Rope.ROPE] = { bad: BOOL ¬ FALSE; t: TIPTable; msg: Rope.ROPE; IF NOT ok THEN RETURN; IF Rope.Equal[r,"default",FALSE] THEN { TEditProfile.DoList["", AddTable, default]; RETURN }; t ¬ TIPUser.InstantiateNewTIPTable[r ! PFS.Error => { bad ¬ TRUE; msg ¬ Rope.Concat["Cannot read TIP table file: ", r]; CONTINUE }; TIPUser.InvalidTable => { bad ¬ TRUE; msg ¬ Rope.Concat["Error(s) saved on TIP.Errors for: ", r]; CONTINUE }]; IF bad THEN { ok ¬ FALSE; MessageWindow.Append[msg, TRUE]; RETURN }; IF newTIP=NIL THEN { newTIP ¬ t; RETURN }; IF TIPLinking.Append[newTIP, t]#NIL THEN { ok ¬ FALSE; MessageWindow.Append[Rope.Concat["Loop in TIP table layers caused by ", r], TRUE]; }; }; TEditProfile.DoList[profileKey, AddTable, default]; IF ~ok AND oldTIP=NIL THEN { <> ok ¬ TRUE; TEditProfile.DoList["", AddTable, default] }; IF ok AND oldTIP # NIL AND newTIP # NIL THEN TRUSTED { Process.Detach[FORK ChangeTipTables[newTIP, oldTIP]]; }; }; alwaysInvalid: BOOL ¬ FALSE; LocalAdjust: ViewerClasses.AdjustProc = { <<[self: ViewerClasses.Viewer] RETURNS [adjusted: BOOL _ FALSE]>> IF self = NIL OR self.iconic OR self.destroyed OR self.paintingWedged THEN RETURN [FALSE]; WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { ch: INTEGER ¬ self.ch; lines: TEditDocument.LineTable ¬ tdd.lineTable; IF lines # NIL THEN FOR i: INT IN [0..lines.lastLine] DO IF lines[i].valid THEN IF alwaysInvalid OR lines[i].yOffset >= ch THEN lines[i].valid ¬ FALSE; ENDLOOP; }; ENDCASE; RETURN [TRUE]; }; tiogaClass: ViewerClasses.ViewerClass ¬ NEW[ViewerClasses.ViewerClassRec ¬ [ paint: TEditPrivate.PaintTEditDocument, bltV: top, -- use blt to copy screen contents to top of viewer when height changes bltH: none, -- but not if width changes notify: TEditPrivate.TEditNotifyProc, modify: TEditSelection.InputModify, adjust: LocalAdjust, init: InitTEditDocument, set: SetTEditDocument, get: GetTEditDocument, save: SaveTEditDocument, destroy: DestroyTEditDocument, scroll: TEditScrolling.ScrollTEditDocument <> ]]; <> tiogaMenu: PUBLIC Menus.Menu ¬ NIL; findMenu, levelMenu, lineMenu: PUBLIC Menus.MenuEntry ¬ NIL; preReset: REF Menus.ClickProc = NEW[Menus.ClickProc ¬ TEditDocumentPrivate.PreReset]; preStore: REF Menus.ClickProc = NEW[Menus.ClickProc ¬ TEditDocumentPrivate.PreStore]; <<>> CreateTiogaMenu: PROC ~ { Append: PROC [line: Menus.MenuLine, name: LONG STRING, proc: Menus.ClickProc, guard: REF Menus.ClickProc ¬ NIL] ~ { nameRope: ROPE ~ ConvertUnsafe.ToRope[name]; <> entry: Menus.MenuEntry ~ Menus.CreateEntry[name: nameRope, proc: proc, guarded: (guard#NIL), documentation: guard]; Menus.AppendMenuEntry[menu: tiogaMenu, entry: entry, line: line]; }; tiogaMenu ¬ Menus.CreateMenu[]; <> Append[0, "Clear", TEditDocumentPrivate.Clear]; Append[0, "Reset", TEditDocumentPrivate.Reset, preReset]; Append[0, "Get", TEditDocumentPrivate.Get]; Append[0, "GetImpl", TEditDocumentPrivate.GetImpl]; Append[0, "PrevFile", TEditDocumentPrivate.PreviousFile]; Append[0, "Store", TEditDocumentPrivate.Store, preStore]; Append[0, "Save", TEditDocumentPrivate.Save]; Append[0, "Time", TEditDocumentPrivate.Time]; Append[0, "Split", TEditDocumentPrivate.Split]; Append[0, "Places", FindMenu]; Append[0, "Levels", LevelMenu]; <> Append[1, "Find", TEditDocumentPrivate.Find]; Append[1, "Word", TEditDocumentPrivate.FindWord]; Append[1, "Def", TEditDocumentPrivate.FindDef]; Append[1, "Position", TEditDocumentPrivate.Position]; Append[1, "Normalize", TEditDocumentPrivate.Normalize]; Append[1, "PrevPlace", TEditDocumentPrivate.JumpToPrevious]; Append[1, "Reselect", TEditDocumentPrivate.Reselect]; Append[1, "StyleKind", StyleKindClick]; <> <> Append[2, "FirstLevelOnly", TEditDocumentPrivate.FirstLevelOnly]; Append[2, "MoreLevels", TEditDocumentPrivate.MoreLevels]; Append[2, "FewerLevels", TEditDocumentPrivate.FewerLevels]; Append[2, "AllLevels", TEditDocumentPrivate.AllLevels]; findMenu ¬ Menus.GetLine[tiogaMenu, 1]; levelMenu ¬ Menus.GetLine[tiogaMenu, 2]; Menus.SetLine[tiogaMenu, 1, NIL]; Menus.SetLine[tiogaMenu, 2, NIL]; Menus.ChangeNumberOfLines[tiogaMenu, 1]; -- only show the top level to start }; <> StyleKindClick: PUBLIC Menus.ClickProc = { TEditInput.InterpretAtom[NARROW[parent], SELECT mouseButton FROM red => $StyleKindScreen, yellow, blue => $StyleKindPrint, ENDCASE => ERROR]; }; StyleKindScreenOp: TEditInput.CommandProc = { old: REF ~ ViewerOps.FetchProp[viewer, $StyleKind]; IF old#NIL THEN { ViewerOps.AddProp[viewer, $StyleKind, NIL]; ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE]; }; }; StyleKindPrintOp: TEditInput.CommandProc = { old: REF ~ ViewerOps.FetchProp[viewer, $StyleKind]; IF old#$Print THEN { ViewerOps.AddProp[viewer, $StyleKind, $Print]; ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE]; }; }; TEditInput.Register[$StyleKindPrint, StyleKindPrintOp]; TEditInput.Register[$StyleKindScreen, StyleKindScreenOp]; CreateTiogaMenu[]; ReloadTipTable[]; ReloadReadonlyTipTable[]; ViewerOps.RegisterViewerClass[$Text, tiogaClass]; [] ¬ Buttons.Create[info: [name: "New"], proc: TEditDocumentPrivate.NewButton, fork: FALSE]; <> [] ¬ Buttons.Create[info: [name: "Open"], proc: TEditDocumentPrivate.OpenButton]; END.