<> 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; <> Viewer: TYPE = ViewerClasses.Viewer; Location: TYPE = TiogaOps.Location; TiogaRef: TYPE = TiogaOpsDefs.Ref; <> 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; }; <> 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]; }; <> 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] = { <> 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; }; <
> 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 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]; }; <> 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]; <> 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]; <> }; <> targetOfLastSearch: ROPE; <> 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]; }; <> 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. <> <> <> <> <> <> <<>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <<>> <> <> <> <<>>