OtherCommandsImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) April 25, 1986 5:53:35 pm PST
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 ROPENIL,
def: ROPE
];
AliasCell: TYPE = REF AliasCellObject;
lastUser: ROPENIL;
The "last" known user (initially unknown) set by NoteNewUser
asterisky: BOOLEANFALSE;
TRUE if the typescript supports *'s during login
Alias: Commander.CommandProc = TRUSTED {
commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine];
name, def: ROPE;
token: REF TEXTNEW[TEXT[30]];
args: LIST OF ROPENIL;
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 ANYNARROW[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 TEXTNEW[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 {
e.g. args are (x y) and substituting y gorp.
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 {
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.
commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine];
name: ROPE;
def: ROPE;
token: REF TEXTNEW[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];
Get the rest of it!
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 {
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
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 {
This command will clean the current menu back to its ground state (STOP! Find Split).
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 {
There is an old menu, so we make a clean new one.
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 = {
[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"
br: ButtonImplRef ← NARROW[clientData];
def: ROPE;
curSel: ROPE;
viewer: ViewerClasses.Viewer ← NIL;
start: TiogaOps.Location;
viewerName: ROPENIL;
fileName: ROPENIL;
shortFileName: ROPENIL;
index: INT ← -1;
pos: INT;
controlRope: ROPEIF control THEN "control" ELSE "noControl";
shiftRope: ROPEIF 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, "!"]];
};
Get prefix of current selection before the first CR
pos ← curSel.Find["\n"];
IF pos # -1 THEN curSel ← Rope.Substr[base: curSel, start: 0, len: pos];
The curSel is the fileName if curSel is longer than one character and contains no whitespace.
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 {
It is likely that we have substitutions to do
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 the selected viewer is the commandtool, then set the caret to the end, or the ViewerIO.TypeChars won't work.
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: ROPENIL
];
WhenProfileChanges: UserProfile.ProfileChangedProc = {
IF reason = rollBack THEN {
We get this whenever we rollback, or the credentials change.
user: ROPE ← UserCredentials.Get[].name;
IF NOT Rope.Equal[user, lastUser, FALSE] THEN {
There is a new user, so we should try to customize the various command tools that exist.
firstTime: BOOLTRUE;
eachTool: ViewerOps.EnumProc = {
[v: ViewerClasses.Viewer] RETURNS [BOOL ← TRUE]
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];
RRA: EachCommandToolCommands is only for compatibility
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}
];
};
List Command
ListCommandProc: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
EachFile: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
attachedTo: ROPENIL;
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
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?
[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] = {
item: REF [fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL]
oldLag: ROPE ← lagPrefix;
printName: ROPE ← item.fullFName;
Process.CheckForAbort[];
IF NOT fullPrint AND NOT directoriesOnly THEN {
Factor out the directories
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: ROPENIL;
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] = {
... 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.
IF lagPrefix # NIL THEN {
do we have a new prefix?
IF Rope.Run[lagPrefix, 0, fileName, 0, FALSE] = lagPrefixLen THEN {
So far we have a match with the lagging prefix. How far does it go?
pos: INT = Rope.SkipTo[fileName, lagPrefixLen, ">/]"];
IF pos = Rope.Length[fileName] THEN RETURN;
};
};
We have a new lagging prefix, so scan backwards for the LAST directory
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;
The file name has no prefix, so clear out the lagPrefix
lagPrefix ← NIL;
lagPrefixLen ← 0;
};
AddSortOption: PROC [option: ATOM] = {
new: LORALIST[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: ROPENIL;
gDir: ROPENIL;
out: STREAM = cmd.out;
lagPrefix: ROPENIL;
lagPrefixLen: INT ← 0;
patternsTried: INT ← 0;
filesSeen: INT ← 0;
bytesTotal: INT ← 0;
directoriesOnly: BOOLFALSE;
complexSorting: BOOLFALSE;
printKeep: BOOLFALSE;
narrowPrint: BOOLFALSE;
attachedPrint: BOOLFALSE;
briefPrint: BOOLFALSE;
remoteCheck: BOOLFALSE;
fullPrint: BOOLFALSE;
highestPrint: BOOL ← cmd.procData.clientData = $Highest;
oneLine: BOOLFALSE;
unattachedOnly: BOOLFALSE;
exactLevelMatch: BOOLFALSE;
gNamePrint: BOOLFALSE;
anglesRequired: INT ← 0;
sortData: LORANIL;
sortDataTail: LORANIL;
pq: PriorityQueue.Ref ← NIL;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ProcessSwitches: PROC [arg: ROPE] = {
sense: BOOLTRUE;
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 {
This argument sets switches for the remaining patterns
ProcessSwitches[arg];
LOOP;
};
Now the argument is assumed to be a file pattern.
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 = {
[x: Item, y: Item, data: REF] RETURNS [BOOL]
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];
};
OpenR commands
OpenCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
Handle = REF [in, out, err: STREAM, commandLine,command: ROPE, propertyList: List.AList]
st: IO.STREAM ← cmd.out;
inStream: IO.STREAMIO.RIS[cmd.commandLine];
eachFile: PROC = {
MyOpen: PROC [def: ROPENIL] = {
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: ROPENIL;
date: GMT;
r: ROPEIO.GetTokenRope[inStream, IO.IDProc].token;
IF Rope.Length[r] = 0 THEN RETURN;
sfl ← FindSource[r];
SELECT TRUE FROM
sfl = NIL => {
No file found as explicitly given, we might need to try some alternatives.
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 => {
At least one match, so open the most recent
[name, date] ← FindMostRecent[sfl];
MyOpen[];
};
};
DO
eachFile[
! FS.Error => {msg ← error.explanation; result ← $Failed; EXIT};
IO.EndOfStream => EXIT;
];
ENDLOOP;
};
FindCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
Handle = REF [in, out, err: STREAM, commandLine,command: ROPE, propertyList: List.AList]
st: IO.STREAM ← cmd.out;
inStream: IO.STREAMIO.RIS[cmd.commandLine];
useBin: BOOL ← cmd.procData.clientData = $Bin;
eachFile: PROC = {
sfl: SourceFileList ← NIL;
r: ROPEIO.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;
};
OpenR utilities
FindSource: PROC [short: ROPE, removeDuplDates: BOOLTRUE, which: ATOMNIL] 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] = {
make a long name into a short one
assumes a valid long name, of course
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: ROPENIL, ext: ROPENIL] = {
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: ROPENIL, 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 {
the file in each is more recent or the same date
eachName: ROPE ← each.first.name;
IF period < 0 OR Rope.Compare[eachName, name, FALSE] = greater THEN {
each is more recent OR (same date AND lexically greater name)
date ← eachDate;
name ← eachName;
};
};
ENDLOOP;
};
};
RemoveDuplicates: PROC [sfl: SourceFileList, removeDuplDates: BOOL] = {
This routine removes entries with duplicate names an (if removeDuplDates = TRUE) also removes entries with duplicate dates.
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 [ROPENIL, GMT ← BasicTime.nullGMT] = {
line: ROPE ← UserProfile.Line["SourceFileExtensions", "mesa tioga df cm config"];
in: STREAMIO.RIS[line, NIL];
DO
token: ROPEIO.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: ROPENIL;
[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;
};
Utilities
RopeSubst: PROC [old, new, base: ROPE, case: BOOLFALSE, allOccurrences: BOOLTRUE] RETURNS [ROPE] = {
if old is not found in base, then value = base.
if allOccurrences THEN substitute for each occurrence of old, otherwise only for first.
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] = {
Returns the enclosing ReadEvalPrint.Handle (NIL if no such 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;
};
Initialization
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"];
For List
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];
For OpenR & FindR
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.