DIRECTORY Ascii USING [Upper], BasicTime USING [earliestGMT, GMT, Now, Period, Unpack], Convert USING [Error, RopeFromInt, TimeFromRope], FS USING [Error, StreamOpen], IO USING [BreakProc, Close, EndOf, EndOfStream, Error, GetCedarTokenRope, GetChar, GetTime, GetTokenRope, PutF, PutFR, RIS, SkipWhitespace, STREAM, TokenProc], Menus USING [AppendMenuEntry, ClickProc, CreateEntry, FindEntry, MenuEntry, MenuProc, ReplaceMenuEntry], MessageWindow USING [Append], Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, Index, IsEmpty, Length, Match, Replace, ROPE, Run, SkipOver, SkipTo, Substr], TiogaOps USING [AddLooks, BackSpace, BackWord, Break, CallWithLocks, CaretAfter, CaretBefore, CaretOnly, ClearLooks, CommandProc, CompareNodeOrder, Delete, DeleteNextCharacter, FetchLooks, FirstChild, GetCaret, GetProp, GetRope, GetSelection, GetTextKey, GoToNextCharacter, GoToPreviousNode, InsertRope, InsertTime, IsComment, IsDirty, LastChild, LastLocWithin, Location, Nest, Next, NextPlaceholder, Parent, Previous, PutProp, PutTextKey, Ref, RegisterCommand, RestoreSelA, RestoreSelB, Root, SaveSelA, SaveSelB, SelectMatchingBrackets, SelectNodes, SelectPoint, SetComment, SetFormat, SetLooks, SetSelection, SetSelectionLooks, StepBackward, StepForward, SubtractLooks, UnNest, ViewerDoc], TiogaOpsDefs USING [Ref], TiogaMenuOps USING [tiogaMenu], UserCredentials USING [Get], UserProfile USING [Boolean, CallWhenProfileChanges, Line, ProfileChangedProc, Token], ViewerClasses USING [Viewer], ViewerTools USING [GetSelectionContents]; EditorComfortsAImpl: CEDAR MONITOR IMPORTS Ascii, BasicTime, Convert, FS, IO, Menus, MessageWindow, Rope, TiogaMenuOps, TiogaOps, UserCredentials, UserProfile, ViewerTools = BEGIN Location: TYPE = TiogaOps.Location; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TiogaRef: TYPE = TiogaOps.Ref; Viewer: TYPE = ViewerClasses.Viewer; LogChange: ENTRY PROC [name: ROPE, description: ROPE] = { ENABLE UNWIND => NULL; outName: ROPE _ UserProfile.Token["EditorComforts.ChangesLog", NIL]; IF outName # NIL THEN { out: STREAM _ NIL; out _ FS.StreamOpen[fileName: outName, accessOptions: append ! FS.Error => IF error.group # bug THEN CONTINUE]; IF out = NIL THEN out _ FS.StreamOpen[fileName: outName, accessOptions: create, keep: 4 ! FS.Error => IF error.group # bug THEN GO TO nope]; IF description = NIL THEN IO.PutF[out, "\nChanged: %g, %t\n", [rope[name]], [time[BasicTime.Now[]]]] ELSE IO.PutF[out, "\nChanged: %g\n\t%g", [rope[name]], [rope[description]]]; IO.Close[out]; EXITS nope => {}; }; }; CtrlNextPlaceholder: TiogaOps.CommandProc = { 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]]; }; IF TiogaOps.SelectMatchingBrackets['[, ']] THEN [, start, end] _ TiogaOps.GetSelection[] ELSE { r: ROPE = TiogaOps.GetRope[start.node]; len: INT _ Rope.Length[r]; IF len > 1 AND start.node = end.node AND start.where < len AND Rope.Fetch[r, start.where] = '\001 AND end.where < len 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 loc.where IN [2..Rope.Length[r]+2) AND Rope.Fetch[r, loc.where - 2] = ': THEN TiogaOps.BackWord[]; loc _ TiogaOps.GetCaret[]; r _ TiogaOps.GetRope[loc.node]; SELECT TRUE FROM loc.where NOT IN [1..Rope.Length[r]) => {}; Rope.Fetch[r, loc.where - 1] # '[ => TiogaOps.BackSpace[2]; Rope.Fetch[r, loc.where] = ', => TiogaOps.DeleteNextCharacter[2]; ENDCASE; 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[]; }; }; DoInner[Cleanup1, viewer]; }; GetNiceUser: PROC RETURNS [ROPE] = { user: ROPE _ UserCredentials.Get[].name; i: INT _ Rope.Find[user, "."]; IF i # -1 THEN user _ Rope.Substr[base: user, len: i]; IF Rope.IsEmpty[user] THEN RETURN [user]; user _ Rope.Replace[user, 0, 1, Rope.FromChar[Ascii.Upper[Rope.Fetch[user, 0]]]]; RETURN [user]; }; GenLastEdited: PROC RETURNS [ROPE] = { r: ROPE _ UserProfile.Line["EditorComforts.LastEdited", NIL]; IF r = NIL THEN r _ Rope.Concat[GetNiceUser[], ","]; RETURN [IO.PutFR["%g %g", [rope[r]], [time[BasicTime.Now[]]]]]; }; DoInner: PROC [inner: PROC [root: TiogaRef], viewer: Viewer] = { root: TiogaRef = IF viewer # NIL THEN TiogaOps.ViewerDoc[viewer] ELSE NIL; IF debugging THEN inner[root] ELSE TiogaOps.CallWithLocks[inner, root]; }; RedSave: TiogaOps.CommandProc = { changeLogNode: TiogaRef; description: ROPE; 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; }; LogChange[viewer.name, description]; }; UpdateForSave: PROC [viewer: Viewer, updateLastEdited: BOOL, updateChangeLog: UpdateChange, insertPlaceHolders: BOOL, mergeWithPrevious: MergeChange] RETURNS[changeLogNode: TiogaRef] = { inner: PROC [root: TiogaRef] = { user: ROPE _ GetNiceUser[]; restoreSel: BOOL _ TRUE; TiogaOps.SaveSelA[]; user _ UserProfile.Line["EditorComforts.LastEdited", user]; IF updateLastEdited THEN UpdateLastEdited[viewer, root, user]; IF updateChangeLog # never THEN [changeLogNode, restoreSel] _ UpdateChangeLog[viewer, root, user, updateChangeLog, insertPlaceHolders, mergeWithPrevious]; IF restoreSel THEN TiogaOps.RestoreSelA[]; }; DoInner[inner, viewer]; }; monthList: LIST OF ROPE = LIST [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; FindDate: PROC [r: ROPE] RETURNS [i, j: INT] = { len: INT = Rope.Length[r]; IF len > maxDateRope THEN RETURN [-1, -1]; FOR months: LIST OF ROPE _ monthList, months.rest UNTIL months = NIL DO start: INT _ 0; WHILE start < len DO i _ Rope.Index[r, start, months.first, TRUE]; IF i = len THEN EXIT; -- not this month SELECT TRUE FROM (j _ Rope.Find[r, "ST", i]) # -1 => {j _ j + 1}; (j _ Rope.Find[r, "DT", i]) # -1 => {j _ j + 1}; (j _ Rope.Find[r, " am", i, FALSE]) # -1 => {j _ j + 2}; (j _ Rope.Find[r, " pm", i, FALSE]) # -1 => {j _ j + 2}; ENDCASE => EXIT; -- no plausible ending! [] _ Convert.TimeFromRope[Rope.Substr[r, i, j - i + 1] ! Convert.Error => { start _ start + 3; LOOP}]; RETURN[i, j]; ENDLOOP; ENDLOOP; RETURN[-1, -1]; }; SelectDate: PROC [viewer: Viewer, loc: Location, rope: ROPE] RETURNS [BOOL] = { 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] = { s: STREAM _ NIL; next: INT _ 0; -- beginning of next line node: TiogaRef _ NIL; nodeRope: ROPE _ NIL; current: INT _ 0; r: ROPE _ NIL; foundOne: BOOL _ FALSE; insertAt: Location _ [NIL, 0]; -- where to insert the last-edited comment. newInsertAtNode: BOOL _ TRUE; -- please assign the next node to insertAt foundLastEditedNode: BOOL _ FALSE; -- we found a last-edited comment TiogaOps.SaveSelA[]; DO SELECT TRUE FROM node = NIL => { node _ TiogaOps.FirstChild[rootNode]; nodeRope _ TiogaOps.GetRope[node]; TiogaOps.SelectPoint[viewer: viewer, caret: [node: node, where: 0]]; next _ -1; }; next = -1 => { node _ TiogaOps.StepForward[node]; IF node = NIL THEN EXIT; IF newInsertAtNode AND insertAt.node # node AND NOT Rope.IsEmpty[nodeRope] THEN insertAt _ [node, 0]; IF foundLastEditedNode THEN newInsertAtNode _ FALSE; nodeRope _ TiogaOps.GetRope[node]; current _ 0; } ENDCASE => { current _ next + 1; IF newInsertAtNode AND NOT Rope.IsEmpty[r] THEN insertAt _ [node, current]; IF foundLastEditedNode THEN newInsertAtNode _ FALSE; }; IF TiogaOps.GetProp[n: node, name: $Mark] # NIL THEN GO TO done; IF TiogaOps.IsComment[node] THEN r _ nodeRope ELSE { 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; IF ~IsMesaComment[r, s] THEN EXIT; }; IF Rope.IsEmpty[r] THEN EXIT; IF Rope.Find[s1: r, s2: user, case: FALSE] # -1 AND SelectDate[viewer: viewer, loc: [node, current], rope: r] THEN { TiogaOps.SaveSelB[]; foundOne _ TRUE; }; IF ~foundOne AND Rope.Find[s1: r, s2: "Last Edited by:"] # -1 THEN newInsertAtNode _ foundLastEditedNode _ TRUE; ENDLOOP; IF foundOne THEN { TiogaOps.RestoreSelB[]; TiogaOps.Delete[]; TiogaOps.InsertTime[]; GO TO done; }; IF insertAt.node = NIL THEN RETURN; -- no comments seen TiogaOps.SelectPoint[viewer: viewer, caret: insertAt]; IF insertAt.where = 0 THEN { empty: BOOL _ Rope.IsEmpty[TiogaOps.GetRope[insertAt.node]]; TiogaOps.Break[]; IF empty THEN TiogaOps.GoToPreviousNode[]; }; TiogaOps.CaretOnly[]; TiogaOps.ClearLooks[]; IF TiogaOps.IsComment[TiogaOps.StepBackward[insertAt.node]] THEN { ForceToLevel[1, TRUE]; TiogaOps.InsertRope[GenLastEdited[]]; } ELSE { TiogaOps.InsertRope["-- "]; TiogaOps.InsertRope[GenLastEdited[]]; IF insertAt.where # 0 THEN TiogaOps.InsertRope["\n"]; }; GO TO done; EXITS done => TiogaOps.RestoreSelA[]; }; IsMesaComment: PROC[rope: ROPE, s: STREAM] RETURNS[BOOL] = { ENABLE IO.EndOfStream => GO TO nope; c: CHAR; s _ IO.RIS[rope, s]; [] _ s.SkipWhitespace[flushComments: FALSE]; IF s.EndOf[] THEN RETURN[TRUE]; -- line with just white space. c _ s.GetChar[]; RETURN[(c = '- OR c = '/) AND NOT s.EndOf[] AND (c = s.GetChar[])]; EXITS nope => RETURN [FALSE]; }; ChangeEntry: TYPE = RECORD[ rope: ROPE, path: ROPE _ NIL, node: TiogaRef, 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: 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: PROC [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 => GO TO nope; IO.Error => IF ec = SyntaxError THEN GO TO nope; }; name _ IO.GetCedarTokenRope[s].token; IF Rope.Equal[IO.GetCedarTokenRope[s].token, ":"] AND (Rope.Find[r, "{"] # -1 OR Rope.Find[r, "BEGIN"] # -1) THEN RETURN[name]; EXITS nope => {}; }; }; AlreadySeen: PROC [node: TiogaRef] RETURNS [BOOL] = { FOR l: LIST OF REF ChangeEntry _ changed, l.rest UNTIL l = NIL DO SELECT l.first.node FROM NIL => entriesToBeMerged _ TRUE; node => RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN[FALSE]; }; depth: INT _ 0; node1 _ node; UNTIL node1 = rootNode DO name: ROPE; SELECT TRUE FROM TiogaOps.IsComment[node1] => {}; change = NIL AND AlreadySeen[node1] => RETURN; (name _ IsADecl[node1]) = NIL => node0 _ node1; change = NIL => change _ NEW[ChangeEntry _ [node: node1, rope: name]]; ENDCASE => { 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.GetCedarTokenRope[s ! IO.Error, IO.EndOfStream => CONTINUE].token]]; }; 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 { 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; }; WITH TiogaOps.GetProp[n: rootNode, name: $ChangeLog] SELECT FROM entry: REF ChangeLogEntry => currentChangeLogEntry _ entry; ENDCASE; SELECT TRUE FROM mergeWithPrevious = newSession AND currentChangeLogEntry # NIL => { currentChangeLogEntry _ NIL; changed _ tailOfChanged _ NIL; }; currentChangeLogEntry # NIL => { 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; tailOfChanged _ changed _ currentChangeLogEntry.changed; IF tailOfChanged # NIL THEN DO rest: LIST OF REF ChangeEntry _ tailOfChanged.rest; IF rest = NIL THEN EXIT; tailOfChanged _ rest; ENDLOOP; EXITS NoLog => currentChangeLogEntry _ NIL; }; ENDCASE; IF currentChangeLogEntry = NIL THEN { FindLastChangeLog: PROC RETURNS [TiogaRef _ NIL] = { n: TiogaRef _ end.node; r: ROPE _ NIL; DO IF n = NIL THEN RETURN; r _ TiogaOps.GetRope[n]; SELECT TRUE FROM Rope.IsEmpty[r] => {}; NOT TiogaOps.IsComment[n] => RETURN; SelectDate[viewer: viewer, loc: [n, 0], rope: r] => EXIT; ENDCASE => RETURN; n _ TiogaOps.Previous[n, rootNode]; ENDLOOP; }; IfSoonEnough: PROC RETURNS [BOOL] = { nowInSeconds, thenInSeconds: LONG CARDINAL; then: ROPE = ViewerTools.GetSelectionContents[]; time: BasicTime.GMT _ IO.GetTime[IO.RIS[then] ! IO.Error => GO TO bogus; IO.EndOfStream => GO TO bogus]; nowInSeconds _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]; thenInSeconds _ BasicTime.Period[BasicTime.earliestGMT, time]; RETURN[nowInSeconds - thenInSeconds < mergeInterval]; EXITS bogus => RETURN [FALSE]; }; SELECT updateChangeLog FROM updateOnly => RETURN; viaButton => NULL; createNew => { n: TiogaRef _ end.node; WHILE n # NIL DO IF NOT TiogaOps.IsComment[n] THEN { r: ROPE = TiogaOps.GetRope[n]; SELECT TRUE FROM Rope.IsEmpty[r] => {}; Rope.Match["*END.*", r], Rope.Match["*}.*", r] => GO TO foundCode; ENDCASE => RETURN; }; n _ TiogaOps.Previous[n, rootNode]; ENDLOOP; RETURN; EXITS foundCode => {}; }; addIfPrevious => IF (previousEntryNode _ FindLastChangeLog[]) = NIL THEN RETURN; ENDCASE => RETURN; SELECT TRUE FROM mergeWithPrevious = newSession => previousEntryNode _ NIL; updateChangeLog # addIfPrevious => previousEntryNode _ FindLastChangeLog[]; ENDCASE; IF previousEntryNode # NIL AND mergeWithPrevious = ifSoonEnough AND NOT IfSoonEnough[] THEN previousEntryNode _ NIL; }; IF previousEntryNode # NIL THEN { r: ROPE _ TiogaOps.GetRope[previousEntryNode]; change: REF ChangeEntry; s _ IO.RIS[Rope.Substr[r, Rope.Find[r, ":"] + 1]]; WHILE ~s.EndOf[] DO r _ IO.GetTokenRope[s ! IO.EndOfStream => EXIT].token; change _ NEW[ChangeEntry _ [rope: r, node: NIL, written: TRUE]]; IF changed = NIL THEN { changed _ LIST[change]; tailOfChanged _ changed; } ELSE { tailOfChanged.rest _ LIST[change]; tailOfChanged _ tailOfChanged.rest; }; r _ IO.GetTokenRope[s ! IO.EndOfStream => EXIT].token; IF Rope.Equal[r, "("] THEN { rparProc: IO.BreakProc = { RETURN[IF char = ') THEN sepr ELSE other]; }; change.path _ IO.GetTokenRope[s, rparProc ! IO.EndOfStream => EXIT].token; r _ IO.GetTokenRope[s ! IO.EndOfStream => EXIT].token; }; WHILE r # NIL DO -- skip over any comments that the user may have inserted IF Rope.Equal[r, ","] THEN EXIT; r _ IO.GetTokenRope[s ! IO.EndOfStream => EXIT].token; 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 r: ROPE _ l.first.rope; SELECT TRUE FROM NOT writeAll AND l.first.written => {}; TiogaOps.CompareNodeOrder[node1: rootNode, node2: l.first.node] = disjoint => { IF lag = NIL THEN changed _ changed.rest ELSE lag.rest _ l.rest; }; TiogaOps.IsComment[l.first.node] => RETURN; ENDCASE => {anyChanges _ TRUE; EXIT}; lag _ l; ENDLOOP; IF updateChangeLog = viaButton THEN { SELECT TRUE FROM NOT anyChanges AND mergeWithPrevious = ifSoonEnough => MessageWindow.Append["No (new) changes found.", TRUE]; currentChangeLogEntry = NIL => { 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. }; previousEntryNode # NIL => MessageWindow.Append["Continuing with previous edit session.", TRUE]; ENDCASE; } 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 { IF currentChangeLogEntry # NIL THEN { currentChangeLogEntry.changed _ changed; changeLogNode _ currentChangeLogEntry.node; -- for return value IF NOT insertPlaceHolders THEN DeletePlaceHolders[]; }; RETURN; }; IF currentChangeLogEntry # NIL THEN { 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 { 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[]; ForceToLevel[0, TRUE]; TiogaOps.InsertRope[GenLastEdited[]]; 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[]; SELECT TRUE FROM NOT insertPlaceHolders => DeletePlaceHolders[]; NOT first => { TiogaOps.SelectPoint[viewer: viewer, caret: [currentChangeLogEntry.node, 0]]; restoreSel _ IF TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN FALSE ELSE TRUE; }; ENDCASE; }; CreateChangeLog: Menus.MenuProc = { [] _ UpdateForSave[viewer: NARROW[parent], updateLastEdited: FALSE, updateChangeLog: viaButton, insertPlaceHolders: TRUE, mergeWithPrevious: IF mouseButton = yellow THEN newSession -- i.e. new entry -- ELSE IF mouseButton = blue THEN continuation -- i.e. merge regardless -- ELSE ifSoonEnough]; }; AddCopyrightProc: Menus.MenuProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; empty: BOOL _ TRUE; inner: PROC [root: TiogaRef] = { nodesSearched: INT _ 0; insertPlain: BOOL _ TRUE; contents: ROPE _ NIL; year: ROPE _ Convert.RopeFromInt[BasicTime.Unpack[BasicTime.Now[]].year]; FOR node: TiogaRef _ root, TiogaOps.StepForward[node] WHILE node # NIL DO copyPos: INT _ Rope.Find[contents _ Rope.Substr[TiogaOps.GetRope[node], 0, 1024], "Copyright "]; IF TiogaOps.IsComment[node] THEN insertPlain _ FALSE; SELECT TRUE FROM copyPos >= 0 => { pos: INT _ copyPos+9; IF Rope.Run[contents, pos, " c "]=3 THEN { TiogaOps.SetSelection[viewer: viewer, start: [node, pos], end: [node, pos+2], pendingDelete: TRUE]; TiogaOps.SetSelectionLooks[]; TiogaOps.InsertRope[" Σ "]; }; contents _ Rope.Substr[contents, 0, Rope.SkipTo[contents, pos, "\n"]]; IF Rope.Find[contents, year, pos] < 0 THEN { lastNum: INT _ 0; FOR i: INT IN [pos..Rope.Length[contents]) DO SELECT Rope.Fetch[contents, i] FROM '\n => EXIT; IN ['0..'9] => lastNum _ i+1; ENDCASE; ENDLOOP; IF lastNum > 0 THEN { TiogaOps.SetSelection[viewer, [node, lastNum], [node, lastNum]]; TiogaOps.ClearLooks[caret]; TiogaOps.InsertRope[", "]; TiogaOps.InsertRope[year]; }; }; RETURN; }; (nodesSearched _ nodesSearched + 1) > maxNodesToSearch => { insertPlain _ FALSE; EXIT; }; ENDCASE; ENDLOOP; FOR node: TiogaRef _ root, TiogaOps.StepForward[node] WHILE node # NIL DO len: INT _ Rope.Length[contents _ TiogaOps.GetRope[node]]; IF TiogaOps.IsComment[node] THEN insertPlain _ FALSE; IF len < 32 AND Rope.SkipOver[contents, 0, " \n\t"] = len THEN { LOOP}; empty _ FALSE; TiogaOps.SelectNodes[viewer, node, node]; EXIT; ENDLOOP; IF empty THEN TiogaOps.SelectNodes[viewer, root, root]; IF insertPlain THEN { pos: INT _ Rope.SkipTo[contents, 1, "\n"] + 1; notice: ROPE _ Rope.Cat["-- Copyright (C) ", year, " by Xerox Corporation. All rights reserved.\n"]; TiogaOps.GoToNextCharacter[pos]; TiogaOps.ClearLooks[caret]; TiogaOps.InsertRope[notice]; } ELSE { notice: ROPE _ Rope.Cat["Copyright Σ ", year, " by Xerox Corporation. All rights reserved."]; TiogaOps.CaretAfter[]; TiogaOps.Break[]; ForceToLevel[1, TRUE]; TiogaOps.InsertRope[notice]; }; }; DoInner[inner, viewer]; }; ForceToLevel: PROC [n: NAT _ 0, setup: BOOL] = { level: NAT _ GetLevel[TiogaOps.GetSelection[].start.node]; SELECT level FROM < n => THROUGH [level..n) DO TiogaOps.Nest[]; ENDLOOP; > n => THROUGH [n..level) DO TiogaOps.UnNest[]; ENDLOOP; ENDCASE; IF setup THEN { TiogaOps.SetFormat[nodeFormat]; TiogaOps.ClearLooks[caret]; TiogaOps.SetComment[]; }; }; GetLevel: PROC [ref: TiogaRef] RETURNS [level: NAT _ 0] = { IF ref # NIL THEN { root: TiogaRef _ TiogaOps.Root[ref]; IF ref # root THEN DO ref _ TiogaOps.Parent[ref]; IF ref = root THEN RETURN; level _ level + 1; ENDLOOP; }; }; debugging: BOOL _ FALSE; updateLastEdited: BOOL _ TRUE; updateChangeLog: UpdateChange _ never; UpdateChange: TYPE = {createNew, addIfPrevious, updateOnly, never, viaButton}; MergeChange: TYPE = {continuation, newSession, ifSoonEnough}; mergeInterval: LONG CARDINAL _ LONG[3600] * 24; SetDefaultSwitches: UserProfile.ProfileChangedProc = { ENABLE UNWIND => NULL; r: ROPE _ UserProfile.Token["EditorComforts.UpdateChangeLog", "AddIfPrevious"]; updateLastEdited _ UserProfile.Boolean["EditorComforts.UpdateLastEdited", TRUE]; updateChangeLog _ (SELECT TRUE FROM Rope.Equal[r, "CreateNew", FALSE] => createNew, Rope.Equal[r, "AddIfPrevious", FALSE] => addIfPrevious, Rope.Equal[r, "UpdateOnly", FALSE] => updateOnly, ENDCASE => never); }; InstallMenuButton: PROC [name: ROPE, proc: Menus.MenuProc] = { old: Menus.MenuEntry = Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name]; new: Menus.MenuEntry = Menus.CreateEntry[name: name, proc: proc]; IF old = NIL THEN Menus.AppendMenuEntry[TiogaMenuOps.tiogaMenu, new] ELSE Menus.ReplaceMenuEntry[TiogaMenuOps.tiogaMenu, old, new]; }; Init: PROC = { InstallMenuButton["(C)", AddCopyrightProc]; InstallMenuButton["Log", CreateChangeLog]; UserProfile.CallWhenProfileChanges[SetDefaultSwitches]; TiogaOps.RegisterCommand[name: $CtrlNextPlaceholder, proc: CtrlNextPlaceholder]; TiogaOps.RegisterCommand[name: $RedSave, proc: RedSave]; }; maxNodesToSearch: INT _ 6; minNodesForFormat: INT _ 3; nodeFormat: ROPE _ "code"; maxDateRope: NAT _ 256; Init[]; END. rEditorComfortsAImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. original author: Teitelman, May 11, 1983 2:53 pm Maxwell, January 25, 1984 7:59:03 am PST Russ Atkinson (RRA) August 25, 1986 12:59:54 pm PDT Tim Diebert: January 13, 1986 11:39:27 am PST Doug Wyatt, December 10, 1986 11:16:01 am PST Types Monitored Data & Entry procedures We need to open up the file named and append to it. There is a log file, so we can write to it. Control-Next [viewer: Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE] + 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 deletes the name: not first argument, delete preceding space and comma was first argument, but not last argument, delete next space and comma Save operations: updating date, changelog 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. ... returns first and last index in where a valid date occurs; [-1, -1] indicates no valid date. month may conflict with someone's name, so skip this and try further on loc is the location corresponding to rope. In most cases, where is 0 and r is GetRope. If rope contains a date, this procedure selects the date and returns TRUE. Otherwise returns FALSE. Enumerate the nodes and the lines with each node. Look for the user's name followed by the date. (There may be more than one; take the last one.) If there is no such thing, then we want to insert a new last-edited comment after the final last-edited comment, or after all of the comments. first time just exhausted a node try the next line check to see if it is a mesa comment (begins with two -'s or /'s) modify the last one, so user can have separate created and edited comments. We just have a date to update (the most frequent case). not found, insert new date string at top. inserting before a node inserting inside of a node, or no node structure user has made a change corresponding to an entry from a previous change log that this change log is being merged with. main body of UpdateChangeLog First, get the current change log entry from the Tioga property. Ignore it unless it is not NIL and it is the right type. start new session even if currently working on a session. 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? find last non-empty comment node. user clicked ChangeLog button. create one if not there create a new one if looks like source file. add one if already one there of appropriate form. if we get to here, we are going to write or update a change log entry read "Local to" now see if there were any new changes tell the user what we decided to do don't write anything out change the date, position caret at end and only write those that have changed since last edit. creating a new one something inserted with placeHolders [parent: REF, clientData: REF, mouseButton: MouseButton, shift: BOOL, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift: BOOL, control: BOOL] There is a "Copyright" message in this node, so we should try to update the year. We try to insert the year after the last year mentioned if the current year is not already present. Replace obsolete math font symbol with proper Xerox character code. don't look farther than the next end-of-line for the year The current year is not here. Insert the current year just after the last number. This is not the greatest algorithm, but it will have to do until someone brighter comes along. At this point we need to add a new copyright notice. We do it in plain text if the document does not look like it has significant node structure, otherwise we do it using tioga formatting. no substantive contents in this node, so skip it Insert the plain text message Insert the formatted text message Initialization if two edits within this time, then consider edit as continuation of previous session, expressed in number of seconds plainTextMessage: ROPE _ "-- Copyright (C) 1985 by Xerox Corporation. All rights reserved.\n"; tiogaMessage: ROPE _ "Copyright c 1985 by Xerox Corporation. All rights reserved."; In FindDate, ropes that are longer than this length are assumed to NOT have embedded dates. This avoids searches of ludicrous length. 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 GetCedarTokenRope blew up. changes to: IsADecl (local of CreateChangeEntry, local of UpdateChangeLog) Edited on January 3, 1984 2:16 pm, by Maxwell converted to Cedar 5. moved procedures that used the UserExec to end of file. commented out references to UserExecExtras in RedSave. Edited on June 15, 1984 2:21:59 pm PDT, by Russ Atkinson fixed FindDate to ensure that what it finds is really a date. This allows the appearance of last names like Mayo, August, and the like. Defensive programming wins again! Russ Atkinson (RRA) January 3, 1985 5:51:17 pm PST Various cleanups, especially the change to "EditorComforts". We have added the ability to insert Xerox copyright notices. We have also thrown out a number of "features". The format of various messages has been simplified & made more uniform. Russ Atkinson (RRA) April 29, 1985 3:56:23 pm PDT fixed FindDate to ignore ropes of excessive length (governed by maxDateRope) Κ&– "Cedar" style˜code™Kšœ Οmœ7™BK™0K™)K™3K™-K™-—K™šΟk ˜ Kšœžœ ˜Kšœ žœžœ˜8Kšœžœ$˜1Kšžœžœ˜Kš žœžœbΠprœ žœžœ ˜ŸKšœžœ]˜hKšœžœ ˜KšœžœUžœ!˜„Kšœ žœ₯˜³Kšœ žœ˜Kšœ žœ ˜Kšœžœ˜Kšœ žœD˜UKšœžœ ˜Kšœ žœ˜)K˜—šΠblœž œ˜#Kšžœžœžœ`˜‰Kšœž˜—K™™K™Kšœ žœ˜#Kšžœžœžœ˜Kšžœžœžœžœ˜Kšœ žœ˜Kšœžœ˜$—K™™!K™š Οn œžœžœžœžœ˜9Kšžœžœžœ˜Kšœ žœ2žœ˜DK˜šžœ žœžœ˜Kšœ3™3Kšœžœžœ˜šœžœ4˜Kšžœžœ{˜šKšžœ žœ˜*Kšœ˜—Kšœ˜Kšœ˜K˜—š œ žœžœžœžœ˜ KšœT˜T—K˜š ‘œžœžœžœžœ˜0Kšœa™aKšœžœ˜Kšžœžœžœ ˜*š žœ žœžœžœžœ žœž˜GKšœžœ˜šžœ ž˜Kšœ'žœ˜-Kšžœ žœžœΟc˜(šžœžœž˜Kšœ0˜0Kšœ0˜0Kšœžœ˜8Kšœžœ˜8Kšžœžœ’˜)—šœ6˜6šœ˜KšœG™GKšœžœ˜——Kšžœ˜ Kšžœ˜—Kšžœ˜—Kšžœ ˜Kšœ˜—K˜š ‘ œžœ'žœžœžœ˜OKšœœžœžœ™»Kšœžœ˜ Kšœ˜Kšžœž œžœ˜Kšœ‚˜‚KšœU˜UKšžœžœ˜ Kšœ˜—K˜š‘œžœ,žœ˜KKšœžœžœ˜Kšœžœ’˜(Kšœžœ˜Kšœ žœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœ’+˜KKšœžœžœ’*˜HKšœžœžœ’!˜DK˜šž˜K™£šž˜šœžœ˜Kšœ ™ K˜%Kšœ"˜"KšœD˜DKšœ ˜ Kšœ˜—šœ˜Kšœ™Kšœ"˜"Kšžœžœžœžœ˜šžœžœžœžœ˜KKšžœ˜—Kšžœžœžœ˜4Kšœ"˜"Kšœ ˜ Kšœ˜—šžœ˜ Kšœ™Kšœ˜Kšžœžœžœžœ˜KKšžœžœžœ˜4K˜——Kš žœ*žœžœžœžœ˜@šžœ˜Kšžœ ˜šžœ˜Kšœ-Οi™Ašžœ@˜BKšžœE˜IKšžœžœ žœ0˜HKšžœ˜—Kšžœžœžœ˜"K˜——Kšžœžœžœ˜•StartOfExpansiond[viewer: ViewerClasses.Viewer, loc: TiogaOps.Location _ [node: NIL, where: 0], rope: ROPE]šžœ"žœžœ;žœ˜tKšœL™LKšœ˜Kšœ žœ˜K˜—šžœ žœ.˜>Kšžœ)žœ˜2—Kšžœ˜—šžœ žœ˜Kšœ7™7Kšœ˜Kšœ˜Kšœ˜Kšžœžœ˜ K˜—Kšœ)™)Kš žœžœžœžœ’˜7Kšœ6˜6šžœžœ˜Kšœžœ1˜K˜Kš žœ žœ žœžœ žœ˜CKšžœ žœžœ˜Kšœ˜—K˜Kš‘ œžœžœžœžœžœžœžœ˜aKš ‘œžœžœ žœžœžœ˜QK˜š‘œžœ,žœ5žœžœ"žœžœžœžœ˜μKšœE˜EKš œžœžœžœžœ˜;Kšœžœ˜ Kšœžœžœ2˜dKšœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜š‘œžœ˜,Kšœžœ ˜Kšœ˜Kšœžœžœ˜ š ‘œžœžœ žœžœ˜@Kšœ žœ˜Kšœ˜Kšžœ žœžœžœ˜ Kšœžœžœ˜šœ˜šžœ˜Kšžœžœžœ˜Kš žœ žœžœžœžœ˜0K˜—Kšœžœ˜%Kš žœ žœ"žœžœžœžœ˜Kšžœ ˜K˜—K˜—š‘ œžœžœžœ˜5š žœžœžœžœžœžœž˜Ašžœž˜Kšžœžœ˜ Kšœžœžœ˜Kšžœ˜—Kšžœ˜—Kšžœžœ˜Kšœ˜—Kšœžœ˜Kšœ ˜ šžœž˜Kšœžœ˜ šžœžœž˜Kšœ ˜ Kšœ žœžœžœ˜.Kšœžœ˜/Kšœ žœ žœ*˜Fšžœ˜ Kšžœžœžœ.˜GKšœ8˜8K˜——Kšœ˜K˜Kšžœ˜—š žœ žœžœ žœžœ˜&Kšœžœžœ˜$šœ žœ#žœ˜GKšžœžœžœ ˜.—Kšœ˜—šžœ žœžœž˜,š žœžœžœžœžœžœž˜Aš žœžœžœ'žœ'žœ˜pKšœv™vKšœ˜Kšœ žœ˜ Kšžœ˜Kšœ˜—Kšž˜——šžœ žœ˜šžœ ž˜šžœ˜Kšœ žœ ˜Kšœ˜Kšœ˜—šžœ˜Kšœžœ ˜"Kšœ#˜#K˜——K˜—Kšœ˜—š‘œžœ˜#Kšœ˜Kšžœžœ’Ο˜ˆKšœ"˜"šžœ žœž˜Kšœ˜Kšœ˜Kšžœ˜—Kšœ˜—–&[n: TiogaExtraOps.Ref, name: ATOM]š‘œžœ˜KšœM˜Mšžœ#žœž˜7K˜ K˜K˜šžœ#žœ˜+Kšœ#˜#K˜K˜—Kšžœ˜—Kšœ˜—K–&[n: TiogaExtraOps.Ref, name: ATOM]˜Kšœ™K˜Kšœ]žœ™zšžœ1žœž˜@Kšœžœ1˜;Kšžœ˜K˜—šžœžœž˜šœžœžœ˜CKšœ9™9Kšœžœ˜Kšœžœ˜K˜—šœžœ˜ KšžœZžœ’œžœ˜„Kšžœžœvžœžœ˜ŒKšœ8˜8šžœžœžœž˜Kšœžœžœžœ"˜3Kšžœžœžœžœ˜Kšœ˜Kšžœ˜—šž˜Kšœ!žœ˜%—K˜—Kšžœ˜—šžœžœžœ˜%Kšœu™uš‘œžœžœ žœ˜5Kšœ˜Kšœžœžœ˜Kšœ!™!šž˜Kšžœžœžœžœ˜Kšœ˜šžœžœž˜Kšœ˜Kšžœžœ˜$Kšœ4žœ˜9Kšžœžœ˜—Kšœ#˜#Kšžœ˜—K˜—š‘ œžœžœžœ˜&Kšœžœžœ˜+Kšœžœ&˜0š œžœžœ žœžœ˜-Kš œžœ žœžœžœžœžœ˜:—KšœH˜HKšœ>˜>Kšžœ/˜5Kšžœ žœžœ˜K˜—šžœž˜Kšœžœ˜šœ žœ˜Kšœ6™6—šœ˜Kšœ+™+Kšœ˜šžœžœž˜šžœžœžœ˜#Kšœžœ˜šžœžœž˜Kšœ˜Kšœ2žœžœ ˜BKšžœžœ˜—K˜—Kšœ#˜#Kšžœ˜—Kšžœ˜Kšžœ˜Kšœ˜—šœ˜Kšœ3™3Kšžœ-žœžœžœ˜?—Kšžœžœ˜—šžœžœž˜Kšœ6žœ˜:KšœK˜KKšžœ˜—Kšžœžœžœ"žœžœžœžœ˜tKšœ˜—K™Ešžœžœžœ˜!Kšœžœ)˜0Kšœžœ ˜Kšœžœžœ(˜2šžœ ž˜Kšœžœžœžœ˜6Kšœ žœžœ žœ˜@šžœ ž˜šžœ˜Kšœ žœ ˜Kšœ˜Kšœ˜—šžœ˜Kšœžœ ˜"Kšœ#˜#K˜——Kšœžœžœžœ˜6šžœžœ˜Kšœ™šœ žœ˜Kšžœžœ žœžœ˜*Kšœ˜—Kšœžœžœžœ˜JKšœžœžœžœ˜6Kšœ˜—šžœžœžœ’9˜JKšžœžœžœ˜ Kšœžœžœžœ˜6Kšžœ˜—Kšžœ˜—–8[n: TiogaExtraOps.Ref, name: ATOM, value: REF ANY]šœ˜Kšœ ˜ Kšœ˜KšœžœO˜qKšœ˜—Kšœ˜—Kšœ$žœ˜)Kšœ’.˜BKšœžœ˜ š žœžœžœžœžœžœž˜AKšœ%™%Kšœžœ˜šžœžœž˜Kšžœ žœ˜'šœO˜OKšžœžœžœžœ˜@Kšœ˜—Kšœ$žœ˜+Kšžœžœžœ˜%—Kšœ˜Kšžœ˜—K–&[n: TiogaExtraOps.Ref, name: ATOM]˜Kš’#™#šžœ˜šžœ˜šžœžœž˜šžœ žœ$˜6Kšœ0žœ˜6—šœžœ˜ Kšœ3žœ’A˜|Kšœ žœ’H˜\K˜—šœžœ˜Kšœ?žœ˜E—Kšžœ˜—K˜—šžœžœ ž˜šœ˜Kšžœžœžœ"˜FKšžœžœžœžœ+˜OKšžœ&˜*Kšž˜K˜———šžœžœ žœ˜Kšœ™šžœžœžœ˜%Kšœ(˜(Kšœ-’˜@Kšžœžœžœ˜4K˜—Kšžœ˜Kšœ˜—šžœž˜šžœ˜Kšœ]ž™^Kšœ˜Kšœ˜šžœžœ˜Kšœ˜Kšœ;˜;Kšžœ žœžœ"˜9šžœž˜ šžœ˜KšœG˜GKšœ'˜'K˜—šžœ˜Kšœ<˜šœ˜Kšœ?˜?—šœ˜Kšœ*˜*—šžœž˜ Kšžœ3˜7Kšžœ:˜>—K˜—K˜š‘œžœ˜Kšœ+˜+Kšœ*˜*K˜7K˜PK˜8K˜K˜—KšœžœI™_Kšœžœœ3™TKšœžœ˜Kšœžœ˜Kšœ žœ ˜šœ žœ˜Kšœ†™†—K˜K˜K˜—Kšžœ˜K˜™1K™\K™CK™™1K™lKšœ €™'—™1K™ŒKšœ €™—™2Kšœ €$œI€ œ7€œ€"™‰—™1Kšœ € ™—™0Kšœ €™$—™0Kšœ €Hœ€œ™—™0Kšœ €œ€œ™S—™.Kšœ €™—™.Kšœ €™"—K™™+Kšœ €œ€œ™N—K™™,Kšœ™Kšœ €œ7™J—K™™-K™†K™—™8Kšœ«™«—K™™2Kšœτ™τ—™1KšœL™L—K™—…—fn«γ