DIRECTORY Commander USING [CommandProc, Handle, Register], CommandTool USING [NextArgument], Convert USING [ IntFromRope ], Idle USING [ IsIdle ], IO, List USING [Length], Process USING [ Detach, SecondsToTicks, SetTimeout ], RuntimeError USING [ UNCAUGHT ], Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, Substr], SymTab USING [ Create, EachPairAction, Fetch, Ref, Pairs, Store ], UserProfile USING [ Token ], ViewerClasses USING [ Menu, MenuEntry, Viewer ], ViewerOps USING [ EnumerateViewers, EnumProc ], ViewerTools USING [ TiogaContents ], WalnutDefs USING [dontCareMsgSetVersion ], WalnutOps USING [AddMsg, CreateMsg, dontCareDomainVersion, GetHandleForRootfile, GetMsg, MoveMsg, MsgExists, MsgSetExists, MsgSetNames, GetDisplayProps, GetMsgText, MsgSetVersion, RemoveMsg, SetHasBeenRead, SizeOfMsgSet, MsgsInSetEnumeration, WalnutOpsHandle ], WalnutNewMail USING [ CheckMailBoxes ], WalnutWindowPrivate USING [ WalnutHandle ] ; WalnutKeyboardImpl: CEDAR MONITOR IMPORTS Commander, CommandTool, Convert, Idle, IO, List, Process, Rope, RuntimeError, SymTab, UserProfile, ViewerOps, WalnutOps, WalnutNewMail SHARES ViewerClasses ~ { OPEN IO; ROPE: TYPE = Rope.ROPE; Handle: TYPE = WalnutOps.WalnutOpsHandle; DB: TYPE = REF DBBody; DBBody: TYPE = RECORD [ dbName: ROPE, dbFileName: ROPE, handle: Handle _ NIL, retryCount: INT _ 0 ]; Entries: TYPE ~ REF EntrySequence; EntrySequence: TYPE ~ RECORD[e: SEQUENCE len: NAT OF EntryRecord]; EntryRecord: TYPE ~ RECORD [ msgID: ROPE, toc: ROPE, tocValid: BOOL, hasBeenRead: BOOL, deleted: BOOL_FALSE]; MsgSet: TYPE = REF MsgSetBody; MsgSetBody: TYPE = RECORD [ db: DB, -- belongs to this database. msgSetName: ROPE, msgSetVersion: WalnutOps.MsgSetVersion, entries: Entries ]; NewFilter: TYPE = {none, read, unread}; dbs: SymTab.Ref; -- Indexed by db short name (e.g., "default", "voice", "old") msgSets: SymTab.Ref; -- Cached indices, indexed by dbname.msgSetName defaultSource: MsgSet; defaultDest: MsgSet; thisSource: MsgSet; thisDest: MsgSet; firstMessageNumber: INT_1; lastMessageNumber: INT_1; thisSourceFilter: ROPE_NIL; thisSourceNewFilter: NewFilter_$none; disableIdleReqt: BOOL_FALSE; checkMinutes: INT _ 10; ListDBsCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; ListOneDB: SymTab.EachPairAction = { db: DB _ NARROW[val]; cmd.out.PutF[" %g: %g\n", rope[db.dbName], rope[db.dbFileName]]; }; []_SymTab.Pairs[dbs, ListOneDB]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; ListMsgSetsCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; msgSetNames: LIST OF ROPE; [result, msg] _ ParseArguments[cmd: cmd, dbOnly: TRUE]; IF result = $Failed THEN RETURN; msgSetNames _ WalnutOps.MsgSetNames[thisSource.db.handle].mL; cmd.out.PutF["Message sets from database %g\n", rope[thisSource.db.dbName]]; FOR mN: LIST OF ROPE _ msgSetNames, mN.rest WHILE mN#NIL DO cmd.out.PutF[" %g\n", rope[mN.first]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; SelectMsgSetCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; [result, msg] _ ParseArguments[cmd]; IF result = $Failed THEN RETURN; defaultSource _ thisSource; defaultDest _ thisDest; cmd.out.PutF["Selected message sets: Source = %g, Dest = %g\n", rope[FullName[defaultSource]], rope[FullName[defaultDest]]]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; ListMsgsInMsgSetCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; refresh: BOOL _ cmd.procData.clientData#$Again; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: refresh, fullRange: TRUE]; cmd.out.PutF["Messages in message set %g:\n", rope[FullName[thisSource]]]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO hasBeenRead, deleted: BOOL; tocEntry: ROPE; [hasBeenRead, deleted, tocEntry] _ GetTocEntry[thisSource, i]; IF FilterOut[i] THEN LOOP; cmd.out.PutF["%g %3d %g%g\n", char[IF hasBeenRead THEN '\040 ELSE '?], int[i], rope[tocEntry], rope[IF deleted THEN " (deleted)" ELSE ""]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; PrintMsgInMsgSetCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; contents: REF TEXT; handle: Handle; cr: ROPE; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: TRUE]; IF result = $Failed THEN RETURN; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO hasBeenRead, deleted: BOOL; tocEntry, msgID: ROPE; [hasBeenRead, deleted, tocEntry] _ GetTocEntry[thisSource, i]; IF FilterOut[i] THEN LOOP; msgID _ thisSource.entries[i-1].msgID; contents _ WalnutOps.GetMsgText[handle, msgID, contents]; TRUSTED { cr _ LOOPHOLE[contents]; }; cmd.out.PutF["-- Message %g:%g\n%g\n\n", int[i], rope[IF deleted THEN " (deleted)" ELSE ""], rope[cr]]; IF NOT thisSource.entries[i-1].hasBeenRead THEN { WalnutOps.SetHasBeenRead[handle, msgID]; thisSource.entries[i-1].hasBeenRead _ TRUE; }; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; MoveMsgCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; op: ROPE _ "Moved "; destExists: BOOL; move: BOOL _ cmd.procData.clientData = $Move; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; destExists _ WalnutOps.MsgSetExists[opsHandle: handle, name: thisDest.msgSetName, msDomainVersion: WalnutOps.dontCareDomainVersion].exists; IF ~destExists THEN RETURN[$Failed, "No destination message set"]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't move message %g from set %g (deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; IF move THEN { thisSource.entries[i-1].deleted _ TRUE; []_WalnutOps.MoveMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], [thisDest.msgSetName]]; } ELSE { op _ "Copied "; []_WalnutOps.AddMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], [thisDest.msgSetName]]; }; cmd.out.PutF["%gmessage %g from set %g to %g\n", rope[op], int [i], rope[FullName[thisSource]], rope[FullName[thisDest]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; XferMsgCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; sourceHandle, destHandle: Handle; destExists: BOOL; [result, msg, sourceHandle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; destHandle _ thisDest.db.handle; destExists _ WalnutOps.MsgSetExists[opsHandle: destHandle, name: thisDest.msgSetName, msDomainVersion: WalnutOps.dontCareDomainVersion].exists; IF ~destExists THEN RETURN[$Failed, "No destination message set"]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO pos: INT; msgID, msgName: ROPE; tc: ViewerTools.TiogaContents; IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't transfer message %g from set %g (deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; msgID _ msgName _ thisSource.entries[i-1].msgID; pos _ Rope.Find[msgID, "$"]; IF pos # 0 THEN msgName _ msgID.Substr[pos]; tc _ WalnutOps.GetMsg[sourceHandle, msgID].contents; IF NOT WalnutOps.MsgExists[destHandle, msgName] THEN { WalnutOps.CreateMsg[destHandle, msgID, tc]; WalnutOps.SetHasBeenRead[destHandle, msgName]; }; IF NOT thisDest.msgSetName.Equal["active", FALSE] THEN []_WalnutOps.MoveMsg[destHandle, msgID, ["active"], [thisDest.msgSetName]]; cmd.out.PutF["Transferred message %g from set %g to %g\n", int [i], rope[FullName[thisSource]], rope[FullName[thisDest]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; DeleteMsgCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't delete message %g from set %g (already deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; thisSource.entries[i-1].deleted _ TRUE; []_WalnutOps.RemoveMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], WalnutDefs.dontCareMsgSetVersion]; cmd.out.PutF["Deleted message %g from set %g\n", int [i], rope[FullName[thisSource]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; GetNewMailCmd: Commander.CommandProc = { SELECT Check[] FROM $running => NULL; $retried => RETURN[$Failed, "** Walnut had to be restarted; wait a few seconds and try again **"]; $failed => RETURN[$Failed, "** Something broke during attempt to restart or retrieve mail. **"]; $quit => RETURN[$Failed, "** Walnut was demanding to be stopped, so it was. **"]; $timedout => RETURN[$Failed, "** Walnut cannot be restarted, apparently. **"]; $noNewMail => RETURN[$Failed, "** No Walnut instance can read mail. **"]; ENDCASE => ERROR; }; Checker: PROC = TRUSTED { DO IF disableIdleReqt OR Idle.IsIdle[] THEN [] _ Check[]; Wait[]; ENDLOOP; }; Wait: ENTRY PROC = TRUSTED { ENABLE UNWIND => NULL; checker: CONDITION; Process.SetTimeout[@checker, Process.SecondsToTicks[60*checkMinutes]]; WAIT checker; }; Check: ENTRY PROC RETURNS[whatHappened: WhatHappened_$failed] = TRUSTED { ENABLE { RuntimeError.UNCAUGHT => GOTO Failed; UNWIND => NULL; }; db: DB; [whatHappened, db] _ CheckAndRetry[]; IF whatHappened = $running THEN WalnutNewMail.CheckMailBoxes[db.handle]; EXITS Failed => NULL; }; WhatHappened: TYPE = { running, retried, timedout, failed, quit, noNewMail }; maxRetryCount: INT _ 72; -- 12 hours without success and we quit; should we? CheckAndRetry: INTERNAL PROC RETURNS [whatHappened: WhatHappened_$noNewMail, db: DB_NIL] = { CheckOne: ViewerOps.EnumProc ~ { menu: ViewerClasses.Menu _ v.menu; menuLine: ViewerClasses.MenuEntry; quitEntry: ViewerClasses.MenuEntry; retryEntry: ViewerClasses.MenuEntry; db1: DB; waHoppen: WhatHappened _ $running; IF menu = NIL THEN RETURN; menuLine _ menu.lines[0]; IF menuLine = NIL THEN RETURN; db1 _ GetDBFromWalnutHandle[menuLine.clientData]; IF db1=NIL THEN RETURN; quitEntry _ FindEntry[menuLine, "Quit"]; retryEntry _ FindEntry[menuLine, "Retry"]; IF quitEntry=NIL THEN db1.retryCount _ 0 ELSE IF retryEntry#NIL THEN waHoppen _ $retried; IF db1.retryCount > maxRetryCount THEN { retryEntry _ quitEntry; waHoppen _ $timedout; }; IF retryEntry#NIL THEN { db1.retryCount _ db1.retryCount + 1; retryEntry.proc[parent: v, clientData: retryEntry.clientData]; }; IF FindEntry[menuLine, "NewMail"]#NIL THEN { db _ db1; whatHappened _ waHoppen; }; }; ViewerOps.EnumerateViewers[CheckOne]; }; FindEntry: PROC[menuLine: ViewerClasses.MenuEntry, menuLabel: ROPE] RETURNS [entry: ViewerClasses.MenuEntry] ~ { FOR entry _ menuLine, entry.link WHILE entry#NIL DO IF entry.name.Equal[menuLabel, FALSE] THEN RETURN; ENDLOOP; }; GetDBFromWalnutHandle: PROC[wH: REF] RETURNS[ db: DB_NIL ] ~ { handle: Handle; IsItTheOne: SymTab.EachPairAction = { oneDB: DB _ NARROW[val]; IF handle.rootName.Equal[oneDB.dbFileName, FALSE] THEN { []_SetHandle[oneDB]; db _ oneDB; RETURN[quit:TRUE]; }; }; WITH wH SELECT FROM wwh: WalnutWindowPrivate.WalnutHandle => handle _ wwh.opsH; ENDCASE => RETURN; []_SymTab.Pairs[dbs, IsItTheOne]; }; GetDBFromName: PROC[dbName: ROPE] RETURNS[ db: DB_NIL] ~ { db _ NARROW[dbs.Fetch[dbName].val]; }; CacheMsgSet: PROC[cmd: Commander.Handle, msgSet: MsgSet, refresh: BOOL_FALSE] RETURNS[handle: Handle] = { ListLength: PROC [l: LIST OF ROPE] RETURNS [i: INT] ~ TRUSTED { RETURN [List.Length[LOOPHOLE[l]]]; }; numMsgs: INT; i: INT; version1, version2: WalnutOps.MsgSetVersion; msgs: LIST OF ROPE; msgSetName: ROPE _ msgSet.msgSetName; entries: Entries _ msgSet.entries; handle _ SetHandle[msgSet.db]; IF (NOT refresh) AND entries#NIL THEN RETURN; -- have it already [numMsgs, version1] _ WalnutOps.SizeOfMsgSet[handle, msgSetName]; IF entries#NIL AND version1 = msgSet.msgSetVersion THEN RETURN; DO [numMsgs, version1] _ WalnutOps.SizeOfMsgSet[handle, msgSetName]; [msgs, version2] _ WalnutOps.MsgsInSetEnumeration[handle, msgSetName]; IF version2=version1 THEN EXIT; ENDLOOP; IF numMsgs # (i _ ListLength[msgs]) THEN { cmd.out.PutF["*** Curious that Walnut says %g messages but provided %g TOC entries\n", int[numMsgs], int[i]]; numMsgs _ i; }; msgSet.msgSetVersion _ version2; entries _ msgSet.entries _ NEW[EntrySequence[numMsgs]]; i_-1; FOR ml: LIST OF ROPE _ msgs, ml.rest WHILE ml#NIL DO entries[i_i+1].msgID _ ml.first; ENDLOOP; cmd.out.PutF["Message set %g contains %g messages\n", rope[FullName[msgSet]], int[i+1]]; }; FilterOut: PROC[msgNo: INT] RETURNS[BOOL] = {-- TRUE IFF entry does NOT match filter tocEntry: ROPE; read: BOOL; [read,,tocEntry] _ GetTocEntry[thisSource, msgNo]; RETURN[ (SELECT thisSourceNewFilter FROM $read => NOT read, $unread => read, ENDCASE => FALSE) OR (thisSourceFilter#NIL AND Rope.Find[s1: tocEntry, s2: thisSourceFilter, case: FALSE]<0)]; }; ParseRange: PROC [ cmd: Commander.Handle, refresh: BOOL_FALSE, fullRange: BOOL_FALSE] RETURNS [result: REF ANY_NIL, msg: ROPE_NIL, handle: Handle_NIL] ~ { [result, msg] _ ParseArguments[cmd: cmd]; IF result = $Failed THEN RETURN; handle _ CacheMsgSet[cmd, thisSource, refresh]; IF thisSourceFilter#NIL OR thisSourceNewFilter # $none THEN fullRange _ TRUE; IF fullRange THEN [result, msg] _ ParseArguments[cmd: cmd, rangeDefaults: $all]; IF result = $Failed THEN RETURN; IF firstMessageNumber = lastMessageNumber AND lastMessageNumber = -1 THEN RETURN[$Failed, "No message range was specified"]; IF firstMessageNumber > thisSource.entries.len THEN RETURN[$Failed, "There aren't that many"]; IF firstMessageNumber <= 0 THEN RETURN[$Failed, "There aren't that few"]; }; ParseArguments: PROC[ cmd: Commander.Handle, rangeDefaults: {none, all}_$none, dbOnly: BOOL_FALSE, sameDB: BOOL _ FALSE] RETURNS[result: REF ANY _ NIL, msg: ROPE _ NIL] = { parsing: BOOL _ FALSE; { ENABLE IO.EndOfStream => IF parsing THEN GOTO Failed ELSE CONTINUE; s: IO.STREAM _ IO.RIS[cmd.commandLine]; arg: ROPE_NIL; quoted: BOOL; GetToken: PROC RETURNS[token: ROPE, quoted: BOOL_FALSE] = { ln: INT; token _ IO.GetCedarTokenRope[s].token; ln _ token.Length[]; IF ln<2 OR token.Fetch[0]#'" THEN RETURN; quoted _ TRUE; token _ token.Substr[start: 1, len: ln-2]; }; ParseMsgSet: PROC[msgSetName: ROPE_NIL] RETURNS[msgSet: MsgSet] ~ { dbName: ROPE _ "default"; parsing _ TRUE; IF msgSetName=NIL THEN msgSetName _ GetToken[].token; IF s.PeekChar[]='. OR dbOnly THEN { dbName _ msgSetName; msgSetName _ NIL; IF dbOnly THEN msgSetName _ "Active" ELSE DO [] _ s.GetChar[]; msgSetName _ msgSetName.Concat[GetToken[].token]; IF s.PeekChar[]#'. THEN EXIT; msgSetName _ msgSetName.Concat["."]; ENDLOOP; }; [result, msg, msgSet] _ GetMsgSet[dbName, msgSetName]; parsing _ FALSE; }; lastSource, lastDest: MsgSet; lastSourceFilter: ROPE; lastSourceNewFilter: NewFilter; lastFirstMessageNumber, lastLastMessageNumber: INT; lastSource _ thisSource; thisSource _ defaultSource; lastDest _ thisDest; thisDest _ defaultDest; lastSourceFilter _ thisSourceFilter; thisSourceFilter _ NIL; lastSourceNewFilter _ thisSourceNewFilter; thisSourceNewFilter _ $none; lastFirstMessageNumber_firstMessageNumber; lastLastMessageNumber_lastMessageNumber; firstMessageNumber _ lastMessageNumber _ -1; WHILE result=NIL AND ([arg, quoted]_GetToken[]).token # NIL DO c: CHAR _ IF arg.Length[]>=1 THEN arg.Fetch[0] ELSE '\000; SELECT TRUE FROM NOT quoted AND arg.Equal["unread", FALSE] => thisSourceNewFilter _ $unread; NOT quoted AND arg.Equal["read", FALSE] => thisSourceNewFilter _ $read; NOT quoted AND arg.Equal["from", FALSE] => thisSource _ ParseMsgSet[]; NOT quoted AND arg.Equal["to", FALSE] => thisDest _ ParseMsgSet[]; NOT quoted AND arg.Equal["with", FALSE] => { parsing _ TRUE; thisSourceFilter _ IO.GetRope[s]; IF thisSourceFilter.Length[]>1 THEN thisSourceFilter _ thisSourceFilter.Substr[start: 1, len: thisSourceFilter.Length[]-2]; parsing _ FALSE; }; NOT quoted AND (arg.Equal["it", FALSE] OR arg.Equal["them", FALSE]) => { thisSource _ lastSource; thisDest _ lastDest; thisSourceFilter _ lastSourceFilter; thisSourceNewFilter _ lastSourceNewFilter; firstMessageNumber _ lastFirstMessageNumber; -- [sic] lastMessageNumber _ lastLastMessageNumber; -- [mea culpa] }; c = '[ => { parsing _ TRUE; arg _ GetToken[].token; IF ~ (arg.Fetch[0] IN ['0..'9]) THEN RETURN[$Failed, "Non-number in message range"]; firstMessageNumber _ Convert.IntFromRope[arg]; arg _ GetToken[].token; IF ~arg.Equal[".."] THEN RETURN[$Failed, "Missing ellipsis in message range"]; arg _ GetToken[].token; IF ~ (arg.Fetch[0] IN ['0..'9]) THEN RETURN[$Failed, "Non-number in message range"]; lastMessageNumber _ Convert.IntFromRope[arg]; arg _ GetToken[].token; IF ~arg.Equal["]"] THEN RETURN[$Failed, "Message range not properly terminated"]; parsing _ FALSE; }; c IN ['0..'9] => { firstMessageNumber _ lastMessageNumber _ Convert.IntFromRope[arg]; }; c IN ['a..'z], c IN ['A..'Z] => thisSource _ ParseMsgSet[arg]; ENDCASE => RETURN[$Failed, "Unrecognized input"]; ENDLOOP; EXITS Failed => RETURN[$Failed, "Error while reading command"]; }; IF rangeDefaults = $all THEN { IF firstMessageNumber = -1 THEN firstMessageNumber _ 1; IF lastMessageNumber = -1 THEN lastMessageNumber _ thisSource.entries.len; }; IF sameDB AND thisSource.db#thisDest.db THEN RETURN[$Failed, "Source and destination message sets must be from same Walnut data base."]; }; GetMsgSet: PROC[dbName, msgSetName: ROPE] RETURNS [result: REF ANY, msg: ROPE, msgSet: MsgSet_NIL] ~ { key: ROPE _ Rope.Cat[dbName, ".", msgSetName]; db: DB; msgSet _ NARROW[msgSets.Fetch[key].val]; IF msgSet#NIL THEN { []_SetHandle[msgSet.db]; RETURN; }; db _ GetDBFromName[dbName]; IF db=NIL THEN { result _ $Failed; msg _ "DB Name not registered; use WalnutKeyboard command to register."; RETURN; }; [] _ SetHandle[db]; msgSet _ NEW[MsgSetBody _ [db, msgSetName]]; [] _ msgSets.Store[key, msgSet]; }; SetHandle: PROC[db: DB] RETURNS[Handle] = { rootFileName: ROPE _ db.dbFileName; RETURN[db.handle _ WalnutOps.GetHandleForRootfile[rootFileName]]; }; GetTocEntry: PROC[msgSet: MsgSet, i: INT] RETURNS[hasBeenRead: BOOL_FALSE, deleted: BOOL_FALSE, toc: ROPE_NIL]~{ entries: Entries _ msgSet.entries; i _ i-1; IF NOT entries[i].tocValid THEN { [hasBeenRead, toc] _ WalnutOps.GetDisplayProps[msgSet.db.handle, entries[i].msgID]; entries[i].tocValid _ TRUE; entries[i].toc _ toc; entries[i].hasBeenRead _ hasBeenRead; } ELSE { toc _ entries[i].toc; hasBeenRead _ entries[i].hasBeenRead; deleted _ entries[i].deleted; }; }; FullName: PROC[msgSet: MsgSet] RETURNS [fullName: ROPE] ~ { RETURN[ IO.PutFR["%g.%g", rope[msgSet.db.dbName], rope[msgSet.msgSetName]]]; }; FlushCmd: Commander.CommandProc = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; msgSets _ SymTab.Create[case: FALSE]; [result, msg, defaultSource] _ GetMsgSet["default", "Active"]; IF result=$Failed THEN RETURN; defaultDest _ defaultSource; cmd.out.PutF["Flushed message sets, default source and dest are default.Active\n"]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; WalnutKbdCmd: Commander.CommandProc = { dbName: ROPE; rootFile: ROPE; dbName _ CommandTool.NextArgument[cmd]; rootFile _ CommandTool.NextArgument[cmd]; [result, msg, dbName, rootFile] _ WalnutKbdInit[dbName, rootFile]; IF result=$Failed THEN RETURN; cmd.out.PutF["Registered %g: %g\n", rope[dbName], rope[rootFile]]; }; WalnutKbdInit: PROC[dbNameIn, rootFileIn: ROPE_NIL] RETURNS[result: REF ANY_NIL, msg: ROPE_NIL, dbName: ROPE_NIL, rootFile: ROPE_NIL] = { ENABLE RuntimeError.UNCAUGHT => GOTO Failed; db: DB; dbName _ dbNameIn; rootFile _ rootFileIn; IF dbName = NIL THEN { dbName _ "default"; rootFile _ UserProfile.Token["Walnut.WalnutRootFile"]; } ELSE IF rootFile=NIL THEN RETURN[$Failed, "No root file name provided"]; db _ GetDBFromName[dbName]; IF db=NIL THEN { db _ NEW[DBBody_[dbName, NIL]]; []_dbs.Store[dbName, db]; }; db.dbFileName _ rootFile; db.handle _ NIL; IF defaultSource=NIL THEN defaultSource _ defaultDest _ GetMsgSet[dbName, "Active"].msgSet; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; Commander.Register["WKDBs", ListDBsCmd, "List available Walnut data bases"]; Commander.Register["WKSets", ListMsgSetsCmd, "List Walnut message sets"]; Commander.Register["WKSelect", SelectMsgSetCmd, "Select working Walnut message set\n Usage: WKSelect |"]; Commander.Register["WKListTOC", ListMsgsInMsgSetCmd, "List messages in message set, as specified by filters and message range.\n Usage: WKList ", $List]; Commander.Register["WKSameTOC", ListMsgsInMsgSetCmd, "List messages in message set, without consulting Walnut again.\n Usage: WKSame ", $Again]; Commander.Register["WKDisplay", PrintMsgInMsgSetCmd, "Display a message designated by number from a selected message set\n Usage: WKDisplay "]; Commander.Register["WKPoll", GetNewMailCmd, "Poll Mail services for new mail"]; Commander.Register["WKMove", MoveMsgCmd, "Move messages from source message set to destination message set.\n Usage: WKMove ", $Move]; Commander.Register["WKCopy", MoveMsgCmd, "Copy messages from source message set to destination message set.\n Usage: WKCopy ", $Copy]; Commander.Register["WKTransfer", XferMsgCmd, "Transfer messages from source message set in one database to destination message set in another.\n Usage: WKTransfer "]; Commander.Register["WKDelete", DeleteMsgCmd, "Delete messages from source message set.\n Usage: WKDelete "]; Commander.Register["WKFlush", FlushCmd, "Flush all cached message sets. Restore default to default.Active."]; Commander.Register["WalnutKeyboard", WalnutKbdCmd, "Usage: Walnutkeyboard \nAdds mapping from to to list of available Walnut databases\nDefault ='default'; default from user profile"]; dbs _ SymTab.Create[case: FALSE]; msgSets _ SymTab.Create[case: FALSE]; TRUSTED { Process.Detach[FORK Checker[]]; }; []_WalnutKbdInit[NIL, NIL]; }. WalnutKeyboardImpl.mesa Copyright Σ 1988, 1989, 1990 by Xerox Corporation. All rights reserved. Swinehart, December 19, 1990 5:36 pm PST Rick Beach, April 29, 1989 2:16:13 pm PDT Notes: 1. Think about replacing some of the notorious global variables with something more reasonable. 2. Next requirement is for the most minimal form of editor. In some sense, the existing Rope commands with some aka's should do it. 3. Find out how to quote things in the command tool. 4. New syntax for commands: Message set syntax: := | . | it | them It and them are pronouns representing the search rule used in the last completed command. Arguments: source: from (from is default if nothing specified) destination: to messages: message: range: [ .. ] list: [, , ...., ] filter: with | unread | read Filter must be the last item in the command line. The remainder of the command line, after "with ", is matched in a case-insensitive way against each entire TOC entry in the indicated message set. Only TOC entries (or messages, depending on the command) matching both the message range and the filter will participate in the command. The read and unread options permit focus of attention on just new or just old mail. Commands: WKDBs -- lists the available Walnut databases WKSets -- lists message sets for the named dbs, default default WKNew -- pushes NewMail button for any open DB that has one. WKSelect | -- sets default value WKListTOC (parameters may be in any order) WKSameTOC (Does not refresh cache first! See below.) WKDisplay WKMove (parameters may be in any order) WKCopy (parameters may be in any order) WKTransfer (databases may (should) differ) WKDelete (WalnutOps says "Deleted" is not a legal destination for Move or copy) WKLogin (system will prompt for names, passwords; legal only when idle.) WKFlush -- Dumps all message set caches. Pragmata: To save time, the program caches information about message sets. Only the WKListTOC command will refresh the caches, although all of the message-specific commands will generate one if it doesn't yet exist. In particular, commands that move messages from one set to another do not use the Walnut version features (so that many can happen sequentially), and do not change the user-visible numbering of messages within either message set. WKListToc will refresh the cache of the named message set if the underlying message set has changed. WKSameToc will not. (Useful while performing a series of moves, copies, or deletes). [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] running: Walnut is not demanding a Quit or Retry. retried: Walnut was demanding it, so we "pushed" the Retry button. timedout: We have retried too many times. failed: Something bad happened. quit: Walnut was demanding we push "Quit", so we did. [key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE] dbOnly -- Ignore message set name quantities, dummy up index entry bearing corresponding dbDesc. sameDB -- source and dest must come from same db. Returns same as commandProc to clue in caller as to problems. Problems include no such (registered) DB and nonexistent DB. Implementation of "it", "them" [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] Κ"– "cedar" style•NewlineDelimiter ˜code™KšœH™HK™(Kšœ)™)K˜—šΟb™KšΟy_™_K™„Kšž4™4™™š™KšG™GK™Y——™ Kš9™9Kš™™ Kš™Kš™K™*—™-K™€——™ Kš.™.KšJ™JKš<™Kšœ-™IKš™Kšœ/™IKšœ/™IKšœ.™LKšœG™cKšH™HKš(™(—™ K™΅K™»———K˜K˜šΟk ˜ Kšœ Ÿœ!˜0Kšœ Ÿœ˜!KšœŸœ˜KšœŸœ ˜KšŸœ˜KšœŸœ ˜KšœŸœ(˜5Kšœ ŸœŸœ˜ KšœŸœ+Ÿœ ˜CKšœŸœ6˜BKšœ Ÿœ ˜KšœŸœ˜0Kšœ Ÿœ ˜/Kšœ Ÿœ˜$Kšœ Ÿœ˜*Kšœ Ÿœφ˜…KšœŸœ˜'KšœŸœ˜*K˜K˜—K˜šΠlnœŸœŸ˜!Kš Ÿœ ΟtΠkt‘œ!‘œ)‘˜ŽKšŸœ˜KšŸœŸœ˜K˜KšŸœŸœŸœ˜K˜KšœŸœ˜)K˜KšœŸœŸœ˜šœŸœŸœ˜KšœŸœ˜ Kšœ Ÿœ˜KšœŸœ˜Kšœ Ÿœ˜K˜K˜—Kšœ ŸœŸœ˜"Kš œŸœŸœŸœŸœŸœ˜Bšœ ŸœŸœ˜Kš œŸœŸœ ŸœŸœ ŸœŸœ˜P—K˜KšœŸœŸœ ˜šœ ŸœŸœ˜Kšœ Οc˜%Kšœ Ÿœ˜Kšœ'˜'Kšœ˜K˜K˜—šœ Ÿœ˜'K˜—KšœN˜N˜DK˜—K˜K˜Kšœ˜Kšœ˜K˜KšœŸœ˜KšœŸœ˜KšœŸœŸœ˜Kšœ%˜%K˜KšœŸœŸœ˜KšœŸœ˜K˜•StartOfExpansionL -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]šΟn œ˜%Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,–= -- [key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]š€ œ˜$KšΠck9™9Kšœ Ÿœ˜šœ˜Kšœ˜Kšœ˜—K˜—K˜ KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œ˜)Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,Kšœ ŸœŸœŸœ˜Kšœ1Ÿœ˜7KšŸœŸœŸœ˜ Kšœ=˜=KšœL˜Lš ŸœŸœŸœŸœŸœŸœŸ˜;Kšœ'Ÿœ˜/—KšŸœ Ÿœ$˜:K˜K˜—š€œ˜*Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,K˜$KšŸœŸœŸœ˜ K˜K˜Kšœ}˜}KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œ˜.Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,K˜Kšœ Ÿœ"˜/KšœJŸœ˜PKšœJ˜Jš ŸœŸœŸœŸœ-Ÿ˜UKšœŸœ˜Kšœ Ÿœ˜K˜>KšŸœŸœŸœ˜šœ˜KšœŸœ ŸœŸœ ˜0KšœŸœ ŸœŸœ˜<—KšŸœ˜—KšŸœ Ÿœ$˜:K˜K˜—š€œ˜.Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,Kšœ ŸœŸœ˜K˜KšœŸœ˜ Kšœ6Ÿœ Ÿœ˜NKšŸœŸœŸœ˜ š ŸœŸœŸœŸœ-Ÿ˜UKšœŸœ˜KšœŸœ˜Kšœ>˜>KšŸœŸœŸœ˜Kšœ&˜&Kšœ9˜9KšŸœŸœ˜%šœ(˜(Kšœ Ÿœ ŸœŸœ˜>—šŸœŸœ%Ÿœ˜1Kšœ(˜(Kšœ&Ÿœ˜+K˜—KšŸœ˜—KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œ˜%Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,K˜KšœŸœ ˜Kšœ Ÿœ˜KšœŸœ#˜-Kšœ6Ÿœ Ÿœ˜OKšŸœŸœŸœ˜ Kšœ‹˜‹KšŸœ ŸœŸœ(˜Bš ŸœŸœŸœŸœ-Ÿ˜UKšŸœŸœŸœ˜šŸœ!Ÿœ˜)šœ@˜@Kšœ$˜$—KšŸœ˜K˜—šŸœŸœ˜Kšœ"Ÿœ˜'šœ˜KšœO˜O—K˜—šŸœ˜K˜šœ˜KšœO˜O—K˜—šœ:˜:Kšœ?˜?—KšŸœ˜—KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œ˜%Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,K˜!Kšœ Ÿœ˜Kšœ<Ÿœ Ÿœ˜UKšŸœŸœŸœ˜ K˜ Kšœ˜KšŸœ ŸœŸœ(˜Bš ŸœŸœŸœŸœ-Ÿ˜UKšœŸœ˜ KšœŸœ˜K˜KšŸœŸœŸœ˜šŸœ!Ÿœ˜)šœD˜DKšœ$˜$—KšŸœ˜K˜—Kšœ0˜0Kšœ˜KšŸœ Ÿœ˜,K˜4šŸœŸœ*Ÿœ˜6Kšœ+˜+Kšœ.˜.K˜—šŸœŸœ%ŸœŸ˜6šœ ˜ Kšœ*˜*——šœ:˜:Kšœ?˜?—KšŸœ˜—KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œ˜'Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,K˜Kšœ6Ÿœ Ÿœ˜OKšŸœŸœŸœ˜ š ŸœŸœŸœŸœ-Ÿ˜UKšŸœŸœŸœ˜šŸœ!Ÿœ˜)šœJ˜JKšœ$˜$—KšŸœ˜K˜—Kšœ"Ÿœ˜'šœ˜KšœZ˜Z—šœ0˜0Kšœ%˜%—KšŸœ˜—KšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œ˜(Kš œŸœ ŸœŸœŸœŸœŸœ™HšŸœ Ÿ˜Kšœ Ÿœ˜Kšœ ŸœP˜bKšœ ŸœO˜`Kšœ ŸœB˜QKšœ Ÿœ;˜NKšœŸœ5˜IKšŸœŸœ˜—K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œŸœŸœ˜Kš ŸœŸœŸœŸœŸœ˜JK˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œŸœŸœŸœ˜KšŸœŸœŸœ˜Kšœ Ÿ œ˜K˜FKšŸœ ˜ K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š €œŸœŸœŸœ'Ÿœ˜IKš ŸœŸœŸœ ŸœŸœ˜AK˜K˜%KšŸœŸœ)˜HKšŸœ Ÿœ˜K˜K˜—šœŸœ;˜MK™1K™BKšœ)™)K™K™5K˜—šœŸœ£3˜LK˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œŸœŸ˜KšŸœ0Ÿœ˜?š€œ˜ K˜"K˜"K˜#K˜$K˜K˜"KšŸœŸœŸœŸœ˜K˜KšŸœ ŸœŸœŸœ˜K˜1KšŸœŸœŸœŸœ˜Kšœ(˜(Kšœ*˜*KšŸœ ŸœŸœ˜(KšŸœŸœ ŸœŸœ˜0šŸœ Ÿœ˜(Kšœ0˜0—šŸœ ŸœŸœ˜Kšœ$˜$Kšœ>˜>K˜—šŸœ ŸœŸœ˜,Kšœ%˜%—K˜—K˜%K˜K˜—š€ œŸœ/Ÿœ˜CKšŸœ%˜,šŸœŸœŸœŸ˜3KšŸœŸœŸœŸœ˜2KšŸœ˜—K˜K˜—š €œŸœŸœŸœ Ÿœ˜>K–= -- [key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]˜˜%Kš₯9™9Kšœ Ÿœ˜šŸœ)ŸœŸœ˜8Kšœ˜Kšœ ˜ KšŸœŸœ˜K˜—K˜—šŸœŸœŸ˜Kšœ;˜;KšŸœŸœ˜—K˜!K˜K˜—š€ œŸœ Ÿœ˜!KšŸœ Ÿœ˜K–= -- [key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]šœŸœ˜#K˜K˜—š€ œŸœ1ŸœŸœ˜MKšŸœ˜š€ œŸœŸœŸœŸœŸœŸœŸœ˜?KšŸœŸœ˜"Kšœ˜—Kšœ Ÿœ˜ KšœŸœ˜K˜,KšœŸœŸœŸœ˜Kšœ Ÿœ˜%K˜"K˜Kš ŸœŸœ Ÿœ ŸœŸœŸœ£˜@KšœA˜AKš Ÿœ ŸœŸœ!ŸœŸœ˜?šŸ˜KšœA˜AKšœF˜FKšŸœŸœŸœ˜KšŸœ˜—šŸœ"Ÿœ˜*Kšœm˜mKšœ ˜ Kšœ˜—Kšœ ˜ KšœŸœ˜7K˜š ŸœŸœŸœŸœŸœŸœŸ˜4Kšœ!Ÿœ˜)—KšœX˜XK˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š € œŸœŸœŸœŸœ£'˜TKšœ ŸœŸœ˜Kšœ2˜2šŸœ˜šœŸœŸ˜ Kšœ ŸœŸœŸœŸ˜<—KšœŸœŸœ5Ÿœ˜Y—Kšœ˜K˜—š€ œŸœ˜Kš œ ŸœŸœ ŸœŸœ˜BKšŸœ ŸœŸœŸœŸœŸœŸœ˜DKšœ)˜)KšŸœŸœŸœ˜ Kšœ/˜/Kš ŸœŸœŸœŸœ Ÿœ˜MKšŸœ Ÿœ?˜PKšŸœŸœŸœ˜ šŸœ(ŸœŸ˜IKšŸœ,˜2—šŸœ-Ÿ˜3KšŸœ$˜*—šŸœŸ˜KšŸœ#˜)—K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œŸœ˜š œAŸœŸœ ŸœŸœ˜bKšœ£Y™`Kšœ£*™1—Kš Ÿœ ŸœŸœŸœŸœŸœ˜3K™=K™Kš œŸœŸœŸœŸœ˜:šŸœŸœŸ˜KšŸœŸœŸœ#˜KKšŸœŸœŸœ!˜GKšŸœŸœŸœ ˜FKšŸœŸœŸœ˜BšŸœŸœŸœ˜,Kšœ Ÿœ˜Kšœ!˜!šŸœŸ˜#KšœW˜W—Kšœ Ÿœ˜K˜—š ŸœŸœŸœŸœŸœ˜HK˜K˜K˜$K˜*K˜5K˜9K˜—˜ Kšœ Ÿœ˜Kšœ˜KšŸœŸœ ŸœŸœ)˜TKšœ.˜.Kšœ˜KšŸœŸœŸœ/˜NKšœ˜KšŸœŸœ ŸœŸœ)˜TKšœ-˜-Kšœ˜KšŸœŸœŸœ3˜QKšœ Ÿœ˜K˜—šœŸœ˜KšœB˜BK˜—KšœŸœ Ÿœ+˜>KšŸœŸœ ˜1—KšŸœ˜šŸ˜Kšœ Ÿœ)˜9—K˜—šŸœŸœ˜KšŸœŸœ˜7KšŸœŸœ,˜JKšœ˜—KšŸœŸœŸœŸœU˜ˆK˜K˜—š€ œŸœŸœ˜)Kš Ÿœ ŸœŸœŸœŸœ˜˜>—KšœŸœ˜Kšœ˜Kšœ%˜%K˜—šŸœ˜Kšœ˜Kšœ%˜%Kšœ˜Kšœ˜—K˜K˜—š€œŸœŸœ Ÿœ˜;šŸœ˜KšŸœB˜D—K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€œ˜#Kš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,KšœŸœ˜%Kšœ>˜>KšŸœŸœŸœ˜K˜K˜SKšŸœ Ÿœ$˜:K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œ˜'Kš œŸœ ŸœŸœŸœŸœŸœ™HKšœŸœ˜ Kšœ Ÿœ˜Kšœ'˜'Kšœ)˜)KšœB˜BKšŸœŸœŸœ˜K˜BK˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š€ œŸœŸœŸœ˜3KšŸœ ŸœŸœŸœŸœŸœ ŸœŸœ ŸœŸœ˜UKš œŸœ ŸœŸœŸœŸœŸœ™HKšŸœŸœŸœ˜,KšœŸœ˜Kšœ˜Kšœ˜šŸœ ŸœŸœ˜K˜Kšœ6˜6K˜—Kš ŸœŸœ ŸœŸœŸœ(˜HK˜šŸœŸœŸœ˜KšœŸœŸœ˜K˜K˜—K˜Kšœ Ÿœ˜šŸœŸœŸ˜KšœA˜A—KšŸœ Ÿœ$˜:K˜K˜—K˜LK˜IKšœx˜xKšœ’˜’Kšœ™˜™Kšœ€˜€K˜OKšœ©˜©K–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ©˜©KšœΙ˜ΙK–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ˜K–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]˜nK–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]˜ςKšœŸœ˜!KšœŸœ˜%KšŸœŸœ˜,KšœŸœŸœ˜—K˜Kšœ˜—…—WJŠε