CommandsCImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
L. Stewart, January 17, 1984 11:08 am
Russ Atkinson, April 11, 1985 12:25:22 pm PST
Russ Atkinson (RRA) August 19, 1985 6:56:37 pm PDT
DIRECTORY
Atom USING [GetPName],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [DoCommand, GetProp],
Convert USING [RopeFromInt],
EditedStream USING [GetEcho, Rubout, SetEcho, SetMode, UnAppendBufferChars],
FileNames USING [GetShortName],
IO USING [BreakProc, Close, EndOf, EndOfStream, Error, GetRefAny, GetToken, IDProc, PeekChar, RIS, SkipWhitespace, STREAM, TokenProc],
MBQueue USING [CreateMenuEntry],
Menus USING [AppendMenuEntry, ClickProc, CopyEntry, CreateMenu, FindEntry, Menu, MenuEntry, ReplaceMenuEntry],
ReadEvalPrint USING [Handle],
Rope USING [Cat, Concat, Equal, Find, FromChar, FromRefText, Length, Match, Replace, ROPE, SkipTo, Substr],
RopeList USING [Reverse],
TiogaOps USING [GetSelection, Location, LocOffset, Ref, Root],
UserCredentials USING [Get, Login],
UserProfile USING [CallWhenProfileChanges, Line, ProfileChangedProc],
ViewerClasses USING [Viewer],
ViewerIO USING [GetBuffer, TypeChars],
ViewerOps USING [EnumerateViewers, EnumProc, FetchProp, PaintViewer],
ViewerTools USING [GetSelectionContents, SetSelection];
CommandsCImpl: CEDAR PROGRAM
IMPORTS Atom, Commander, CommandTool, Convert, EditedStream, FileNames, IO, MBQueue, Menus, Rope, RopeList, TiogaOps, UserCredentials, UserProfile, ViewerIO, ViewerOps, ViewerTools
= BEGIN
AliasCellObject: TYPE = RECORD [
args: LIST OF ROPENIL,
def: ROPE
];
AliasCell: TYPE = REF AliasCellObject;
ROPE: TYPE = Rope.ROPE;
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];
commandLineStream.Close[];
EXITS
Die => NULL;
};
};
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: PROC [c: CHAR ← 'A] RETURNS[ROPE] = {
gennum ← gennum + 1;
RETURN[Rope.Concat[Rope.FromChar[c], Convert.RopeFromInt[gennum, 10, FALSE]]];
};
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, case: FALSE];
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, case: FALSE];
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"];
commandLineStream.Close[];
result ← CommandTool.DoCommand[commandLine: newCommandLine, parent: cmd];
EXITS
Nasty => {msg ← "IO.Error while parsing arguments"; result ← $Failed};
};
CRBreak: IO.BreakProc = {
IF char = '\n THEN RETURN[break];
RETURN[other];
};
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;
};
};
};
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;
};
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}
];
};
Init: PROC = {
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"];
UserProfile.CallWhenProfileChanges[WhenProfileChanges];
};
Init[];
END.