Last Edited by: Teitelman, May 11, 1983 2:53 pm
DIRECTORY
AMTypes USING [TV, Type, TVType, TypeClass, TVToType],
DateAndTime USING [Parse],
IO USING [BreakProc, CreateDribbleStream, CreateOutputStreamToRope, EndOf, EndOfStream, GetCedarToken, Flush, GetChar, GetOutputStreamRope, GetToken, PutChar, PutF, PutType, RIS, ROPE, rope, SkipOver, SyntaxError, STREAM, time, TokenProc, UserAborted, WhiteSpace],
List USING [NthTail],
Menus USING [ClickProc, CreateEntry, FindEntry, AppendMenuEntry],
MessageWindow USING [Blink, Clear, Append],
Process USING [Detach, MsecToTicks, Pause],
Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, IsEmpty, Length, Replace, Run, Substr, Upper],
System USING [SecondsSinceEpoch],
Time USING [Current],
TiogaOps USING [AddLooks, BackSpace, BackWord, Break, CallWithLocks, CaretAfter, CaretBefore, CaretOnly, ClearLooks, CommandProc, CompareNodeOrder, Delete, DeleteNextCharacter, FetchLooks, FindText, FindWord, FindDef, FirstChild, GetCaret, GetProp, GetRope, GetSelection, GetTextKey, GoToPreviousCharacter, InsertRope, InsertTime, IsComment, IsDirty, LastChild, LastLocWithin, Location, Lock, LockSel, Nest, Next, NextPlaceholder, Parent, Previous, PutProp, PutTextKey, Ref, RegisterAbbrevFailedProc, RegisterCommand, RestoreSelA, RestoreSelB, SaveSelA, SaveSelB, SelectMatchingBrackets, SelectNodes, SelectPoint, SetComment, SetFormat, SetLooks, SetSelection, StepForward, SubtractLooks, Unlock, UnlockSel, UnNest, ViewerDoc],
TiogaOpsDefs USING [Ref],
TiogaMenuOps USING [tiogaMenu, Load, CloseAndOpen],
TEditDocuments2Impl USING [DoOpenFile],
UserExec USING [GetNameAndPassword, Expression, EvalExpr, CreateExpr, ParseFailed, EvaluationFailed, GetExecHandle],
UserExecExtras USING [GetChangesLog, GetSessionLog],
UserProfile USING [ProfileChangedProc, CallWhenProfileChanges, Boolean, Token],
UserTerminal USING [BlinkDisplay],
ViewerClasses USING [Viewer],
ViewerTools USING [GetSelectionContents, GetSelectedViewer],
WindowManager USING [WaitCursor, UnWaitCursor]
;
NewStuffImpl: CEDAR PROGRAM
IMPORTS AMTypes, DateAndTime, IO, List, Menus, MessageWindow, Process, Rope, System, TEditDocuments2Impl, Time, TiogaMenuOps, TiogaOps, UserExec, UserExecExtras, UserProfile, UserTerminal, ViewerTools, WindowManager
= BEGIN OPEN IO;
Types
Viewer: TYPE = ViewerClasses.Viewer;
Location: TYPE = TiogaOps.Location;
TiogaRef: TYPE = TiogaOpsDefs.Ref;
Control-E expansion
Expand:
PROC
RETURNS [
BOOL] = {
caret: Location;
r: ROPE;
i: INT;
caret ← TiogaOps.GetCaret[];
r ← TiogaOps.GetRope[caret.node];
i ← caret.where;
IF i = 0 THEN RETURN[FALSE];
WHILE i > 0
DO
char: CHARACTER = Rope.Fetch[r, i - 1];
IF -- char # '* AND -- NOT IO.TokenProc[char] = other THEN EXIT;
i ← i -1;
ENDLOOP;
TRUSTED {Process.Detach[FORK Expand0[Rope.Substr[base: r, start: i, len: caret.where - i], caret]]}; -- forked because evaluating the expression might require confirmation.
RETURN[TRUE];
};
Expand0:
PROC [name:
ROPE, caret: Location] = {
Expand1:
PROC [root:
REF] = {
IF newName #
NIL
THEN
{TiogaOps.BackSpace[Rope.Length[name]];
TiogaOps.InsertRope[newName];
caret ← TiogaOps.GetCaret[];
};
TiogaOps.SaveSelA[];
TiogaOps.PutProp[n: caret.node, name: $StartOfExpansion, value: expansion];
TiogaOps.PutTextKey[node: caret.node, where: caret.where - 1, key: expansion]; -- associates a sticky address with the first character in the expansion. expansion is used as the key simply because it is a unique ref. could just as easily have done a gensym.
IF Rope.Run[s1: expansion, s2: " -- "] = 4
THEN
{TiogaOps.SetLooks[looks: "ck", which: caret];
TiogaOps.InsertRope[expansion];
TiogaOps.ClearLooks[which: caret];
TiogaOps.InsertRope[" = {"];
TiogaOps.Break[];
TiogaOps.Nest[];
TiogaOps.InsertRope["Body"];
TiogaOps.Break[];
TiogaOps.InsertRope["};"];
[] ← TiogaOps.NextPlaceholder[dir: backwards, gotoend: FALSE];
}
ELSE
{TiogaOps.InsertRope[expansion];
TiogaOps.GoToPreviousCharacter[Rope.Length[expansion]];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
};
};
viewer: Viewer;
expansion, newName: ROPE;
[viewer, ] ← TiogaOps.GetSelection[];
IF Rope.Find[name, "."] = -1
AND UserExec.GetExecHandle[viewer: viewer] =
NIL
THEN
{n: TiogaRef ← TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]; -- should check and if module not loaded, then search file instead of evaluating
WHILE n #
NIL
DO
r: ROPE = TiogaOps.GetRope[n];
IF
NOT TiogaOps.IsComment[n]
AND (Rope.Find[r, "PROGRAM"] # -1
OR Rope.Find[r, "MONITOR"] # -1)
THEN
{name ← Rope.Cat[IO.GetCedarToken[IO.RIS[r]], ".", name];
EXIT
};
n ← TiogaOps.Next[n];
ENDLOOP;
};
MessageWindow.Append[message: Rope.Cat["Evaluating ", name, "..."], clearFirst: TRUE];
WindowManager.WaitCursor[];
[expansion, newName] ← ExpandRecord[name, viewer ! UNWIND => WindowManager.UnWaitCursor[]];
WindowManager.UnWaitCursor[];
IF viewer = TiogaOps.GetSelection[].viewer
THEN
{
IF TiogaOps.GetCaret[] # caret
THEN
{MessageWindow.Append["Caret has moved, insertion NOT performed.", TRUE];
MessageWindow.Blink[];
RETURN;
};
}
ELSE TiogaOps.SetSelection[viewer: viewer, start: caret, end: caret]; -- user went to some other viewer or exec while waiting.
MessageWindow.Clear[];
IF useLocks THEN TiogaOps.CallWithLocks[proc: Expand1]
ELSE Expand1[NIL];
};
ExpandRecord:
PROC [name:
ROPE, viewer: Viewer]
RETURNS [expansion:
ROPE, newName:
ROPE] = {
h: STREAM = CreateOutputStreamToRope[];
i: INT;
expr: UserExec.Expression = UserExec.CreateExpr[name];
tv: AMTypes.TV;
type: AMTypes.Type;
[] ← UserExec.EvalExpr[expr: expr, exec:
NIL, viewer: viewer !
UserExec.ParseFailed => {MessageWindow.Append[msg, TRUE]; MessageWindow.Blink[]; GOTO Fail};
UserExec.EvaluationFailed => {MessageWindow.Append[msg, TRUE]; MessageWindow.Blink[]; GOTO Fail};
IO.UserAborted => {MessageWindow.Append["Aborted", TRUE]; GOTO Fail};
];
tv ← expr.value;
type ← AMTypes.TVType[tv];
IF expr.correctionMade THEN newName ← expr.rope;
IF AMTypes.TypeClass[type] = type
THEN
{h.PutType[type: AMTypes.TVToType[tv], verbose: TRUE]; -- if the tv describes a type, e.g. UserExec.CommandProc, don't want the type of that object, which would just be TYPE, but rather the object itself, printed as a type.
expansion ← h.GetOutputStreamRope[];
i ← Rope.Find[expansion, "["];
IF i = -1 THEN expansion ← NIL
ELSE expansion ← Rope.Cat[" -- ", Rope.Substr[base: expansion, start: i], " -- ", ];
RETURN;
};
h.PutType[type: type, verbose: TRUE];
expansion ← h.GetOutputStreamRope[];
IF (i ← Rope.Find[expansion, "RETURNS"]) # -1 THEN expansion ← Rope.Substr[base: expansion, len: i - 1]; -- the minus one is to delete the space before the RETURNS. important in case of typescripts, so that ctrl-next puts you at the end of the typescript.
i ← Rope.Find[expansion, "["];
IF i = -1 THEN {expansion ← "[]"; RETURN};
expansion ← Rope.Substr[expansion, i]; -- strips off the PROCEDURE, PROC, ERROR, RECORD, whatever.
{
Expand1:
PROC [args:
ROPE]
RETURNS[expansion:
ROPE] = {
left: ROPE = Rope.FromChar['\001];
right: ROPE = Rope.FromChar['\002];
i, length: INT;
i ← 1;
expansion ← args;
length ← Rope.Length[expansion];
DO
-- i is the first character in this argument. find end of argument
n: INT ← -1; -- will be character position of :
j: INT ← i; -- will be position of the first character beyond this argument, i.e. the , or ]
WHILE j < length
DO
SELECT Rope.Fetch[expansion, j]
FROM
'', '\\ => j ← j + 2;
'], ', => EXIT;
': => n ← j;
'[ =>
-- default value for this argument is a record constructor. find matching right ] and replace interior by its expansion
{count: INT ← 1;
k: INT ← j + 1;
r: ROPE;
nChars: INT;
WHILE k < length
DO
SELECT Rope.Fetch[expansion, k]
FROM
'', '\\ => {k ← k + 2; LOOP};
'[ => count ← count + 1;
'] => IF (count ← count - 1) = 0 THEN EXIT;
ENDCASE;
k ← k + 1;
REPEAT
FINISHED => GOTO Fail; -- did not find matching ]
ENDLOOP;
nChars ← k - j + 1;
r ← Expand1[Rope.Substr[base: expansion, start: j, len: nChars]];
expansion ← Rope.Replace[base: expansion, start: j, len: nChars, with: r];
length ← Rope.Length[expansion];
j ← k + (Rope.Length[r] - nChars) + 1; -- first character beyond the matching ]
EXIT;
};
ENDCASE;
j ← j + 1;
ENDLOOP;
IF n = -1 OR j >= length THEN GOTO Fail;
expansion ← Rope.Replace[base: expansion, start: j, len: 0, with: right]; -- insert right place holder. work from right so positions accurate.
expansion ← Rope.Replace[base: expansion, start: n + 2, len: 0, with: left]; -- + 2 to skip over the : and the space.
length ← length + 2;
i ← j + 3;
IF i = length THEN EXIT;
ENDLOOP;
EXITS
Fail => expansion ← args;
};
expansion ← Expand1[expansion];
};
EXITS
Fail => expansion ← NIL;
};
Control-Next
CtrlNextPlaceholder: TiogaOps.CommandProc
-- [viewer: ViewerClasses.Viewer ← NIL] RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE] --
= {
Cleanup1:
PROC [root: TiogaRef] = {
viewer: Viewer;
start, end: Location;
key: REF ANY;
fieldsRemain: BOOL ← FALSE;
[viewer, start, end] ← TiogaOps.GetSelection[];
IF (key ← TiogaOps.GetProp[n: start.node, name: $StartOfExpansion]) #
NIL
THEN {
TiogaOps.SelectPoint[viewer: viewer, caret: [start.node, TiogaOps.GetTextKey[start.node, key].where + 2]]; -- + 1 takes us to the [, +2 to the first character inside of the [. when sticky addresses done right, then can make sticky address for both start and end of selection and not worry about node breaks, and then not have to do the SelectMatchingBrackets
};
IF TiogaOps.SelectMatchingBrackets['[, ']] THEN [, start, end] ← TiogaOps.GetSelection[]
ELSE
{r: ROPE = TiogaOps.GetRope[start.node];
IF start.node = end.node AND Rope.Fetch[r, start.where] = '\001 AND Rope.Fetch[r, end.where] = '\002 THEN TiogaOps.Delete[];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
RETURN;
};
TiogaOps.PutTextKey[node: end.node, where: end.where, key: $EndCleanup];
TiogaOps.SaveSelA[];
TiogaOps.CaretBefore[];
DO
loc: Location;
r: ROPE;
IF NOT TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN EXIT;
loc ← TiogaOps.GetCaret[];
IF loc.node # end.node OR loc.where > TiogaOps.GetTextKey[node: end.node, key: $EndCleanup].where THEN EXIT;
r ← ViewerTools.GetSelectionContents[];
IF Rope.Find[r, "←"] = -1
THEN {
fieldsRemain ← TRUE;
LOOP;
};
TiogaOps.Delete[];
loc ← TiogaOps.GetCaret[];
r ← TiogaOps.GetRope[loc.node];
IF Rope.Fetch[r, loc.where - 2] = ': THEN TiogaOps.BackWord[]; -- deletes the name:
loc ← TiogaOps.GetCaret[];
r ← TiogaOps.GetRope[loc.node];
IF Rope.Fetch[r, loc.where - 1] # '[
THEN
-- not first argument, delete preceding space and comma
TiogaOps.BackSpace[2]
ELSE
IF Rope.Fetch[r, loc.where] = ',
THEN
-- was first argument, but not last argument, delete next space and comma
TiogaOps.DeleteNextCharacter[2]
ENDLOOP;
end.where ← TiogaOps.GetTextKey[node: end.node, key: $EndCleanup].where;
IF fieldsRemain
THEN {
TiogaOps.RestoreSelA[];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
MessageWindow.Append["Selected field cannot be defaulted.", TRUE];
}
ELSE {
TiogaOps.RestoreSelA[];
TiogaOps.CaretAfter[];
TiogaOps.CaretOnly[];
};
};
IF useLocks THEN TiogaOps.CallWithLocks[proc: Cleanup1]
ELSE Cleanup1[NIL];
};
NextPlaceholder: TiogaOps.CommandProc
-- [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE] --
= {
start, end: Location;
node: TiogaRef;
r: ROPE;
IF NOT TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN RETURN;
quit ← TRUE;
[, start, end, ] ← TiogaOps.GetSelection[];
IF start.where # 0 THEN RETURN;
r ← TiogaOps.GetRope[start.node];
IF NOT Rope.Equal[r, " ."] THEN RETURN;
r ← TiogaOps.GetRope[(node ← TiogaOps.Next[start.node])];
IF NOT Rope.Equal[r, " ."] THEN RETURN;
r ← TiogaOps.GetRope[(node ← TiogaOps.Next[node])];
IF NOT Rope.Equal[r, " ."] THEN RETURN;
r ← TiogaOps.GetRope[TiogaOps.Next[node]];
TiogaOps.SetSelection[viewer, start, start];
TiogaOps.Break[];
TiogaOps.InsertRope[Rope.Substr[base: r, len: Rope.Length[r] - 1]];
TiogaOps.GoToPreviousCharacter[Rope.Length[r]];
TiogaOps.CaretOnly[];
[] ← TiogaOps.NextPlaceholder[gotoend: TRUE];
};
Save operations: updating date, changelog
sessionLog, changesLog: IO.STREAM;
RedSave: TiogaOps.CommandProc = {
changeLogNode: TiogaRef;
description: ROPE;
Stars:
PROCEDURE = {
sessionLog.PutChar['\n];
FOR i:
INT
IN [0..50)
DO
sessionLog.PutChar['*];
ENDLOOP;
sessionLog.PutChar['\n];
};
LogChange:
PROC [out:
IO.
STREAM] = {
IF sessionLog # NIL THEN Stars[];
out.PutF["\nChanged File: %g\n", rope[viewer.name]];
IF description = NIL THEN out.PutF["\tEdited on %t (no change log entry)\n", time[]]
ELSE out.PutF["%g", rope[description]];
IF sessionLog # NIL THEN Stars[];
out.Flush[];
};
IF sessionLog = NIL THEN sessionLog ← UserExecExtras.GetSessionLog[];
IF changesLog =
NIL
THEN {
changesLog ← UserExecExtras.GetChangesLog[];
IF changesLog # NIL AND sessionLog # NIL THEN changesLog ← IO.CreateDribbleStream[changesLog, sessionLog];
};
changeLogNode ← UpdateForSave[viewer: viewer, updateLastEdited: updateLastEdited, updateChangeLog: updateChangeLog, insertPlaceHolders: FALSE, mergeWithPrevious: ifSoonEnough];
IF changeLogNode #
NIL
THEN {
node: TiogaOps.Ref ← TiogaOps.FirstChild[changeLogNode];
description ← Rope.Cat["\t", TiogaOps.GetRope[changeLogNode], "\n"];
WHILE node #
NIL
DO
description ← Rope.Cat[description, "\t\t", TiogaOps.GetRope[node], "\n"];
node ← TiogaOps.Next[node];
ENDLOOP;
};
IF changesLog # NIL THEN LogChange[changesLog]
ELSE IF sessionLog # NIL THEN LogChange[sessionLog];
};
UpdateForSave:
PROC [viewer: Viewer, updateLastEdited:
BOOL, updateChangeLog: UpdateChange, insertPlaceHolders:
BOOL, mergeWithPrevious: MergeChange]
RETURNS[changeLogNode: TiogaRef] = {
rootNode: TiogaRef = TiogaOps.ViewerDoc[viewer]; -- root, the argument passed in to WriteDate1, is the root of the selection. which might not be in the viewer being saved.
user: ROPE ← UserExec.GetNameAndPassword[].name;
i: INT;
IF Rope.IsEmpty[user] THEN RETURN;
IF (i ← Rope.Find[user, "."]) # -1 THEN user ← Rope.Substr[base: user, len: i];
user ← Rope.Concat[Rope.FromChar[Rope.Upper[Rope.Fetch[user, 0]]], Rope.Substr[base: user, start: 1]];
{
ENABLE UNWIND => IF useLocks THEN {TiogaOps.Unlock[rootNode]; TiogaOps.UnlockSel[]};
restoreSel: BOOL ← TRUE;
TiogaOps.SaveSelA[]; -- for restoration, e.g. so user can click save and then click compile and not have to be aware of the fact that save will change the selection.
IF useLocks THEN {TiogaOps.LockSel[]; TiogaOps.Lock[rootNode]};
IF updateLastEdited THEN UpdateLastEdited[viewer, rootNode, user];
IF updateChangeLog # never THEN [changeLogNode, restoreSel] ← UpdateChangeLog[viewer, rootNode, user, updateChangeLog, insertPlaceHolders, mergeWithPrevious];
IF restoreSel THEN TiogaOps.RestoreSelA[];
IF useLocks THEN {TiogaOps.Unlock[rootNode]; TiogaOps.UnlockSel[]};
};
};
FindDate:
PROC [r:
ROPE]
RETURNS [i, j:
INT] = {
FOR months:
LIST
OF
ROPE ←
LIST["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], months.rest
UNTIL months =
NIL
DO
i ← Rope.Find[r, months.first];
IF i # -1
THEN
{
IF (j ← Rope.Find[r, " am", i]) # -1
OR (j ← Rope.Find[r, " pm", i]) # -1
THEN
RETURN[i, j + 2]; -- returns position of first and last character in date.
};
ENDLOOP;
RETURN[-1, -1];
};
SelectDate:
PROC [viewer: Viewer, loc: Location, rope:
ROPE]
RETURNS [
BOOLEAN] = {
loc is the location corresponding to r. In most cases, where is 0 and r is GetRope. If r contains a date, this procedure selects the date and ret urns TRUE. Otherwise returns FALSE.
i, j: INT;
[i, j] ← FindDate[rope];
IF i = -1 THEN RETURN[FALSE];
TiogaOps.SetSelection[viewer: viewer, start: [node: loc.node, where: loc.where + i], end: [node: loc.node, where: loc.where + j]];
TiogaOps.SetLooks[looks: TiogaOps.FetchLooks[loc.node, loc.where + i], which: caret];
RETURN[TRUE];
};
UpdateLastEdited:
PROC [viewer: Viewer, rootNode: TiogaRef, user:
ROPE] = {
node: TiogaRef;
nodeRope: ROPE;
s: IO.STREAM;
current, next: INT ← 0;
r, insertRope: ROPE;
foundOne: BOOL ← FALSE;
insertAt: Location ← [NIL, 0];
DO
IF node =
NIL
THEN
-- first time
{node ← TiogaOps.FirstChild[rootNode];
TiogaOps.SelectPoint[viewer: viewer, caret: [node: node, where: 0]];
nodeRope ← TiogaOps.GetRope[node];
next ← -1;
}
ELSE
IF next = -1
THEN
-- just exhausted a node
{node ← TiogaOps.StepForward[node];
IF node = NIL THEN EXIT;
IF insertAt.node # node AND NOT Rope.IsEmpty[nodeRope] THEN insertAt ← [node, 0];
nodeRope ← TiogaOps.GetRope[node];
current ← 0;
}
ELSE
{current ← next + 1;
IF NOT Rope.IsEmpty[r] THEN insertAt ← [node, current];
};
IF TiogaOps.IsComment[node]
THEN
r ← nodeRope
ELSE
{c: CHAR;
oldR: ROPE = r;
IF (next ← Rope.Find[s1: nodeRope, s2: "\n", pos1: current]) # -1 THEN r ← Rope.Substr[base: nodeRope, start: current, len: next - current]
ELSE IF current # 0 THEN r ← Rope.Substr[base: nodeRope, start: current]
ELSE r ← nodeRope;
s ← IO.RIS[r, s];
IO.SkipOver[s, IO.WhiteSpace];
IF s.EndOf[] THEN LOOP; -- line with just white space.
c ← s.GetChar[];
IF (c = '- OR c = '/) AND NOT s.EndOf[] AND (c = s.GetChar[]) THEN NULL -- is a comment
ELSE EXIT;
};
IF Rope.IsEmpty[r] THEN LOOP;
IF Rope.Find[s1: r, s2: user, case:
FALSE] # -1
AND SelectDate[viewer: viewer, loc: [node, current], rope: r]
THEN
{TiogaOps.SaveSelB[]; -- modify the last one, so user can have separate created and edited comments.
foundOne ← TRUE;
};
LOOP;
ENDLOOP;
IF foundOne
THEN
{TiogaOps.RestoreSelB[];
TiogaOps.Delete[];
TiogaOps.InsertTime[];
RETURN;
};
-- not found, insert new date string at top.
IF insertAt.node = NIL THEN RETURN; -- no comments seen
TiogaOps.SelectPoint[viewer: viewer, caret: insertAt];
insertRope ← Rope.Cat["Last Edited by: ", user, ", "];
IF current # 0
THEN
-- inserting inside of a node, or no node structure
{TiogaOps.InsertRope[Rope.Cat["-- ", insertRope]];
TiogaOps.InsertTime[];
TiogaOps.InsertRope["\n"];
}
ELSE
{
IF
NOT Rope.IsEmpty[TiogaOps.GetRope[insertAt.node]]
THEN
-- inserting in front of a non-comment node, e.g. the DIRECTORY.
TiogaOps.Break[];
TiogaOps.SetFormat[format: NIL];
TiogaOps.InsertRope[insertRope];
TiogaOps.InsertTime[];
TiogaOps.SetComment[];
};
};
ChangeEntry: TYPE = RECORD[rope, path: ROPE ← NIL, node: TiogaRef ← NIL, written: BOOL ← FALSE];
ChangeLogEntry: TYPE = RECORD[changed: LIST OF REF ChangeEntry, node: TiogaRef];
UpdateChangeLog:
PROC [viewer: Viewer, rootNode: TiogaRef, user:
ROPE, updateChangeLog: UpdateChange, insertPlaceHolders:
BOOL ←
FALSE, mergeWithPrevious: MergeChange]
RETURNS [changeLogNode: TiogaRef ←
NIL, restoreSel:
BOOL ←
TRUE] = {
end: Location = TiogaOps.LastLocWithin[TiogaOps.LastChild[rootNode]];
changed, tailOfChanged, lag: LIST OF REF ChangeEntry ← NIL;
s: IO.STREAM;
currentChangeLogEntry: REF ChangeLogEntry ← NARROW[TiogaOps.GetProp[n: rootNode, name: $ChangeLog]];
previousEntryNode: TiogaRef;
first: BOOL ← TRUE;
writeAll: BOOL ← TRUE;
anyChanges: BOOL ← FALSE;
CreateChangeEntry:
PROC [node: TiogaRef] = {
change: REF ChangeEntry;
node0, node1: TiogaRef;
entriesToBeMerged: BOOL ← FALSE;
IsADecl:
PROCEDURE [node: TiogaRef]
RETURNS[declName:
ROPE ←
NIL] = {
r, name: ROPE;
r ← TiogaOps.GetRope[node];
IF r.IsEmpty[] THEN RETURN[NIL];
s ← IO.RIS[r, s];
{
ENABLE
{
IO.EndOfStream => CONTINUE;
IO.SyntaxError => CONTINUE;
};
name ← IO.GetCedarToken[s];
IF Rope.Equal[IO.GetCedarToken[s], ":"] AND (Rope.Find[r, "{"] # -1 OR Rope.Find[r, "BEGIN"] # -1) THEN RETURN[name];
};
};
AlreadySeen:
PROCEDURE [node: TiogaRef]
RETURNS [
BOOL] = {
FOR l:
LIST
OF
REF ChangeEntry ← changed, l.rest
UNTIL l =
NIL
DO
IF l.first.node = NIL THEN entriesToBeMerged ← TRUE
ELSE IF l.first.node = node THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
depth: INT ← 0;
node1 ← node;
UNTIL node1 = rootNode
DO
name: ROPE;
IF TiogaOps.IsComment[node1] THEN NULL
ELSE IF change = NIL AND AlreadySeen[node1] THEN RETURN
ELSE IF (name ← IsADecl[node1]) = NIL THEN node0 ← node1 -- best entry so far
ELSE IF change = NIL THEN change ← NEW[ChangeEntry ← [node: node1, rope: name]]
ELSE
{IF change.path # NIL THEN change.path ← Rope.Concat[change.path, ", "];
change.path ← Rope.Cat[change.path, "local of ", name];
};
node1 ← TiogaOps.Parent[node1];
depth ← depth + 1;
ENDLOOP;
IF node0 #
NIL
AND change =
NIL
THEN
{s ← IO.RIS[TiogaOps.GetRope[node0]];
change ← NEW[ChangeEntry ← [node: node0, rope: IO.GetCedarToken[s ! IO.EndOfStream => CONTINUE]]];
};
IF change #
NIL
AND entriesToBeMerged
THEN
FOR l:
LIST
OF
REF ChangeEntry ← changed, l.rest
UNTIL l =
NIL
DO
IF l.first.node =
NIL
AND Rope.Equal[l.first.rope, change.rope]
AND Rope.Equal[l.first.path, change.path]
THEN
-- user has made a change corresponding to an entry from a previous change log that this change log is being merged with.
{l.first.node ← change.node;
change ← NIL;
EXIT;
};
ENDLOOP;
IF change #
NIL THEN
{
IF changed =
NIL
THEN
{changed ← LIST[change];
tailOfChanged ← changed;
}
ELSE
{tailOfChanged.rest ← LIST[change];
tailOfChanged ← tailOfChanged.rest;
};
};
};
IsDirty:
PROC [node: TiogaRef] = {
child: TiogaRef;
IF TiogaOps.IsDirty[node] THEN CreateChangeEntry[node]; -- note that must still look at children, because they may require a separate entry, e.g. in the case that there are changes to local procedures. Or, this node being a comment might not have caused an entry.
child ← TiogaOps.FirstChild[node];
UNTIL child =
NIL
DO
IsDirty[child];
child ← TiogaOps.Next[child];
ENDLOOP;
};
DeletePlaceHolders:
PROC = {
TiogaOps.SelectPoint[viewer: viewer, caret: [currentChangeLogEntry.node, 0]];
WHILE TiogaOps.NextPlaceholder[gotoend:
FALSE].found
DO
n: TiogaRef;
TiogaOps.Delete[];
n ← TiogaOps.GetCaret[].node;
IF Rope.IsEmpty[TiogaOps.GetRope[n]]
THEN
{TiogaOps.SelectNodes[viewer, n, n];
TiogaOps.Delete[];
};
ENDLOOP;
};
main body of UpdateChangeLog
IF mergeWithPrevious = newSession
AND currentChangeLogEntry #
NIL
THEN
-- start new session even if currently working on a session.
{currentChangeLogEntry ← NIL;
changed ← tailOfChanged ← NIL;
-- ClearBits[rootNode];
}
ELSE IF currentChangeLogEntry #
NIL
THEN
-- see if old change log is valid, and if so, initialize changed to appropriate value.
{IF TiogaOps.CompareNodeOrder[node1: rootNode, node2: currentChangeLogEntry.node] = disjoint THEN -- node was deleted -- GOTO NoLog;
IF NOT SelectDate[viewer: viewer, loc: [currentChangeLogEntry.node, 0], rope: TiogaOps.GetRope[currentChangeLogEntry.node]] THEN GOTO NoLog;
changed ← currentChangeLogEntry.changed;
TRUSTED {tailOfChanged ← LOOPHOLE[List.NthTail[LOOPHOLE[changed], -1]]};
EXITS
NoLog => currentChangeLogEntry ← NIL;
};
IF currentChangeLogEntry =
NIL
THEN {
-- no active change log. Is this one of those cases where we are going to create a new one or merge with a previous one?
FindLastChangeLog:
PROCEDURE
RETURNS [TiogaRef ←
NIL] = {
n: TiogaRef ← end.node;
WHILE n #
NIL
DO
-- find last non-empty comment node.
r: ROPE = TiogaOps.GetRope[n];
IF Rope.IsEmpty[r] THEN {n ← TiogaOps.Previous[n, rootNode]; LOOP};
IF NOT TiogaOps.IsComment[n] OR NOT SelectDate[viewer: viewer, loc: [n, 0], rope: r] THEN RETURN;
EXIT;
REPEAT
FINISHED => RETURN;
ENDLOOP;
n ← TiogaOps.FirstChild[n];
WHILE n #
NIL
DO
-- look at its children, see if there is a "changes to: " entry.
IF Rope.Find[TiogaOps.GetRope[n], "changes to: "] = 0 THEN RETURN[n];
n ← TiogaOps.Next[n];
REPEAT
FINISHED => RETURN;-- the last thing in the file did not look like a change log entry that was inserted by this package
ENDLOOP;
};
IfSoonEnough:
PROCEDURE
RETURNS [
BOOL] = {
nowInSeconds, thenInSeconds: LONG CARDINAL;
then: ROPE = ViewerTools.GetSelectionContents[];
TRUSTED {nowInSeconds ← System.SecondsSinceEpoch[Time.Current[]]};
thenInSeconds ← System.SecondsSinceEpoch[DateAndTime.Parse[then].dt];
RETURN[nowInSeconds - thenInSeconds < mergeInterval];
};
SELECT updateChangeLog
FROM
updateOnly => RETURN;
viaChangeLogButton => NULL; -- user clicked ChangeLog button. create one if not there
automaticallyCreateNew
=>
-- create a new one if looks like source file.
{n: TiogaRef ← end.node;
WHILE n # rootNode
DO
r: ROPE = TiogaOps.GetRope[n];
IF Rope.IsEmpty[r] OR TiogaOps.IsComment[n] THEN {n ← TiogaOps.Previous[n, rootNode]; LOOP};
IF Rope.Find[s1: r, s2: "END.", case: FALSE] # 0 THEN RETURN;
EXIT;
REPEAT
FINISHED => RETURN;
ENDLOOP;
};
addIfPrevious =>
-- add one if already one there of appropriate form.
IF (previousEntryNode ← FindLastChangeLog[]) = NIL THEN RETURN;
ENDCASE => ERROR;
IF mergeWithPrevious = newSession THEN previousEntryNode ← NIL
ELSE
IF updateChangeLog # addIfPrevious
THEN
-- if = addIfPrevious then FindLastChangeLog was already called
previousEntryNode ← FindLastChangeLog[];
IF previousEntryNode # NIL AND mergeWithPrevious = ifSoonEnough AND NOT IfSoonEnough[] THEN previousEntryNode ← NIL;
};
if we get to here, we are going to write or update a change log entry
IF previousEntryNode #
NIL
THEN
-- parse the entry.
{r: ROPE ← TiogaOps.GetRope[previousEntryNode];
change: REF ChangeEntry;
s ← IO.RIS[Rope.Substr[r, Rope.Find[r, ":"] + 1]];
WHILE (r ← IO.GetToken[s]) #
NIL
DO
change ← NEW[ChangeEntry ← [rope: r, written: TRUE]];
IF changed =
NIL
THEN
{changed ← LIST[change];
tailOfChanged ← changed;
}
ELSE
{tailOfChanged.rest ← LIST[change];
tailOfChanged ← tailOfChanged.rest;
};
IF Rope.Equal[r ← IO.GetToken[s], "("]
THEN
-- read "Local to"
{
rparProc: IO.BreakProc = {
RETURN[IF char = ') THEN sepr ELSE other];
};
change.path ← IO.GetToken[s, rparProc];
r ← IO.GetToken[s];
};
WHILE r #
NIL
DO
-- skip over any comments that the user may have inserted
IF Rope.Equal[r, ","] THEN EXIT;
r ← IO.GetToken[s];
ENDLOOP;
ENDLOOP;
TiogaOps.PutProp[
n: rootNode,
name: $ChangeLog,
value: currentChangeLogEntry ← NEW[ChangeLogEntry ← [changed: changed, node: TiogaOps.Parent[previousEntryNode]]]
];
};
writeAll ← (currentChangeLogEntry = NIL);
IsDirty[rootNode]; -- scan the tree structure looking for changes
lag ← NIL;
FOR l:
LIST
OF
REF ChangeEntry ← changed, l.rest
UNTIL l =
NIL
DO
-- now see if there were any new changes
r: ROPE ← l.first.rope;
IF NOT writeAll AND l.first.written THEN NULL
ELSE IF TiogaOps.CompareNodeOrder[node1: rootNode, node2: l.first.node] = disjoint
THEN
{IF lag = NIL THEN changed ← changed.rest ELSE lag.rest ← l.rest} -- remove the node
ELSE IF TiogaOps.IsComment[l.first.node] THEN ERROR -- shouldn't happen
ELSE {anyChanges ← TRUE; EXIT}; -- something found
lag ← l;
ENDLOOP;
-- tell the user what we decided to do
IF updateChangeLog = viaChangeLogButton
THEN
{IF NOT anyChanges AND mergeWithPrevious = ifSoonEnough THEN MessageWindow.Append["No (new) changes found.", TRUE]
ELSE
IF currentChangeLogEntry =
NIL
THEN
{MessageWindow.Append["Starting new edit session.", TRUE]; -- e.g. no changes, but user yellow clicked to start new session.
anyChanges ← TRUE; -- want to write out changeLog so that will know next time not to merge.
}
ELSE IF previousEntryNode # NIL THEN MessageWindow.Append["Continuing with previous edit session.", TRUE]
}
ELSE
IF anyChanges
THEN
MessageWindow.Append[
(IF currentChangeLogEntry = NIL THEN "Writing new change log entry."
ELSE IF previousEntryNode # NIL THEN "Merging changes with previous log entry."
ELSE "Updating current change log entry"),
TRUE
];
IF
NOT anyChanges
THEN
-- don't write anything out
{
IF currentChangeLogEntry #
NIL
THEN {
currentChangeLogEntry.changed ← changed;
changeLogNode ← currentChangeLogEntry.node; -- for return value
IF NOT insertPlaceHolders THEN DeletePlaceHolders[];
};
RETURN;
};
IF currentChangeLogEntry #
NIL
THEN
-- change the date, position caret at end and only write those that have changed since last edit
.
{TiogaOps.Delete[];
TiogaOps.InsertTime[];
IF insertPlaceHolders
THEN
{lastChild, n: TiogaRef;
lastChild ← TiogaOps.LastChild[currentChangeLogEntry.node];
IF lastChild # NIL THEN n ← TiogaOps.Previous[lastChild];
IF n #
NIL
THEN
{TiogaOps.SelectPoint[viewer: viewer, caret: TiogaOps.LastLocWithin[n]];
TiogaOps.InsertRope["more comments"];
}
ELSE
{TiogaOps.SelectPoint[viewer: viewer, caret: [lastChild, 0]];
TiogaOps.Break[];
TiogaOps.InsertRope["general comments"];
};
};
TiogaOps.SelectPoint[viewer: viewer, caret: TiogaOps.LastLocWithin[currentChangeLogEntry.node]]; -- move to end.
}
ELSE
-- creating a new one
{TiogaOps.SelectPoint[viewer: viewer, caret: end];
TiogaOps.PutProp[
n: rootNode,
name: $ChangeLog,
value: currentChangeLogEntry ← NEW[ChangeLogEntry ← [changed: NIL, node: TiogaOps.GetCaret[].node]]
];
IF NOT Rope.IsEmpty[TiogaOps.GetRope[end.node]] THEN TiogaOps.Break[];
TiogaOps.InsertRope["Edited on "];
TiogaOps.InsertTime[];
TiogaOps.InsertRope[Rope.Cat[", by ", user]];
TiogaOps.SetComment[];
TiogaOps.Break[];
TiogaOps.Nest[];
IF insertPlaceHolders
THEN
{TiogaOps.InsertRope["general comments"];
TiogaOps.SetComment[];
TiogaOps.Break[];
};
TiogaOps.SetComment[];
TiogaOps.InsertRope["changes to: "];
};
TiogaOps.SetLooks["r", caret];
FOR l:
LIST
OF
REF ChangeEntry ← changed, l.rest
UNTIL l =
NIL
DO
r: ROPE ← l.first.rope;
IF NOT writeAll AND l.first.written THEN LOOP;
IF
NOT Rope.IsEmpty[r]
THEN
{IF NOT first OR NOT writeAll THEN TiogaOps.InsertRope[", "];
TiogaOps.InsertRope[r];
IF
NOT Rope.IsEmpty[l.first.path]
THEN
{TiogaOps.SubtractLooks["r", caret];
TiogaOps.InsertRope[" ("];
TiogaOps.InsertRope[l.first.path];
TiogaOps.InsertRope[")"];
TiogaOps.AddLooks["r"];
};
IF insertPlaceHolders
THEN
{TiogaOps.InsertRope[""];
TiogaOps.SubtractLooks["r", caret];
TiogaOps.InsertRope[" "];
TiogaOps.AddLooks["r"];
TiogaOps.InsertRope[""];
};
first ← FALSE;
};
l.first.written ← TRUE;
ENDLOOP;
currentChangeLogEntry.changed ← changed;
changeLogNode ← currentChangeLogEntry.node; -- for return value
TiogaOps.Break[];
TiogaOps.UnNest[];
IF NOT insertPlaceHolders THEN DeletePlaceHolders[]
ELSE IF
NOT first
THEN
-- something inserted with placeHolders
{TiogaOps.SelectPoint[viewer: viewer, caret: [currentChangeLogEntry.node, 0]];
restoreSel ← IF TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN FALSE ELSE TRUE;
};
};
CreateChangeLog: Menus.ClickProc
-- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
[] ← UpdateForSave[viewer: NARROW[parent], updateLastEdited: FALSE, updateChangeLog: viaChangeLogButton, insertPlaceHolders: TRUE, mergeWithPrevious: IF mouseButton = yellow THEN newSession -- i.e. new entry -- ELSE IF mouseButton = blue THEN continuation -- i.e. merge regardless -- ELSE ifSoonEnough];
};
Swap Icons
ForkViewerOp:
PROC [proc:
PROC [parent: Viewer, fileName:
ROPE], parent: Viewer]
RETURNS [BOOL] = TRUSTED {
viewer: Viewer = ViewerTools.GetSelectedViewer[];
IF viewer=
NIL
OR (
NOT viewer.iconic)
OR (viewer.file=
NIL)
THEN RETURN[FALSE];
TEditInput.CloseEvent[];
ForkCall[proc, parent, viewer.file];
RETURN [TRUE -- N.B.!! --] };
StackBottom:
PROC
[proc:
PROC [parent: Viewer, fileName:
ROPE], parent: Viewer, fileName:
ROPE] = {
proc[parent, fileName ! ABORTED => CONTINUE] };
ForkCall:
PROC [proc:
PROC [parent: Viewer, fileName:
ROPE], parent: Viewer, fileName:
ROPE] =
TRUSTED {
Process.Detach[
FORK StackBottom[proc, parent, fileName]];
-- fork so can release input monitor without waiting for call to complete
Process.Pause[Process.MsecToTicks[250]];
-- HACK: wait 1/4 sec to let other process lock the tdd before we return
};
LoadIconicOp: TiogaOps.CommandProc = {
RETURN[FALSE, ForkViewerOp[LoadIconicFile, viewer]];
};
LoadIconicFile:
PUBLIC
PROC [parent: ViewerClasses.Viewer, fileName:
ROPE] = {
[] ← TiogaMenuOps.Load[parent, fileName];
};
OpenIconicOp: TiogaOps.CommandProc = {
RETURN [FALSE, ForkViewerOp[OpenIconicFile,viewer]];
};
OpenIconicFile:
PUBLIC
PROC [parent: ViewerClasses.Viewer, fileName:
ROPE] =
TRUSTED {
[] ← TEditDocuments2Impl.DoOpenFile[fileName, parent];
};
CloseAndOpenIconicOp: TiogaOps.CommandProc = {
RETURN [FALSE, ForkViewerOp[CloseAndOpenIconicFile,viewer]] };
CloseAndOpenIconicFile:
PUBLIC
PROC [parent: Viewer, fileName:
ROPE] = {
[] ← TiogaMenuOps.CloseAndOpen[parent, fileName];
ViewerOps.PaintViewer[viewer: ViewerOps.FindViewer[parent.name], hint: all]; -- work around
};
Remembering last search
targetOfLastSearch: ROPE;
should all be one procedure if TiogaCommandProc were right type.
SaveLastSearch:
PROC [viewer: Viewer, name:
ATOM]
RETURNS[quit:
BOOL ←
FALSE] = {
rope: ROPE = ViewerTools.GetSelectionContents[];
IF Rope.Length[rope] > 0 THEN targetOfLastSearch ← rope
ELSE
IF
NOT Rope.IsEmpty[targetOfLastSearch]
THEN {
found: BOOL;
SELECT name
FROM
$FindNext => found ← TiogaOps.FindText[viewer, targetOfLastSearch, forwards];
$FindAny => found ← TiogaOps.FindText[viewer, targetOfLastSearch, anywhere];
$FindPrev => found ← TiogaOps.FindText[viewer, targetOfLastSearch, backwards];
$FindNextWord => found ← TiogaOps.FindWord[viewer, targetOfLastSearch, forwards];
$FindAnyWord => found ← TiogaOps.FindWord[viewer, targetOfLastSearch, anywhere];
$FindPrevWord => found ← TiogaOps.FindWord[viewer, targetOfLastSearch, backwards];
$FindNextDef => found ← TiogaOps.FindDef[viewer, targetOfLastSearch, forwards];
$FindAnyDef => found ← TiogaOps.FindDef[viewer, targetOfLastSearch, anywhere];
$FindPrevDef => found ← TiogaOps.FindDef[viewer, targetOfLastSearch, backwards];
ENDCASE => ERROR;
quit ← TRUE;
IF
NOT found
THEN TRUSTED {
MessageWindow.Append[Rope.Concat[targetOfLastSearch, " not Found"], TRUE];
UserTerminal.BlinkDisplay[];
};
};
};
FindNext: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindNext];
};
FindAny: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindAny];
};
FindPrev: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindPrev];
};
FindNextWord: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindNextWord];
};
FindAnyWord: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindAnyWord];
};
FindPrevWord: TiogaOps.CommandProc = {
quit ← SaveLastSearch[viewer, $FindPrevWord];
};
FindNextDef: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindNextDef];
};
FindAnyDef: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindAnyDef];
};
FindPrevDef: TiogaOps.CommandProc = {
recordAtom ← FALSE;
quit ← SaveLastSearch[viewer, $FindPrevDef];
};
Initialization
updateLastEdited: BOOLEAN;
updateChangeLog: UpdateChange;
useLocks: BOOLEAN;
UpdateChange: TYPE = {automaticallyCreateNew, addIfPrevious, updateOnly, never, viaChangeLogButton};
MergeChange: TYPE = {continuation, newSession, ifSoonEnough};
mergeInterval: LONG CARDINAL ← LONG[3600] * 24; -- if two edits within this time, then consider edit as continuation of previous session, expressed in number of seconds
SetDefaultSwitches: UserProfile.ProfileChangedProc = {
r: ROPE;
updateLastEdited ← UserProfile.Boolean["UpdateLastEdited", TRUE];
r ← UserProfile.Token["UpdateChangeLog", "AddIfPrevious"];
updateChangeLog ← (
SELECT
TRUE
FROM
Rope.Equal[r, "CreateNew", FALSE] => automaticallyCreateNew,
Rope.Equal[r, "AddIfPrevious", FALSE] => addIfPrevious,
Rope.Equal[r, "UpdateOnly", FALSE] => updateOnly,
ENDCASE => addIfPrevious);
IF updateChangeLog # never
THEN {
IF Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: "ChangeLog"] =
NIL
THEN
Menus.AppendMenuEntry[
menu: TiogaMenuOps.tiogaMenu,
entry: Menus.CreateEntry[name: "ChangeLog", proc: CreateChangeLog, documentation: "Create/update change log entry"],
line: 0];
};
useLocks ← UserProfile.Boolean["NewStuff.UseLocks", TRUE];
};
UserProfile.CallWhenProfileChanges[SetDefaultSwitches];
TiogaOps.RegisterAbbrevFailedProc[Expand];
TiogaOps.RegisterCommand[name: $CtrlNextPlaceholder, proc: CtrlNextPlaceholder];
TiogaOps.RegisterCommand[name: $RedSave, proc: RedSave];
TiogaOps.RegisterCommand[$Load, LoadIconicOp];
TiogaOps.RegisterCommand[$Open, OpenIconicOp];
TiogaOps.RegisterCommand[$CloseAndOpen, CloseAndOpenIconicOp];
TiogaOps.RegisterCommand[$FindNext, FindNext];
TiogaOps.RegisterCommand[$FindAny, FindAny];
TiogaOps.RegisterCommand[$FindPrev, FindPrev];
TiogaOps.RegisterCommand[$FindNextWord, FindNextWord];
TiogaOps.RegisterCommand[$FindAnyWord, FindAnyWord];
TiogaOps.RegisterCommand[$FindPrevWord, FindPrevWord];
TiogaOps.RegisterCommand[$FindNextDef, FindNextDef];
TiogaOps.RegisterCommand[$FindAnyDef, FindAnyDef];
TiogaOps.RegisterCommand[$FindPrevDef, FindPrevDef];
-- TiogaOps.RegisterCommand[name: $NextPlaceholder, proc: NextPlaceholder];
END.
Edited on December 1, 1982 11:29 pm, by Teitelman
fixed bug in addIfPrevious, createNew so that if there were no non-comment nodes, would fail
fixed addIfPrevious to search through all children for "changes to"
use TiogaExtraOps.CompareLocOrder rather than SelectBranches
fix middle button continuation to add a new comments field.
changes to: UpdateChangeLog
Edited on December 2, 1982 1:29 pm, by Teitelman
fixed bug causing two appearances of ChangeLog.
fixed control-E not to stop scanning on *. Add PROGRAM name in case no .
changes to: UpdateLastEdited, ChangeLogEntry, UpdateChangeLog, SetDefaultSwitches, Expand0
Edited on December 2, 1982 3:09 pm, by Teitelman
changes to: Expand0
Edited on December 3, 1982 12:57 pm, by Teitelman
fixed bug relating to naked CR in non-node structure file causing loop in UpdateLastEdit
changes to: UpdateLastEdited
Edited on December 14, 1982 1:53 pm, by Teitelmann, SelectDate
Edited on December 14, 1982 2:05 pm, by Teitelman
fixed updatedate to keep looking after it finds an appropriate comment, and to change the last one it finds.
changes to: UpdateLastEdited, DIRECTORY
Edited on December 20, 1982 5:03 pm, by Teitelman
fixed FindLastChangeLog to stop on NIL node, rather than rootNode, because if the last node happens to have children, cauused infinite loop.
changes to: UpdateChangeLog
Edited on December 22, 1982 12:49 pm, by Teitelman
changes to: Expand0, UpdateLastEdited, TokenProc (local of IsADecl, local of CreateChangeEntry, local of UpdateChangeLog), IsADecl (local of CreateChangeEntry, local of UpdateChangeLog), CreateChangeEntry (local of UpdateChangeLog), UpdateChangeLog, IMPORTS, Expand
Edited on January 25, 1983 11:10 pm, by Teitelman
changes to: UpdateForSave
Edited on January 28, 1983 6:51 pm, by Teitelman
changes to: UpdateChangeLog, RedSave
Edited on February 1, 1983 9:32 pm, by Teitelman
changes to: RedSave, CloseAndOpenIconicFile, SetDefaultSwitches, IMPORTS, PrintStars (local of RedSave), Stars (local of RedSave)
Edited on February 9, 1983 5:17 pm, by Teitelman
changes to: RedSave, Stars (local of RedSave), sessionLog, Stars (local of RedSave)
Edited on March 10, 1983 3:02 am, by Teitelman
changes to: IMPORTS, RedSave
Edited on March 31, 1983 5:03 pm, by Teitelman
changes to: SaveLastSearch, Expand
Edited on May 9, 1983 6:13 pm, by Teitelman
changes to: IMPORTS, RedSave, proc (local of RedSave), proc (local of RedSave)
Edited on May 11, 1983 2:51 pm, by Teitelman
added catch phrase for SyntaxError to IsADecl. User had clicked changelog on a file that did not contain cedar and GetCedarToken blew up.
changes to: IsADecl (local of CreateChangeEntry, local of UpdateChangeLog)