DIRECTORY Atom USING [GetPName], BasicTime USING [GMT, nullGMT, Period], BcdDefs USING [NullVersion, VersionStamp], Commander USING [CommandProc, Handle, Register], CommandTool USING [ArgumentVector, DoCommand, Failed, GetProp, Parse], Convert USING [RopeFromInt], DFUtilities USING [DateToStream], EditedStream USING [GetEcho, Rubout, SetEcho, SetMode, UnAppendBufferChars], File USING [GetVolumeName, SystemVolume], FileNames USING [GetShortName, ResolveRelativePath], FS USING [Close, EnumerateForNames, Error, ExpandName, FileInfo, GetName, NameProc, Open, OpenFile], IO USING [BreakProc, Close, EndOf, EndOfStream, Error, GetRefAny, GetToken, GetTokenRope, IDProc, PeekChar, PutChar, PutF, PutRope, RIS, SkipWhitespace, STREAM, TokenProc], MBQueue USING [CreateMenuEntry], Menus USING [AppendMenuEntry, ClickProc, CopyEntry, CreateMenu, FindEntry, Menu, MenuEntry, ReplaceMenuEntry], PriorityQueue USING [Create, Insert, Ref, Remove, Size, SortPred], Process USING [CheckForAbort], ReadEvalPrint USING [Handle], Rope USING [Cat, Compare, Concat, Equal, Fetch, Find, Flatten, FromChar, FromRefText, Index, Length, Match, Replace, ROPE, Run, SkipTo, Substr], RopeList USING [Reverse], ThisMachine USING [Name], TiogaMenuOps USING [Open], TiogaOps USING [FindDef, GetSelection, Location, LocOffset, Ref, Root], UserCredentials USING [Get, Login], UserProfile USING [CallWhenProfileChanges, Line, ProfileChangedProc, Token], VersionMap USING [Length, Map, MapList, Range, RangeList, RangeToEntry, ShortNameToRanges], VersionMapDefaults USING [GetMapList], ViewerClasses USING [Viewer], ViewerIO USING [GetBuffer, TypeChars], ViewerOps USING [EnumerateViewers, EnumProc, FetchProp, PaintViewer], ViewerTools USING [GetSelectionContents, SetSelection]; OtherCommandsImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Commander, CommandTool, Convert, DFUtilities, EditedStream, File, FileNames, FS, IO, MBQueue, Menus, PriorityQueue, Process, Rope, RopeList, ThisMachine, TiogaMenuOps, TiogaOps, UserCredentials, UserProfile, VersionMap, VersionMapDefaults, ViewerIO, ViewerOps, ViewerTools SHARES VersionMap = BEGIN GMT: TYPE = BasicTime.GMT; LORA: TYPE = LIST OF REF ANY; Map: TYPE = VersionMap.Map; MapList: TYPE = VersionMap.MapList; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; VersionStamp: TYPE = BcdDefs.VersionStamp; NullVersion: VersionStamp = BcdDefs.NullVersion; SourceFileList: TYPE = LIST OF SourceFileEntry; SourceFileEntry: TYPE = RECORD [ map: VersionMap.Map, name: ROPE, created: BasicTime.GMT, stamp: VersionStamp]; AliasCellObject: TYPE = RECORD [ args: LIST OF ROPE _ NIL, def: ROPE ]; AliasCell: TYPE = REF AliasCellObject; lastUser: ROPE _ NIL; asterisky: BOOLEAN _ FALSE; Alias: Commander.CommandProc = TRUSTED { commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine]; name, def: ROPE; token: REF TEXT _ NEW[TEXT[30]]; args: LIST OF ROPE _ NIL; aliasCell: AliasCell; { ENABLE { IO.EndOfStream => GOTO Die; IO.Error => GOTO Die; }; token.length _ 0; token _ commandLineStream.GetToken[IO.TokenProc, token].token; name _ Rope.FromRefText[token]; [] _ commandLineStream.SkipWhitespace[FALSE]; IF NOT commandLineStream.EndOf[] AND commandLineStream.PeekChar[] = '( THEN { FOR l: LIST OF REF ANY _ NARROW[commandLineStream.GetRefAny[]], l.rest UNTIL l = NIL DO WITH l.first SELECT FROM r: ROPE => args _ CONS[r, args]; a: ATOM => args _ CONS[Atom.GetPName[a], args]; ENDCASE => ERROR; ENDLOOP; TRUSTED {args _ RopeList.Reverse[args]; }; }; token.length _ 0; token _ commandLineStream.GetToken[CRBreak, token].token; def _ Rope.FromRefText[token]; aliasCell _ NEW[AliasCellObject _ [args, def]]; Commander.Register[ key: name, proc: AliasImplProc, doc: Rope.Concat["Alias ", cmd.commandLine], clientData: aliasCell]; IO.Close[commandLineStream]; EXITS Die => NULL; }; }; AliasImplProc: Commander.CommandProc = TRUSTED { aliasCell: AliasCell _ NARROW[cmd.procData.clientData]; newCommandLine: ROPE _ aliasCell.def; token: REF TEXT _ NEW[TEXT[40]]; new: ROPE; restOfStream: ROPE; commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine]; synonyms: LIST OF REF SynonymRecord; SynonymRecord: TYPE = RECORD[key, val: ROPE]; FOR l: LIST OF ROPE _ aliasCell.args, l.rest UNTIL l = NIL DO token.length _ 0; token _ commandLineStream.GetToken[IO.IDProc, token ! IO.EndOfStream => CONTINUE; IO.Error => GO TO Nasty ].token; new _ Rope.FromRefText[token]; IF RopeMemb[new, l.rest] THEN { dummy: ROPE = UniqueRope[]; synonyms _ CONS[NEW[SynonymRecord _ [new, dummy]], synonyms]; new _ dummy; }; newCommandLine _ RopeSubst[old: l.first, new: new, base: newCommandLine]; ENDLOOP; FOR l: LIST OF REF SynonymRecord _ synonyms, l.rest UNTIL l = NIL DO newCommandLine _ RopeSubst[old: l.first.val, new: l.first.key, base: newCommandLine]; ENDLOOP; token.length _ 0; token _ commandLineStream.GetToken[CRBreak, token ! IO.EndOfStream => CONTINUE; IO.Error => GO TO Nasty ].token; restOfStream _ Rope.FromRefText[token]; newCommandLine _ Rope.Cat[newCommandLine, restOfStream, "\n"]; IO.Close[commandLineStream]; result _ CommandTool.DoCommand[commandLine: newCommandLine, parent: cmd]; EXITS Nasty => {msg _ "IO.Error while parsing arguments"; result _ $Failed}; }; CreateButton: Commander.CommandProc = TRUSTED { commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine]; name: ROPE; def: ROPE; token: REF TEXT _ NEW[TEXT[30]]; br: ButtonImplRef _ NEW[ButtonImplObject]; cth: Commander.Handle _ cmd; reph: ReadEvalPrint.Handle _ br.rep _ GetReadEvalPrint[cmd]; { ENABLE { IO.EndOfStream => GOTO Die; IO.Error => GOTO Die; }; token.length _ 0; token _ commandLineStream.GetToken[IO.TokenProc, token].token; name _ Rope.FromRefText[token]; [] _ commandLineStream.SkipWhitespace[FALSE]; token.length _ 0; token _ commandLineStream.GetToken[CRBreak, token ! IO.EndOfStream => CONTINUE].token; EXITS Die => NULL; }; IO.Close[commandLineStream]; def _ Rope.FromRefText[token]; SELECT TRUE FROM Rope.Length[def] = 0 => def _ NIL; Rope.Match["*\n", def] => {}; ENDCASE => def _ Rope.Concat[def, "\n"]; br.def _ def; IF reph = NIL THEN RETURN [$Failure, "Can't find $ReadEvalPrintHandle"] ELSE { viewer: ViewerClasses.Viewer _ reph.viewer; IF viewer # NIL THEN { menu: Menus.Menu _ reph.viewer.menu; IF menu # NIL THEN { new: Menus.MenuEntry _ IF def = NIL THEN NIL ELSE MBQueue.CreateMenuEntry[q: reph.menuHitQueue, name: name, proc: ButtonImplButtonProc, clientData: br]; old: Menus.MenuEntry _ Menus.FindEntry[menu, name]; SELECT TRUE FROM old # NIL => Menus.ReplaceMenuEntry[menu, old, new]; new # NIL => Menus.AppendMenuEntry[menu, new, 0]; ENDCASE; ViewerOps.PaintViewer[viewer: viewer, hint: menu, clearClient: FALSE]; }; }; }; }; ClearMenu: Commander.CommandProc = TRUSTED { reph: ReadEvalPrint.Handle _ GetReadEvalPrint[cmd]; IF reph # NIL THEN { viewer: ViewerClasses.Viewer _ reph.viewer; IF viewer # NIL THEN { oldMenu: Menus.Menu _ viewer.menu; IF oldMenu # NIL THEN { newMenu: Menus.Menu _ Menus.CreateMenu[1]; CopyNamedEntry["STOP!", oldMenu, newMenu]; CopyNamedEntry["Find", oldMenu, newMenu]; CopyNamedEntry["Split", oldMenu, newMenu]; reph.viewer.menu _ newMenu; ViewerOps.PaintViewer[viewer: viewer, hint: menu, clearClient: FALSE]; }; RETURN; }; }; }; ButtonImplButtonProc: Menus.ClickProc = { br: ButtonImplRef _ NARROW[clientData]; def: ROPE; curSel: ROPE; viewer: ViewerClasses.Viewer _ NIL; start: TiogaOps.Location; viewerName: ROPE _ NIL; fileName: ROPE _ NIL; shortFileName: ROPE _ NIL; index: INT _ -1; pos: INT; controlRope: ROPE _ IF control THEN "control" ELSE "noControl"; shiftRope: ROPE _ IF shift THEN "shift" ELSE "noShift"; buttonRope: ROPE; SELECT mouseButton FROM red => buttonRope _ "left"; yellow => buttonRope _ "middle"; blue => buttonRope _ "right"; ENDCASE => ERROR; IF br = NIL THEN RETURN; def _ br.def; [viewer: viewer, start: start] _ TiogaOps.GetSelection[primary]; curSel _ ViewerTools.GetSelectionContents[]; IF viewer # NIL AND NOT viewer.destroyed AND NOT viewer.newFile THEN { root: TiogaOps.Ref _ TiogaOps.Root[start.node]; offset: INT _ TiogaOps.LocOffset[loc1: [root, 0], loc2: start, skipCommentNodes: TRUE]; index _ offset; viewerName _ viewer.file; IF viewerName = NIL THEN viewerName _ viewer.name; fileName _ viewer.file; fileName _ Rope.Substr[fileName, 0, Rope.SkipTo[fileName, 0, "!"]]; }; pos _ curSel.Find["\n"]; IF pos # -1 THEN curSel _ Rope.Substr[base: curSel, start: 0, len: pos]; fileName _ IF (Rope.SkipTo[s: curSel, pos: 0, skip: " \t"] = curSel.Length[]) AND (curSel.Length[] > 1) THEN curSel ELSE fileName; shortFileName _ FileNames.GetShortName[path: fileName, stripOffVersionNumber: TRUE]; IF Rope.SkipTo[def, 0, "$"] < Rope.Length[def] THEN { def _ RopeSubst[old: "$CurrentSelection$", new: curSel, base: def, case: TRUE]; def _ RopeSubst[old: "$FileNameSelection$", new: fileName, base: def, case: TRUE]; def _ RopeSubst[old: "$ShortFileNameSelection$", new: shortFileName, base: def, case: TRUE]; def _ RopeSubst[old: "$SelectedViewerName$", new: viewerName, base: def, case: TRUE]; def _ RopeSubst[old: "$ViewerPosition$", new: Convert.RopeFromInt[index, 10, FALSE], base: def, case: TRUE]; def _ RopeSubst[old: "$MouseButton$", new: buttonRope, base: def, case: TRUE]; def _ RopeSubst[old: "$ControlKey$", new: controlRope, base: def, case: TRUE]; def _ RopeSubst[old: "$ShiftKey$", new: shiftRope, base: def, case: TRUE]; }; { bufferContents: REF TEXT _ ViewerIO.GetBuffer[br.rep.in]; IF bufferContents # NIL AND bufferContents.length > 0 AND bufferContents[bufferContents.length - 1] # '\n THEN { FOR n: NAT DECREASING IN [0..bufferContents.length) DO IF bufferContents[n] = '\n THEN { EditedStream.UnAppendBufferChars[ stream: br.rep.in, nChars: bufferContents.length - n - 1]; EXIT; } REPEAT FINISHED => EditedStream.UnAppendBufferChars[stream: br.rep.in, nChars: LAST[NAT]]; ENDLOOP; }; }; IF viewer = br.rep.viewer THEN ViewerTools.SetSelection[viewer: viewer, selection: NIL]; ViewerIO.TypeChars[editedViewerStream: br.rep.in, chars: def]; }; ButtonImplRef: TYPE = REF ButtonImplObject; ButtonImplObject: TYPE = RECORD [ rep: ReadEvalPrint.Handle _ NIL, def: ROPE _ NIL ]; WhenProfileChanges: UserProfile.ProfileChangedProc = { IF reason = rollBack THEN { user: ROPE _ UserCredentials.Get[].name; IF NOT Rope.Equal[user, lastUser, FALSE] THEN { firstTime: BOOL _ TRUE; eachTool: ViewerOps.EnumProc = { IF Rope.Match["CommandTool: *", v.name] AND v.class.flavor = $Typescript AND NOT v.destroyed THEN { IF firstTime THEN { firstTime _ FALSE; StuffCommand[v, "///Commands/NoteNewUser\n"]; }; StuffCommand[v, "///Commands/NotePerLogin\n"]; }; }; ViewerOps.EnumerateViewers[eachTool]; }; }; }; NotePerLogin: Commander.CommandProc = { line: ROPE _ UserProfile.Line["CommandTool.PerLogin", NIL]; IF line # NIL THEN [] _ CommandTool.DoCommand[line, cmd]; }; NoteNewUser: Commander.CommandProc = { line: ROPE _ UserProfile.Line["CommandTool.NewUser", NIL]; lastUser _ UserCredentials.Get[].name; IF line # NIL THEN [] _ CommandTool.DoCommand[line, cmd]; }; NotePerCommandTool: Commander.CommandProc = { line: ROPE _ UserProfile.Line["CommandTool.PerCommandTool", NIL]; IF line = NIL THEN line _ UserProfile.Line["CommandTool.EachCommandToolCommands", NIL]; IF line # NIL THEN [] _ CommandTool.DoCommand[line, cmd]; }; NoteBootCommands: Commander.CommandProc = { line: ROPE _ UserProfile.Line["CommandTool.BootCommands", NIL]; IF line # NIL THEN [] _ CommandTool.DoCommand[line, cmd]; }; Login: Commander.CommandProc = { oldEcho: IO.STREAM; StartInteraction: PROC RETURNS [in, out: IO.STREAM] = { IF asterisky THEN EditedStream.SetMode[stream: cmd.in, echoAsterisks: TRUE] ELSE { oldEcho _ EditedStream.GetEcho[cmd.in]; EditedStream.SetEcho[cmd.in, NIL]; }; in _ cmd.in; out _ cmd.out; }; EndInteraction: PROC [in, out: IO.STREAM] = { IF asterisky THEN EditedStream.SetMode[stream: cmd.in, echoAsterisks: FALSE] ELSE EditedStream.SetEcho[cmd.in, oldEcho]; }; UserCredentials.Login[ startInteraction: StartInteraction, endInteraction: EndInteraction, options: [alwaysInteract: TRUE] ! EditedStream.Rubout => {result _ $Failed; msg _ " -- Aborted.\n"; CONTINUE} ]; }; ListCommandProc: Commander.CommandProc = { EachFile: FS.NameProc = { attachedTo: ROPE _ NIL; created: GMT _ BasicTime.nullGMT; bytes: INT _ 0; keep: CARDINAL _ 0; item: FileItem _ NIL; needInfo: BOOL _ unattachedOnly; continue _ TRUE; Process.CheckForAbort[]; IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN; SELECT TRUE FROM directoriesOnly => {}; briefPrint AND NOT complexSorting AND NOT attachedPrint => {}; ENDCASE => needInfo _ TRUE; IF needInfo THEN { pos: INT _ Rope.SkipTo[fullFName, 1, "]"]+1; len: INT _ Rope.Length[fullFName]; [fullFName, attachedTo, keep, bytes, created] _ FS.FileInfo[name: fullFName, remoteCheck: remoteCheck]; }; IF unattachedOnly AND attachedTo # NIL THEN RETURN; IF bytes < 0 AND needInfo AND NOT remoteCheck THEN [fullFName, attachedTo, keep, bytes, created] _ FS.FileInfo[name: fullFName, remoteCheck: TRUE]; IF gNamePrint THEN fullFName _ FNameToGName[name: fullFName]; item _ NEW[FileItemRep _ [fullFName, attachedTo, created, bytes, keep]]; filesSeen _ filesSeen + 1; IF bytes > 0 THEN bytesTotal _ bytesTotal + bytes; SELECT TRUE FROM directoriesOnly => { oldLag: ROPE _ lagPrefix; SetLagPrefix[fullFName]; IF oldLag # lagPrefix THEN { item.fullFName _ lagPrefix; PriorityQueue.Insert[pq, item]; }; }; complexSorting => PriorityQueue.Insert[pq, item]; ENDCASE => PrintOneFile[item]; }; PrintOneFile: PROC [item: FileItem] = { oldLag: ROPE _ lagPrefix; printName: ROPE _ item.fullFName; Process.CheckForAbort[]; IF NOT fullPrint AND NOT directoriesOnly THEN { SetLagPrefix[printName]; IF oldLag # lagPrefix THEN { IO.PutRope[out, lagPrefix]; IO.PutChar[out, IF oneLine THEN ' ELSE '\n]; }; printName _ Rope.Substr[printName, lagPrefixLen]; IF NOT oneLine THEN IO.PutRope[out, " "]; }; SELECT TRUE FROM directoriesOnly => IO.PutRope[out, printName]; briefPrint => { IO.PutRope[out, printName]; IF attachedPrint AND item.attachedTo # NIL THEN { IF NOT oneLine THEN IO.PutRope[out, "\n "]; IO.PutF[out, " => %g", [rope[item.attachedTo]] ]; }; }; ENDCASE => { form: ROPE = IF narrowPrint THEN "%g\n%12g " ELSE "%-24g %6g "; IO.PutF[out, form, [rope[printName]], [integer[item.bytes]] ]; IF item.created = BasicTime.nullGMT THEN IO.PutRope[out, "??"] ELSE DFUtilities.DateToStream[out, [explicit, item.created] ]; IF attachedPrint AND item.attachedTo # NIL THEN { IF NOT oneLine THEN IO.PutRope[out, "\n "]; IO.PutF[out, " => %g", [rope[item.attachedTo]] ]; }; IF printKeep THEN IO.PutF[out, ", keep: %g", [cardinal[item.keep]] ]; }; IO.PutChar[out, IF oneLine THEN ' ELSE '\n]; }; TryPattern: PROC [pattern: ROPE] = { ENABLE FS.Error => IF error.group # $bug THEN { IO.PutRope[out, " -- "]; IO.PutRope[out, error.explanation]; GO TO err}; patternsTried _ patternsTried + 1; pattern _ FileNames.ResolveRelativePath[pattern]; pattern _ FS.ExpandName[pattern].fullFName; IF highestPrint AND NOT Rope.Match["*!*", pattern] THEN pattern _ Rope.Concat[pattern, "!h"]; IF exactLevelMatch THEN anglesRequired _ CountAngles[pattern]; complexSorting _ sortData # NIL; SELECT TRUE FROM directoriesOnly => pq _ PriorityQueue.Create[SortPred, NIL]; complexSorting => pq _ PriorityQueue.Create[SortPred, sortData]; ENDCASE => pq _ NIL; SetLagPrefix[NIL]; FS.EnumerateForNames[pattern, EachFile]; SetLagPrefix[NIL]; IF pq # NIL THEN { lagName: ROPE _ NIL; THROUGH [0..PriorityQueue.Size[pq]) DO item: FileItem = NARROW[PriorityQueue.Remove[pq]]; IF directoriesOnly THEN { IF Rope.Equal[item.fullFName, lagName] THEN LOOP; lagName _ item.fullFName; }; PrintOneFile[item]; ENDLOOP; }; EXITS err => {IO.PutRope[out, "\n"]; RETURN}; }; SetLagPrefix: PROC [fileName: ROPE] = { IF lagPrefix # NIL THEN { IF Rope.Run[lagPrefix, 0, fileName, 0, FALSE] = lagPrefixLen THEN { pos: INT = Rope.SkipTo[fileName, lagPrefixLen, ">/]"]; IF pos = Rope.Length[fileName] THEN RETURN; }; }; FOR i: INT DECREASING IN [0..Rope.Length[fileName]) DO SELECT Rope.Fetch[fileName, i] FROM '>, '/, '] => {lagPrefix _ Rope.Flatten[fileName, 0, lagPrefixLen _ i+1]; RETURN}; ENDCASE; ENDLOOP; lagPrefix _ NIL; lagPrefixLen _ 0; }; AddSortOption: PROC [option: ATOM] = { new: LORA _ LIST[option]; IF sortDataTail = NIL THEN sortData _ new ELSE sortDataTail.rest _ new; sortDataTail _ new; }; RemSortOption: PROC [option: ATOM] = { lag: LORA _ sortData; IF lag = NIL THEN RETURN; IF lag.first = option THEN { sortData _ sortData.rest; RETURN}; FOR each: LORA _ lag.rest, each.rest WHILE each # NIL DO IF each.first = option THEN {lag.rest _ each.rest; EXIT}; lag _ each; ENDLOOP; }; FNameToGName: PROC [name: ROPE] RETURNS [ROPE] = { IF Rope.Match["[]*", name] THEN { IF gHost = NIL THEN gHost _ Rope.Cat["[", ThisMachine.Name[], "]"]; IF gDir = NIL THEN gDir _ Rope.Cat[gHost, "<", File.GetVolumeName[File.SystemVolume[]], ">"]; IF Rope.Match["[]<>*", name] THEN RETURN [Rope.Replace[base: name, start: 0, len: 4, with: gDir]] ELSE RETURN [Rope.Replace[base: name, start: 0, len: 2, with: gHost]]; }; RETURN [name]; }; gHost: ROPE _ NIL; gDir: ROPE _ NIL; out: STREAM = cmd.out; lagPrefix: ROPE _ NIL; lagPrefixLen: INT _ 0; patternsTried: INT _ 0; filesSeen: INT _ 0; bytesTotal: INT _ 0; directoriesOnly: BOOL _ FALSE; complexSorting: BOOL _ FALSE; printKeep: BOOL _ FALSE; narrowPrint: BOOL _ FALSE; attachedPrint: BOOL _ FALSE; briefPrint: BOOL _ FALSE; remoteCheck: BOOL _ FALSE; fullPrint: BOOL _ FALSE; highestPrint: BOOL _ cmd.procData.clientData = $Highest; oneLine: BOOL _ FALSE; unattachedOnly: BOOL _ FALSE; exactLevelMatch: BOOL _ FALSE; gNamePrint: BOOL _ FALSE; anglesRequired: INT _ 0; sortData: LORA _ NIL; sortDataTail: LORA _ NIL; pq: PriorityQueue.Ref _ NIL; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; direction: {up, down} _ down; FOR index: INT IN [0..Rope.Length[arg]) DO SELECT Rope.Fetch[arg, index] FROM '~ => {sense _ NOT sense; LOOP}; '> => direction _ down; '< => direction _ up; 'a, 'A => attachedPrint _ sense; 'b, 'B => briefPrint _ sense; 'd, 'D => { RemSortOption[$MoreRecent]; RemSortOption[$LessRecent]; IF sense THEN AddSortOption[IF direction = up THEN $LessRecent ELSE $MoreRecent]; }; 'f, 'F => fullPrint _ sense; 'g, 'G => gNamePrint _ sense; 'h, 'H => highestPrint _ sense; 'k, 'K => printKeep _ sense; 'n, 'N => narrowPrint _ sense; 'o, 'O => oneLine _ sense; 'p, 'P => directoriesOnly _ sense; 'r, 'R => remoteCheck _ sense; 's, 'S => { RemSortOption[$Larger]; RemSortOption[$Smaller]; IF sense THEN AddSortOption[IF direction = up THEN $Smaller ELSE $Larger]; }; 'u, 'U => unattachedOnly _ sense; 'x, 'X => exactLevelMatch _ sense; ENDCASE; sense _ TRUE; ENDLOOP; }; ProcessSwitches[UserProfile.Token["ListCommand.DefaultSwitches"]]; FOR i: NAT IN [1..argv.argc) DO arg: ROPE = argv[i]; IF Rope.Length[arg] = 0 THEN LOOP; IF Rope.Fetch[arg, 0] = '- THEN { ProcessSwitches[arg]; LOOP; }; TryPattern[arg]; ENDLOOP; IF patternsTried = 0 THEN TryPattern["*"]; IF oneLine THEN IO.PutChar[out, '\n]; IF filesSeen > 0 THEN { IO.PutF[out, "-- %g files", [integer[filesSeen]] ]; IF bytesTotal > 0 THEN IO.PutF[out, ", %g total bytes", [integer[bytesTotal]] ]; IO.PutChar[out, '\n]; }; EXITS failed => {result _ $Failure}; }; CountAngles: PROC [pattern: ROPE] RETURNS [count: INT _ 0] = { len: INT = Rope.Length[pattern]; pos: INT _ Rope.SkipTo[pattern, 0, ">"]; WHILE pos < len DO pos _ Rope.SkipTo[pattern, pos+1, ">"]; count _ count + 1; ENDLOOP; }; FileItem: TYPE = REF FileItemRep; FileItemRep: TYPE = RECORD [ fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL]; SortPred: PriorityQueue.SortPred = { xx: FileItem = NARROW[x]; yy: FileItem = NARROW[y]; options: LORA = NARROW[data]; FOR each: LORA _ options, each.rest WHILE each # NIL DO SELECT each.first FROM $MoreRecent => { IF xx.created = yy.created THEN LOOP; RETURN [BasicTime.Period[xx.created, yy.created] < 0]; }; $LessRecent => { IF xx.created = yy.created THEN LOOP; RETURN [BasicTime.Period[xx.created, yy.created] > 0]; }; $Larger => { IF xx.bytes = yy.bytes THEN LOOP; RETURN [xx.bytes > yy.bytes]; }; $Smaller => { IF xx.bytes = yy.bytes THEN LOOP; RETURN [xx.bytes < yy.bytes]; }; ENDCASE; ENDLOOP; RETURN [Rope.Compare[xx.fullFName, yy.fullFName, FALSE] = less]; }; OpenCommand: Commander.CommandProc = { st: IO.STREAM _ cmd.out; inStream: IO.STREAM _ IO.RIS[cmd.commandLine]; eachFile: PROC = { MyOpen: PROC [def: ROPE _ NIL] = { file: FS.OpenFile _ FS.Open[name: name, wantedCreatedTime: date ! FS.Error => { IF error.code = $unknownFile THEN { IO.PutF[st, "Not found: %g\n", [rope[name]] ]; } ELSE { IO.PutF[st, "%g\n", [rope[error.explanation]] ]; }; result _ $Failure; GO TO exit; }; ]; viewer: ViewerClasses.Viewer _ NIL; name _ FS.GetName[file].fullFName; FS.Close[file]; viewer _ TiogaMenuOps.Open[name]; IF viewer = NIL THEN { IO.PutF[st, "Not found: %g\n", [rope[r]] ]; result _ $Failure; } ELSE { IO.PutF[st, "Opened: %g\n", [rope[r]] ]; result _ NIL; IF Rope.Length[def] # 0 THEN [] _ TiogaOps.FindDef[viewer, def, forwards, feedback]; }; EXITS exit => {}; }; sfl: SourceFileList _ NIL; name: ROPE _ NIL; date: GMT; r: ROPE _ IO.GetTokenRope[inStream, IO.IDProc].token; IF Rope.Length[r] = 0 THEN RETURN; sfl _ FindSource[r]; SELECT TRUE FROM sfl = NIL => { rBase, rExt: ROPE; [rBase, rExt] _ SplitName[r]; IF rExt = NIL THEN { sfl _ FindSource[Rope.Concat[rBase, ".*"]]; [name, date] _ TryExtensions[sfl]; } ELSE { sfl _ FindSource[Rope.Concat[rBase, ".mesa"]]; [name, date] _ FindMostRecent[sfl]; }; IF name # NIL THEN MyOpen[rExt] ELSE { IO.PutF[st, "Sorry, '%g' is not in the current Cedar release.\n", [rope[r]]]; result _ $Failure; RETURN; }; }; ENDCASE => { [name, date] _ FindMostRecent[sfl]; MyOpen[]; }; }; DO eachFile[ ! FS.Error => {msg _ error.explanation; result _ $Failed; EXIT}; IO.EndOfStream => EXIT; ]; ENDLOOP; }; FindCommand: Commander.CommandProc = { st: IO.STREAM _ cmd.out; inStream: IO.STREAM _ IO.RIS[cmd.commandLine]; useBin: BOOL _ cmd.procData.clientData = $Bin; eachFile: PROC = { sfl: SourceFileList _ NIL; r: ROPE _ IO.GetTokenRope[inStream, IO.IDProc].token; IF Rope.Length[r] = 0 THEN RETURN; IF NOT Rope.Match["*.*", r] THEN r _ Rope.Concat[r, ".*"]; sfl _ FindSource[r, FALSE, IF useBin THEN $Symbols ELSE $Source]; IF sfl = NIL THEN { IO.PutF[st, "Sorry, '%g' is not in the current Cedar release.\n", [rope[r]] ]; RETURN; }; IO.PutF[st, "%g =>\n", [rope[r]] ]; WHILE sfl # NIL DO IO.PutF[st, " %g\n %g\n", [rope[sfl.first.name]], [time[sfl.first.created]] ]; sfl _ sfl.rest; ENDLOOP; }; DO eachFile[ ! FS.Error => {msg _ error.explanation; result _ $Failed; EXIT}; IO.EndOfStream => EXIT; ]; ENDLOOP; }; FindSource: PROC [short: ROPE, removeDuplDates: BOOL _ TRUE, which: ATOM _ NIL] RETURNS [SourceFileList _ NIL] = TRUSTED { size: INT _ Rope.Length[short]; starPos: INT _ short.Index[0, "*"]; match: BOOL _ starPos # size; hasDot: BOOL _ short.Index[0, "."] # size; rangeList: VersionMap.RangeList _ NIL; head: SourceFileList _ NIL; tail: SourceFileList _ NIL; shortShort: ROPE _ Rope.Flatten[short, 0, starPos]; shortShortLen: INT _ Rope.Length[shortShort]; mapList: MapList _ NIL; IF size = 0 THEN RETURN; IF which = NIL THEN which _ $Source; IF mapList = NIL THEN mapList _ VersionMapDefaults.GetMapList[which]; rangeList _ VersionMap.ShortNameToRanges[mapList, short]; WHILE rangeList # NIL DO range: VersionMap.Range _ rangeList.first; map: Map = range.map; rangeList _ rangeList.rest; Process.CheckForAbort[]; IF match THEN { entries: CARDINAL = VersionMap.Length[map]; IF range.first >= entries THEN LOOP; range.len _ entries - range.first; WHILE range.len # 0 DO fullName: ROPE; stamp: VersionStamp; thisShort: ROPE; created: BasicTime.GMT; [fullName, stamp, created, range] _ VersionMap.RangeToEntry[range]; thisShort _ ShortName[fullName]; IF Rope.Run[shortShort, 0, thisShort, 0, FALSE] # shortShortLen THEN EXIT; IF Rope.Match[short, thisShort, FALSE] THEN { new: SourceFileList _ LIST[[map: range.map, name: fullName, created: created, stamp: stamp]]; IF tail = NIL THEN head _ new ELSE tail.rest _ new; tail _ new; }; ENDLOOP; } ELSE { WHILE range.len # 0 DO new: SourceFileList; fullName: ROPE; stamp: VersionStamp; created: BasicTime.GMT; [fullName, stamp, created, range] _ VersionMap.RangeToEntry[range]; new _ LIST[[map: range.map, name: fullName, created: created, stamp: stamp]]; IF tail = NIL THEN head _ new ELSE tail.rest _ new; tail _ new; ENDLOOP; }; ENDLOOP; RemoveDuplicates[head, removeDuplDates]; RETURN [head]; }; ShortName: PROC [r: ROPE] RETURNS [ROPE] = { first: INT _ 0; last: INT _ Rope.Length[r]; FOR i: INT DECREASING IN [0..last) DO c: CHAR _ r.Fetch[i]; SELECT c FROM '>, '/ => {first _ i+1; EXIT}; '! => last _ i ENDCASE; ENDLOOP; RETURN [r.Substr[first, last - first]] }; SplitName: PROC [name: ROPE] RETURNS [prefix: ROPE _ NIL, ext: ROPE _ NIL] = { dot: INT _ 0; len: INT _ Rope.Length[name]; pos: INT _ len; WHILE (pos _ pos - 1) > 0 DO SELECT Rope.Fetch[name, pos] FROM '! => {name _ Rope.Flatten[name, 0, pos]; len _ pos}; '. => {prefix _ Rope.Flatten[name, 0, pos]; ext _ Rope.Flatten[name, pos+1]; RETURN}; '], '/, '> => EXIT; ENDCASE; ENDLOOP; RETURN [name, NIL]; }; FindMostRecent: PROC [sfl: SourceFileList] RETURNS [name: ROPE _ NIL, date: GMT] = { IF sfl # NIL THEN { date _ sfl.first.created; name _ sfl.first.name; FOR each: SourceFileList _ sfl.rest, each.rest WHILE each # NIL DO eachDate: GMT _ each.first.created; period: INT _ BasicTime.Period[from: date, to: eachDate]; IF period >= 0 THEN { eachName: ROPE _ each.first.name; IF period < 0 OR Rope.Compare[eachName, name, FALSE] = greater THEN { date _ eachDate; name _ eachName; }; }; ENDLOOP; }; }; RemoveDuplicates: PROC [sfl: SourceFileList, removeDuplDates: BOOL] = { WHILE sfl # NIL DO entry: SourceFileEntry _ sfl.first; thisStamp: VersionStamp _ entry.stamp; each: SourceFileList _ sfl.rest; lag: SourceFileList _ sfl; WHILE each # NIL DO next: SourceFileList _ each.rest; SELECT TRUE FROM Rope.Equal[each.first.name, entry.name, FALSE] => lag.rest _ next; removeDuplDates AND each.first.stamp = thisStamp => lag.rest _ next; ENDCASE => lag _ each; each _ next; ENDLOOP; sfl _ sfl.rest; ENDLOOP; }; TryExtensions: PROC [sfl: SourceFileList] RETURNS [ROPE _ NIL, GMT _ BasicTime.nullGMT] = { line: ROPE _ UserProfile.Line["SourceFileExtensions", "mesa tioga df cm config"]; in: STREAM _ IO.RIS[line, NIL]; DO token: ROPE _ IO.GetTokenRope[in, IO.IDProc ! IO.EndOfStream => EXIT].token; currentList: SourceFileList _ NIL; IF Rope.Length[token] = 0 THEN EXIT; FOR each: SourceFileList _ sfl, each.rest WHILE each # NIL DO name: ROPE _ each.first.name; base, ext: ROPE _ NIL; [base, ext] _ SplitName[name]; IF Rope.Equal[ext, token, FALSE] THEN { name _ FS.FileInfo[ name: Rope.Cat[base, ".", token], wantedCreatedTime: each.first.created, remoteCheck: FALSE ! FS.Error => SELECT error.code FROM $unknownFile, $unknownCreatedTime => LOOP; ENDCASE ].fullFName; currentList _ CONS[each.first, currentList]; }; ENDLOOP; IF currentList # NIL THEN RETURN FindMostRecent[currentList]; ENDLOOP; }; RopeSubst: PROC [old, new, base: ROPE, case: BOOL _ FALSE, allOccurrences: BOOL _ TRUE] RETURNS [ROPE] = { lenOld: INT = old.Length[]; lenNew: INT = new.Length[]; i: INT _ 0; WHILE (i _ Rope.Find[s1: base, s2: old, case: case, pos1: i]) # -1 DO base _ Rope.Replace[base: base, start: i, len: lenOld, with: new]; IF ~allOccurrences THEN EXIT; i _ i + lenNew; ENDLOOP; RETURN[base]; }; RopeMemb: PROC [rope: ROPE, lst: LIST OF ROPE] RETURNS[BOOL] = { FOR l: LIST OF ROPE _ lst, l.rest UNTIL l = NIL DO IF Rope.Equal[rope, l.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE] }; gennum: LONG CARDINAL _ 10000; UniqueRope: ENTRY PROC [c: CHAR _ 'A] RETURNS [ROPE] = { ENABLE UNWIND => NULL; gennum _ gennum + 1; RETURN[Rope.Concat[Rope.FromChar[c], Convert.RopeFromInt[gennum, 10, FALSE]]]; }; CRBreak: IO.BreakProc = { IF char = '\n THEN RETURN[break]; RETURN[other]; }; CopyNamedEntry: PROC [name: ROPE, oldMenu,newMenu: Menus.Menu] = { old: Menus.MenuEntry _ Menus.FindEntry[oldMenu, name]; IF old # NIL THEN Menus.AppendMenuEntry[newMenu, Menus.CopyEntry[old], 0]; }; GetReadEvalPrint: PROC [cmd: Commander.Handle] RETURNS [ReadEvalPrint.Handle] = { DO WITH CommandTool.GetProp[cmd, $ReadEvalPrintHandle] SELECT FROM reph: ReadEvalPrint.Handle => IF reph # NIL AND reph.viewer # NIL THEN RETURN [reph]; ENDCASE; WITH CommandTool.GetProp[cmd, $ParentCommander] SELECT FROM next: Commander.Handle => {cmd _ next; LOOP;}; ENDCASE; RETURN [NIL]; ENDLOOP; }; StuffCommand: PROC [viewer: ViewerClasses.Viewer, commands: ROPE] = { WITH ViewerOps.FetchProp[viewer, $ReadEvalPrint] SELECT FROM reph: ReadEvalPrint.Handle => { data: ButtonImplRef _ NEW[ButtonImplObject _ [rep: reph, def: commands]]; ButtonImplButtonProc[viewer, data]; }; ENDCASE; }; Init: PROC = { listDoc: ROPE = "(List | LS) {switch | pattern}*\nswitch = -a: attached print, -b: brief format, -d: date sort, -f: full name print, -g: GName print, -k: keep print, -n: narrow print, -o: one line, -p: prefixes only, -r: remote check, -s: size sort, -u: un backed up, -x: exact level match"; Commander.Register[key: "///Commands/Alias", proc: Alias, doc: "Create an alias for a command", interpreted: FALSE]; Commander.Register[key: "///Commands/CreateButton", proc: CreateButton, doc: "Create a CommandTool herald button", interpreted: FALSE]; Commander.Register[key: "///Commands/ClearMenu", proc: ClearMenu, doc: "Reset the CommandTool menu"]; Commander.Register[key: "///Commands/Login", proc: Login, doc: "Login a new user"]; Commander.Register[key: "///Commands/NoteBootCommands", proc: NoteBootCommands, doc: "Perform commands for the first commander after a full boot"]; Commander.Register[key: "///Commands/NoteNewUser", proc: NoteNewUser, doc: "Perform commands for a new user"]; Commander.Register[key: "///Commands/NotePerCommandTool", proc: NotePerCommandTool, doc: "Perform commands for a login (or rollback)"]; Commander.Register[key: "///Commands/NotePerLogin", proc: NotePerLogin, doc: "Perform commands for a login (or rollback)"]; Commander.Register[key: "///Commands/RemoveButton", proc: CreateButton, doc: "Remove a CommandTool herald button"]; Commander.Register["///Commands/LS", ListCommandProc, listDoc]; Commander.Register["///Commands/LSH", ListCommandProc, listDoc, $Highest]; Commander.Register["///Commands/List", ListCommandProc, listDoc]; Commander.Register["///Commands/ListH", ListCommandProc, listDoc, $Highest]; Commander.Register [ "///Commands/OpenR", OpenCommand, "Opens viewers on Cedar release source files given the short names (.mesa extension is the default). If a short name has multiple long names associated with it, the alternatives are listed, and no viewer is opened for that name."]; Commander.Register [ "///Commands/FindR", FindCommand, "Finds Cedar release source file names given the short names (.mesa extension is the default)"]; Commander.Register [ "///Commands/FindRBin", FindCommand, "Finds Cedar release binary file names given the short names.", $Bin]; UserProfile.CallWhenProfileChanges[WhenProfileChanges]; }; Init[]; END. OtherCommandsImpl.mesa Copyright c 1984, 1985, 1986 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) April 25, 1986 5:53:35 pm PST The "last" known user (initially unknown) set by NoteNewUser TRUE if the typescript supports *'s during login e.g. args are (x y) and substituting y gorp. The first token willl be the name of the button. The rest of the commandLine will be the thing to stuff. A special character sequence, $$, will stand for the current selection. Get the rest of it! Various cases: (0) no old entry & no new definition => ignore (1) no old entry & a new definition => create a new entry (2) old entry & no new definition => remove the old one (3) old entry & a new definition => replace the old entry This command will clean the current menu back to its ground state (STOP! Find Split). There is an old menu, so we make a clean new one. [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] There are specialized tokens: $CurrentSelection$ => replaced by the current selection up to but not including the first carriage return $FileNameSelection$ => replaced by the current selection if it appears to be a file name, otherwise replaced by the name of the selected viewer $ShortFileNameSelection$ => same as $FileNameSelection$ except that version number and directory are omitted $SelectedViewerName$ => replaced by the name of the selected viewer $ViewerPosition$ => replaced by the position of the current selection in a viewer $MouseButton$ => "left", "middle", or "right" $ShiftKey$ => "shift", "noShift" $ControlKey$ "control", "noControl" Get prefix of current selection before the first CR The curSel is the fileName if curSel is longer than one character and contains no whitespace. It is likely that we have substitutions to do If the selected viewer is the commandtool, then set the caret to the end, or the ViewerIO.TypeChars won't work. We get this whenever we rollback, or the credentials change. There is a new user, so we should try to customize the various command tools that exist. [v: ViewerClasses.Viewer] RETURNS [BOOL _ TRUE] RRA: EachCommandToolCommands is only for compatibility List Command [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [fullFName: ROPE] RETURNS [continue: BOOL] We do not know the # of bytes in the file, so we have to check on the true length, which is on the server. This seems to happen more often than I would like. I wonder why? item: REF [fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] Factor out the directories ... sets the lagging prefix from the given file name, which is presumed to be syntactically correct, although it need not be complete. A file name without a prefix will set the lagPrefix to NIL. We also enforce lagPrefixLen = Rope.Length[lagPrefix] at exit, assuming that no other routine sets lagPrefix. do we have a new prefix? So far we have a match with the lagging prefix. How far does it go? We have a new lagging prefix, so scan backwards for the LAST directory The file name has no prefix, so clear out the lagPrefix This argument sets switches for the remaining patterns Now the argument is assumed to be a file pattern. [x: Item, y: Item, data: REF] RETURNS [BOOL] OpenR commands [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] Handle = REF [in, out, err: STREAM, commandLine,command: ROPE, propertyList: List.AList] No file found as explicitly given, we might need to try some alternatives. At least one match, so open the most recent [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] Handle = REF [in, out, err: STREAM, commandLine,command: ROPE, propertyList: List.AList] OpenR utilities make a long name into a short one assumes a valid long name, of course the file in each is more recent or the same date each is more recent OR (same date AND lexically greater name) This routine removes entries with duplicate names an (if removeDuplDates = TRUE) also removes entries with duplicate dates. Utilities if old is not found in base, then value = base. if allOccurrences THEN substitute for each occurrence of old, otherwise only for first. Returns the enclosing ReadEvalPrint.Handle (NIL if no such handle). Initialization For List For OpenR & FindR Κ*Χ˜codešœ™Kšœ Οmœ=™HK™1K˜šΟk ˜ Kšœžœ ˜Kšœ žœžœ˜'Kšœžœ˜*Kšœ žœ!˜0Kšœ žœ5˜FKšœžœ˜Kšœ žœ˜!Kšœ žœ:˜LKšœžœ˜)Kšœ žœ%˜4Kšžœžœ\˜dKšžœžœ|žœžœ ˜¬Kšœžœ˜ Kšœžœc˜nKšœžœ/˜BKšœžœ˜Kšœžœ ˜Kšœžœkžœ˜Kšœ žœ ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ9˜GKšœžœ˜#Kšœ žœ;˜LKšœ žœK˜[Kšœžœ˜&Kšœžœ ˜Kšœ žœ˜&Kšœ žœ6˜EKšœ žœ&˜7——headšœžœž˜ Kšžœ_žœžœ½˜©Kšžœ ˜Kšœž˜K˜Kšžœžœ žœ˜Kš žœžœžœžœžœžœ˜Kšœžœ˜Kšœ žœ˜#Kšžœžœžœ˜Kšžœžœžœžœ˜šœžœ˜*Kšœ0˜0—K˜Kšœžœžœžœ˜/šœžœžœ˜ Kšœžœžœ˜NK˜—šœžœžœ˜ Kš œžœžœžœžœ˜Kšœž˜ K˜—K˜Kšœ žœžœ˜&K˜šœ žœžœ˜Kšœ<™<—šœ žœžœ˜Kšœ0™0—K˜—šΟnœžœ˜(Kš œžœžœžœžœ˜7Kšœ žœ˜Kš œžœžœžœžœ˜ Kš œžœžœžœžœ˜K˜˜šžœ˜Kšžœžœ˜Kšžœ žœ˜K˜—K˜Kšœ#žœ˜>Kšœ˜Kšœ&žœ˜-šžœžœžœ#žœ˜Mšžœžœžœžœžœžœ(žœžœž˜Xšžœ žœž˜Kšœžœ žœ ˜ Kšœžœ žœ˜/Kšžœžœ˜—Kšžœ˜—Kšžœ#˜*K˜—K˜Kšœ9˜9Kšœ˜Kšœ žœ ˜/šœ˜Kšœ ˜ Kšœ˜Kšœ-˜-Kšœ˜—Kšžœ˜Kšžœžœ˜K˜—K˜K˜—šŸ œžœ˜0Kšœžœ˜7Kšœžœ˜%Kš œžœžœžœžœ˜ Kšœžœ˜ Kšœžœ˜Kš œžœžœžœžœ˜7Kšœ žœžœžœ˜$Kšœžœžœ žœ˜-K˜š žœžœžœžœžœžœž˜=K˜šœ#žœ˜3Kš œžœžœžœ žœžœ˜5Kšœ˜—Kšœ˜šžœžœ˜Kšœ,™,Kšœžœ˜Kšœ žœžœ*˜=K˜ K˜—KšœJ˜JKšžœ˜—š žœžœžœžœ"žœžœž˜DKšœV˜VKšžœ˜—K˜šœ1˜1Kš œžœžœžœ žœžœ˜5Kšœ˜—Kšœ'˜'Kšœ>˜>Kšžœ˜KšœI˜IKšžœG˜LK˜K˜—šŸ œžœ˜/K™²Kš œžœžœžœžœ˜7Kšœžœ˜ Kšœžœ˜ Kš œžœžœžœžœ˜ Kšœžœ˜*K˜Kšœ<˜<˜šžœ˜Kšžœžœ˜Kšžœ žœ˜K˜—K˜Kšœ#žœ˜>Kšœ˜Kšœ&žœ˜-K™K˜Kšœ4žœžœ˜Všž˜Kšœžœ˜ —K˜—šžœ˜K˜—Kšœ˜šžœžœž˜Kšœžœ˜"Kšœ˜Kšžœ!˜(—K˜ K˜šžœž˜ Kšžœžœ.˜9Kšžœ˜šœ™Kšœ.™.Kšœ9™9Kšœ7™7Kšœ9™9—Kšœ+˜+šžœ žœžœ˜Kšœ$˜$šžœžœžœ˜Kš œžœžœžœžœžœg˜˜Kšœ3˜3šžœžœž˜Kšœžœ+˜4Kšœžœ(˜1Kšžœ˜—Kšœ?žœ˜FK˜—K˜—K˜—K˜K˜—šŸ œžœ˜,K™UKšœ3˜3šžœžœžœ˜Kšœ+˜+šžœ žœžœ˜Kšœ"˜"šžœ žœžœ˜Kšœ1™1Kšœ*˜*Kšœ*˜*Kšœ)˜)Kšœ*˜*Kšœ˜Kšœ?žœ˜FK˜—Kšžœ˜K˜—K˜—K˜K˜—•StartOfExpansion‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šŸœ˜)KšΠck~™~™K™jK™K™lK™CK™QK™-K™ K™#—Kšœžœ ˜'Kšœžœ˜ Kšœžœ˜ Kšœžœ˜#Kšœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœžœ˜ Kš œ žœžœ žœ žœ ˜?Kš œ žœžœžœ žœ ˜7Kšœ žœ˜K˜šžœ ž˜Kšœ˜Kšœ ˜ Kšœ˜Kšžœžœ˜—K˜Kšžœžœžœžœ˜K˜ Kšœ@˜@Kšœ,˜,šžœ žœžœžœžœžœžœ˜FKšœ/˜/KšœžœFžœ˜WK˜Kšœ˜Kšžœžœžœ˜2Kšœ˜K–)[s: ROPE, pos: INT _ 0, skip: ROPE]šœC˜CK˜—K˜K™3Kšœ˜Kšžœ žœ8˜HK™]Kš œ žœAžœžœžœ ˜‚KšœNžœ˜TK˜šžœ-žœ˜5Kšœ-™-KšœIžœ˜OKšœLžœ˜RKšœVžœ˜\KšœOžœ˜UKšœMžœžœ˜lKšœHžœ˜NKšœHžœ˜NKšœDžœ˜JKšœ˜—K˜˜Kšœžœžœ!˜9š žœžœžœžœ1žœ˜pš žœžœž œžœž˜6šžœžœ˜!šœ!˜!Kšœ:˜:—Kšžœ˜K˜—Kšž˜Kšžœ@žœžœ˜SKšžœ˜—K˜—K˜—Kšœo™oKšžœžœ5žœ˜XKšœ>˜>K˜K˜—šœžœžœ˜+K˜—šœžœžœ˜!Kšœžœ˜ Kšœžœž˜K˜K˜—šŸœ$˜6šžœžœ˜Kšœ<™šžœ˜Kšœ'˜'Kšœžœ˜"Kšœ˜——K˜ K˜Kšœ˜—šŸœžœ žœžœ˜-šžœ ˜ Kšžœ5žœ˜?Kšžœ'˜+—K˜—˜Kšœ#˜#Kšœ˜Kšœžœ˜KšœDžœ˜MKšœ˜—K˜K˜—™ K™šŸœ˜*š œžœ žœžœžœžœ™:Kšœžœžœ™G—šœ žœ ˜Kšœ žœžœ žœ™*Kšœ žœžœ˜Kšœ žœ˜!Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœ žœ˜ Kšœ žœ˜Kšœ˜Kšžœžœ)žœžœ˜Kšžœžœž˜Kšœ˜Kš œ žœžœžœžœ˜>Kšžœžœ˜—šžœ žœ˜Kšœžœ$˜,Kšœžœ˜"šœ-˜-Kšœžœ5˜9—K˜—Kš žœžœžœžœžœ˜3š žœ žœ žœžœ ž˜2Kšœ­™­šœ-˜-Kšœžœ(žœ˜2——Kšžœ žœ+˜=Kšœžœ>˜HK˜Kšžœ žœ!˜2šžœžœž˜šœ˜Kšœžœ ˜Kšœ˜šžœžœ˜Kšœ˜Kšœ˜K˜—K˜—Kšœ1˜1Kšžœ˜—K˜—šŸ œžœ˜'Kš œžœžœ žœ žœžœ™QKšœžœ ˜Kšœ žœ˜!Kšœ˜š žœžœ žœžœžœ˜/Kšœ™Kšœ˜šžœžœ˜Kšžœ˜Kšžœžœ žœžœ˜-K˜—Kšœ1˜1Kšžœžœ žœžœ˜*K˜—šžœžœž˜Kšœžœ˜.šœ˜Kšžœ˜šžœžœžœžœ˜1Kšžœžœ žœžœ˜+Kšžœ/˜1K˜—K˜—šžœ˜ Kš œžœžœ žœžœ˜@Kšžœ<˜>šžœ!˜#Kšžœžœ˜Kšžœ:˜>—šžœžœžœžœ˜1Kšžœžœ žœžœ˜+Kšžœ/˜1K˜—Kšžœ žœžœ1˜EK˜——Kšžœžœ žœžœ˜-K˜—šŸ œžœ žœ˜$šžœžœ žœžœ˜/Kšžœ˜Kšžœ!˜#Kšžœžœ˜ —K˜"Kšœ1˜1Kšœ žœ˜+šžœžœžœž˜7Kšœ%˜%—šžœž˜Kšœ&˜&—Kšœžœ˜ šžœžœž˜Kšœ7žœ˜Kšœžœ˜ Kšœžœ ˜(šžœ ž˜Kšœ'˜'Kšœ˜Kšžœ˜—K˜K˜—Kšœ žœžœ ˜!šœ žœžœ˜Kš œžœ žœ žœžœ˜GK˜—šŸœ˜$Kšœžœžœžœ™,Kšœžœ˜Kšœžœ˜Kšœ žœžœ˜š žœžœžœžœž˜7šžœ ž˜˜Kšžœžœžœ˜%Kšžœ0˜6K˜—˜Kšžœžœžœ˜%Kšžœ0˜6K˜—˜ Kšžœžœžœ˜!Kšžœ˜K˜—˜ Kšžœžœžœ˜!Kšžœ˜K˜—Kšž˜—Kšžœ˜—Kšžœ+žœ ˜@K˜K˜——™K˜šŸ œ˜&š œžœ žœžœžœžœ™:Kšœ žœžœžœ™X—Kšœžœžœ ˜Kš œ žœžœžœžœ˜.šœ žœ˜šŸœžœžœžœ˜"šœžœ žœ)˜?šœžœ ˜šžœ˜šžœ˜Kšžœ,˜.K˜—šžœ˜Kšžœ.˜0K˜——Kšœ˜Kšžœžœ˜ K˜—K˜—Kšœžœ˜#Kšœžœ˜"Kšžœ ˜Kšœ!˜!šžœ ž˜šžœ˜Kšžœ)˜+Kšœ˜K˜—šžœ˜Kšžœ&˜(Kšœ žœ˜ šžœž˜Kšœ7˜7—K˜——Kšžœ ˜K˜—Kšœžœ˜Kšœžœžœ˜Kšœžœ˜ Kšœžœžœžœ˜5Kšžœžœžœ˜"Kšœ˜šžœžœž˜šœžœ˜K™JKšœ žœ˜Kšœ˜šžœž˜ šžœ˜Kšœ+˜+Kšœ"˜"K˜—šžœ˜Kšœ.˜.Kšœ#˜#K˜——šžœžœžœžœ˜&KšžœK˜MKšœ˜Kšžœ˜K˜—K˜—šžœ˜ K™+Kšœ#˜#K˜ K˜——K˜—šž˜˜ Kšœžœ6žœ˜@Kšœžœžœ˜K˜—Kšžœ˜—˜K˜——šŸ œ˜&š œžœ žœžœžœžœ™:Kšœ žœžœžœ™X—Kšœžœžœ ˜Kš œ žœžœžœžœ˜.Kšœžœ"˜.šœ žœ˜Kšœžœ˜Kšœžœžœžœ˜5Kšžœžœžœ˜"Kšžœžœžœ˜:Kš œžœžœžœ žœ ˜Ašžœžœžœ˜KšžœL˜NKšžœ˜Kšœ˜—Kšžœ!˜#šžœžœž˜šžœ˜Kšœ˜Kšœ˜Kšœ˜—K˜Kšžœ˜—K˜—šž˜˜ šœžœ6žœ˜@Kšžœžœ˜—K˜—Kšžœ˜—K˜K˜——™K˜šŸ œžœ žœžœžœ žœžœžœžœžœ˜zKšœžœ˜Kšœ žœ˜#Kšœžœ˜Kšœžœ˜*Kšœ"žœ˜&Kšœžœ˜Kšœžœ˜Kšœ žœ#˜3Kšœžœ˜-Kšœžœ˜Kšžœ žœžœ˜Kšžœ žœžœ˜$Kšžœ žœžœ0˜EKšœ9˜9šžœ žœž˜Kšœ*˜*K˜Kšœ˜K˜šžœ˜šžœ˜Kšœ žœ˜+Kšžœžœžœ˜$Kšœ"˜"šžœž˜Kšœ žœ˜Kšœ˜Kšœ žœ˜Kšœžœ˜KšœC˜CKšœ ˜ Kšžœ'žœžœžœ˜Jšžœžœžœ˜-šœ˜KšœžœC˜I—Kšžœžœžœ žœ˜3K˜ K˜—Kšžœ˜—K˜—šžœ˜šžœž˜Kšœ˜Kšœ žœ˜Kšœ˜Kšœžœ˜KšœC˜CKšœžœC˜MKšžœžœžœ žœ˜3K˜ Kšž˜—K˜——Kšžœ˜—Kšœ(˜(Kšžœ˜K˜K˜—š Ÿ œžœžœžœžœ˜,Kšœ!™!Kšœ$™$Kšœžœ˜Kšœžœ˜š žœžœž œžœ ž˜%Kšœžœ˜šžœž˜ Kšœžœ˜K˜Kšžœ˜ —Kšžœ˜—Kšžœ ˜&K˜K˜—šŸ œžœžœžœ žœžœžœžœ˜NKšœžœ˜Kšœžœ˜Kšœžœ˜šžœž˜šžœž˜!K˜5KšœNžœ˜VKšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœžœ˜K˜K˜—š Ÿœžœžœžœžœžœ˜Tšžœžœžœ˜Kšœ˜Kšœ˜šžœ,žœžœž˜BKšœ žœ˜#Kšœžœ.˜9šžœ žœ˜Kšœ0™0Kšœ žœ˜!šžœ žœžœ žœ˜EKšœžœ žœ™=Kšœ˜Kšœ˜K˜—K˜—Kšžœ˜—K˜—K˜K˜—šŸœžœ(žœ˜GKšœKžœ,™{šžœžœž˜K˜#K˜&K˜ K˜šžœžœž˜K˜!šžœžœž˜Kšœ(žœ˜BKšœžœ1˜DKšžœ˜—K˜ Kšžœ˜—K˜Kšžœ˜—K˜K˜—š Ÿ œžœžœžœžœžœ˜[KšœžœG˜QKš œžœžœžœžœ˜šž˜Kš œžœžœžœ žœΟr œžœ˜LKšœžœ˜"Kšžœžœžœ˜$šžœ'žœžœž˜=Kšœžœ˜Kšœ žœžœ˜Kšœ˜šžœžœžœ˜'šœžœ ˜Kšœ!˜!Kšœ&˜&Kšœ ž˜šœžœ ˜ šžœ ž˜Kšœ%žœ˜*Kšž˜——Kšœ ˜ —Kšœžœ˜,K˜—Kšžœ˜—Kšžœžœžœžœ˜=Kšžœ˜—Kšœ˜K˜——™ K™šŸ œžœžœžœžœžœžœžœžœ˜jšœ/™/KšœW™W—Kšœžœ˜Kšœžœ˜Kšœžœ˜ šžœ>ž˜EKšœB˜BKšžœžœžœ˜K˜Kšžœ˜—Kšžœ˜ K˜K˜—šŸœžœžœžœžœžœžœžœ˜@š žœžœžœžœžœžœž˜2Kš žœžœžœžœžœ˜6Kšžœ˜—Kšžœžœ˜ K˜K˜—šœžœžœ ˜K˜—š Ÿ œž œžœžœžœ˜8Kšžœžœžœ˜K˜Kšžœ?žœ˜NKšžœ˜K™—šŸœžœ˜Kšžœ žœžœ˜!Kšžœ˜K˜K˜—šŸœžœžœ"˜BKšœ6˜6Kšžœžœžœ9˜JK˜K˜—šŸœžœžœ˜QKšœ,žœ™Cšž˜šžœ0žœž˜?šœ˜Kš žœžœžœžœžœžœ˜7—Kšžœ˜—šžœ,žœž˜;Kšœ'žœ˜.Kšžœ˜—Kšžœžœ˜ Kšžœ˜—K˜K˜—šŸ œžœ*žœ˜Ešžœ-žœž˜<šœ˜Kšœžœ0˜IKšœ#˜#K˜—Kšžœ˜—K˜K˜——™K˜šŸœžœ˜Kšœ žœ–˜£Kšœmžœ˜tKšœ€žœ˜‡Kšœe˜eKšœS˜SKšœ“˜“Kšœn˜nKšœ‡˜‡Kšœ{˜{Kšœs˜sK˜šœ™K˜Kšœ?˜?KšœJ˜JKšœA˜AKšœL˜LK˜—šœ™K™šœ˜K˜!K˜θ—šœ˜K˜!K˜`—šœ˜K˜$K˜?K˜——K˜Kšœ7˜7K˜K˜—K˜—K˜Kšžœ˜K˜—…—yβ΅Ρ