TEditDocumentsImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) August 19, 1985 4:07:47 pm PDT
Bier, January 10, 1989 11:50:21 am PST
Michael Plass, March 23, 1989 3:09:02 pm PST
Willie-s, February 13, 1991 3:32 pm PST
Doug Wyatt, March 26, 1993 4:06 pm PST
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 {
make sure another link didn't already init
and if so, copy that data
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 {
somebody else already did the init
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];
old heuristic: IF HasBrackets[self.file] THEN ViewerTools.InhibitUserEdits[self] ELSE
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 {
lock so no edits while saving
MessageWindow.Clear[];
TEditDocumentPrivate.KillSelections[self];
We want to kill off selections so we don't lock up the world while saving.
[] ¬ 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;
Save the file.
{
ENABLE {
PFS.Error => { errorMessage ¬ error.explanation; CONTINUE };
IO.Error => { errorMessage ¬ IF msg#NIL THEN msg ELSE "IO.Error"; CONTINUE };
};
defaultKeep: INT ~ UserProfile.Number["Tioga.defaultKeep", 2];
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;
};
Unlock the document if it was locked to begin with.
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] = {
Returns true if the file on the disk has a more recent create date than the root file create date. RRA sez that this should never happen in the wonderful world of Cedar 5.0.
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 {
reuse rather than reallocate
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;
Should really have a write lock on the document for this. change for Tioga2.
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 {
Need a repaint
ViewerForkers.ForkPaint[self, hint, TRUE, TEditTouchup.fullUpdate, TRUE];
};
};
$SelPos =>
IF NOT self.iconic AND NOT self.destroyed THEN {
no iconic selections please
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];
this also fixes up the Root => 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] = {
Need to pass self.file as variable because it will be set to NIL
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
see if already showing the submenu
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;
Menus.ChangeNumberOfLines[menu, newLast-1];
newLines ¬ newLines-1;
found ¬ TRUE; EXIT
};
ENDLOOP;
IF NOT found THEN {
add it. do insertion sort to get it in the right place
GoesBefore: PROC [m1, m2: Menus.MenuEntry] RETURNS [BOOL] = INLINE --gfi saver-- {
Priority: PROC [m: Menus.MenuEntry] RETURNS [INTEGER] = {
higher priority means goes above in series of submenus
looks at rope for first item to identify the subMenu.
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 {
put it here
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];
Would really like to enumerate viewer classes here . . .
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 {
try the default by itself
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
tipTable set below
]];
Menu creation
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];
DKW: this crock reduces the number of rope literals in this module, to avoid breaking the C compiler's optimizer. Change name to a ROPE if this is ever fixed.
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[];
First line:
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];
Places menu:
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];
StyleKind in the places menu is flakey, but there's room
Levels menu:
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
};
Stuff for StyleKind button (should live in TEditDocuments3Impl, but I didn't want to change or add interfaces. — MFP)
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];
don't fork since will change the selection; want to capture following typein
[] ¬ Buttons.Create[info: [name: "Open"], proc: TEditDocumentPrivate.OpenButton];
END.