DIRECTORY Ascii, Atom, Buttons, Commander, CommanderBackdoor, CommanderOps, CommanderViewer, Convert, ConvertUnsafe, EditedStream, Icons, IO, List, MBQueue, Menus, MessageWindow, PFS, PFSNames, Process, RefText, Rope, RopeList, TiogaMenuOps, TiogaOps, TypeScript, UserProfile, ViewerClasses, ViewerIO, ViewerOps, ViewerTools; CommanderViewerImpl: CEDAR PROGRAM IMPORTS Atom, Buttons, Commander, CommanderBackdoor, CommanderOps, Convert, ConvertUnsafe, EditedStream, Icons, IO, List, MBQueue, Menus, MessageWindow, PFS, PFSNames, Process, RefText, Rope, RopeList, TiogaMenuOps, TiogaOps, TypeScript, UserProfile, ViewerIO, ViewerOps, ViewerTools EXPORTS CommanderViewer ~ BEGIN PATH: TYPE ~ PFSNames.PATH; ROPE: TYPE ~ Rope.ROPE; CreateCommanderButtonProc: Buttons.ButtonProc = { initial: ROPE ~ "CommandsFromProfile CommandTool.PerCommandTool"; Process.SetPriority[Process.priorityNormal]; [] ฌ Create[info: [name: "initializing...", column: right, iconic: FALSE], initial: initial]; }; CommanderViewerCommand: Commander.CommandProc = { fork: BOOL ~ FALSE; -- fork is handled by the new Fork routine below, Bier, October 29, 1990 [] ฌ Create[info: [ name: "initializing...", column: IF fork THEN left ELSE right, iconic: fork ], initial: cmd.commandLine]; }; SwitchesAndCommand: PROC [line: Rope.ROPE] RETURNS [switches, command: Rope.ROPE] = { lineLength: NAT ฌ 0; beginCommand: INT ฌ 0; i: NAT ฌ 0; inSwitch: BOOL ฌ FALSE; lineLength ฌ Rope.Length[line]; FOR i: INT IN [0..lineLength) DO IF inSwitch THEN { SELECT Rope.Fetch[line, i] FROM IO.SP, IO.TAB => {inSwitch ฌ FALSE; beginCommand ฌ i}; '- => {beginCommand ฌ -1}; -- in a new switch. Keep going ENDCASE => {beginCommand ฌ -1} -- keep going in a switch } ELSE { -- not in a switch SELECT Rope.Fetch[line, i] FROM IO.SP, IO.TAB => {beginCommand ฌ i}; -- keep going '- => {inSwitch ฌ TRUE; beginCommand ฌ -1}; ENDCASE => { -- command begins here inSwitch ฌ FALSE; beginCommand ฌ i; EXIT; }; }; ENDLOOP; IF inSwitch THEN {switches ฌ line; command ฌ NIL} ELSE { switches ฌ IF beginCommand > 0 THEN Rope.Substr[line, 0, beginCommand] ELSE NIL; command ฌ IF beginCommand # -1 THEN Rope.Substr[line, beginCommand] ELSE NIL; }; }; Fork: Commander.CommandProc = { open: BOOL ฌ FALSE; right: BOOL ฌ FALSE; commandStarts: INT ฌ 0; switches, command: Rope.ROPE; [switches, command] ฌ SwitchesAndCommand[cmd.commandLine]; open ฌ Rope.Find[switches, "-open", 0, FALSE] # -1; right ฌ Rope.Find[switches, "-right", 0, FALSE] # -1; [] ฌ Create[info: [ name: "initializing...", column: IF right THEN right ELSE left, iconic: NOT open ], initial: command]; }; StopHit: Menus.MenuProc = TRUSTED { WITH clientData SELECT FROM cmd: Commander.Handle => { CommanderBackdoor.AbortCommander[cmd ! Process.InvalidProcess => CONTINUE]; }; ENDCASE; }; CurrentWorkingDirectory: PROC RETURNS [ROPE] ~ { wd: PFS.PATH ~ PFS.GetWDir[]; RETURN [PFS.RopeFromPath[wd]] }; GetShortName: PROC [path: ROPE] RETURNS [ROPE] = { len: INT ฌ Rope.Length[path]; bang: INT ฌ len; pos: INT ฌ len; WHILE pos # 0 DO np: INT ฌ pos - 1; c: CHAR ฌ Rope.Fetch[path, np]; SELECT c FROM '! => bang ฌ np; '>, '], '/ => RETURN [Rope.Substr[path, pos, bang-pos]]; ENDCASE; pos ฌ np; ENDLOOP; RETURN [Rope.Substr[path, 0, bang]]; }; GetBaseName: PROC [shortName: ROPE] RETURNS [ROPE] = { dotPos: INT ฌ Rope.Find[shortName, "."]; IF dotPos<0 THEN RETURN [shortName] ELSE RETURN [Rope.Substr[shortName, 0, dotPos]] }; defaultPrompt: ROPE ฌ "%l%% %l"; ViewerPrompt: PROC [cmd: Commander.Handle] ~ { prompt: ROPE ~ WITH CommanderOps.GetProp[cmd, $Prompt] SELECT FROM rope: ROPE => rope, ENDCASE => defaultPrompt; IO.PutF[cmd.err, prompt, [rope["b"]], [rope["B"]]]; WITH ViewerIO.GetViewerFromStream[cmd.in] SELECT FROM viewer: ViewerClasses.Viewer => { wd: ROPE ~ CurrentWorkingDirectory[]; IF ViewerOps.FetchProp[viewer, $WorkingDirectoryInCaption] # wd THEN { ViewerOps.AddProp[viewer, $WorkingDirectoryInCaption, wd]; viewer.name ฌ Rope.Cat[wd, " ", IO.PutFR[prompt, [rope["b"]], [rope["B"]]]]; ViewerOps.PaintViewer[viewer, caption]; }; }; ENDCASE; }; CommanderViewerBase: PROC [cmd: Commander.Handle, viewer: ViewerClasses.Viewer, initial: ROPE, wDir: PFS.PATH] ~ { Inner: PROC ~ { IF initial # NIL THEN { [] ฌ CommanderOps.ReadEvalPrintLoop[ CommanderOps.CreateFromStreams[in: IO.RIS[initial], parentCommander: cmd] ]; }; [] ฌ CommanderOps.ReadEvalPrintLoop[cmd]; }; IF Process.GetPriority[]>Process.priorityNormal THEN Process.SetPriority[Process.priorityNormal]; PFS.DoInWDir[wDir: wDir, inner: Inner]; }; stopKey: ATOM ~ Atom.MakeAtom["STOP!"]; -- funny key to prevent casual use. Abort: PUBLIC PROC [commanderViewer: ViewerClasses.Viewer] ~ { StopHit[commanderViewer, ViewerOps.FetchProp[commanderViewer, stopKey], red, FALSE, FALSE]; }; Create: PUBLIC PROC [info: ViewerClasses.ViewerRec, initial: ROPE] RETURNS [ViewerClasses.Viewer] ~ { viewer: ViewerClasses.Viewer ~ TypeScript.Create[info: info, paint: TRUE]; in, out: IO.STREAM; [in: in, out: out] ฌ ViewerIO.CreateViewerStreams[name: NIL, viewer: viewer]; BEGIN cmd: Commander.Handle ~ CommanderOps.CreateFromStreams[in: in, out: out]; data: CommanderBackdoor.CommandToolData ~ CommanderBackdoor.GetCommandToolData[cmd]; EditedStream.SetDeliverWhen[in, MyDeliverWhen, cmd]; data.Prompt ฌ ViewerPrompt; Menus.InsertMenuEntry[viewer.menu, Menus.CreateEntry["STOP!", StopHit, cmd] ]; ViewerOps.PaintViewer[viewer: viewer, hint: menu]; ViewerOps.AddProp[viewer, stopKey, cmd]; TRUSTED {Process.Detach[FORK CommanderViewerBase[cmd, viewer, initial, PFS.GetWDir[]]]}; RETURN [viewer] END; }; DequeueProc: PROC [data: REF] ~ { WITH data SELECT FROM data: REF MBQueue.Action.user => { data.proc[data.parent, data.clientData, data.mouseButton, data.shift, data.control]; }; ENDCASE; }; Queue: PUBLIC PROC [commanderViewer: ViewerClasses.Viewer, commandLine: Rope.ROPE, mouseButton: ViewerClasses.MouseButton ฌ red, shift: BOOL ฌ FALSE, control: BOOL ฌ FALSE] ~ { queue: MBQueue.Queue ฌ GetMBQueue[commanderViewer]; MBQueue.QueueClientAction[queue, DequeueProc, NEW[MBQueue.Action.user ฌ [user[proc: ButtonImplButtonProc, parent: commanderViewer, clientData: NEW[ButtonImplObject ฌ [cmd: NARROW[ViewerOps.FetchProp[commanderViewer, stopKey]]]], mouseButton: mouseButton, shift: shift, control: control]]]]; }; OpenViewer: PROC [name: ROPE, out: IO.STREAM, column: ViewerClasses.Column] = { viewer: ViewerClasses.Viewer; IF Rope.IsEmpty[name] THEN name ฌ CurrentWorkingDirectory[]; viewer ฌ TiogaMenuOps.Open[fileName: name, column: column]; IF viewer = NIL THEN out.PutF1["\tViewer file not found: %g\n", [rope[name]]] ELSE out.PutF1["\tCreated Viewer: %g\n", [rope[viewer.name]]]; }; NewCommand: Commander.CommandProc = { ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; argv: CommanderOps.ArgumentVector ฌ CommanderOps.Parse[cmd]; column: ViewerClasses.Column ฌ left; noFiles: BOOL ฌ TRUE; dammit: BOOL ฌ FALSE; -- the -d switch FOR i: NAT IN [1..argv.argc) DO argi: ROPE ~ argv[i]; IF Rope.Match["-*", argi] THEN { IF Rope.Match["-d*", argi] THEN dammit ฌ TRUE ELSE column ฌ ColumnFromSwitch[argi, column] } ELSE { path: PATH ~ PFS.AbsoluteName[PFS.PathFromRope[argi]]; IF NOT dammit THEN { ENABLE PFS.Error => IF error.group = user THEN CONTINUE; exists: PATH ~ PFS.FileInfo[name: path].fullFName; ERROR CommanderOps.Failed[Rope.Concat[PFS.RopeFromPath[exists], " already exists! Use the -d switch to create a new viewer anyway."]]; }; IF NOT dammit THEN { IF NOT Rope.Match[pattern: "*.*", object: PFSNames.ComponentRope[PFSNames.ShortName[path]]] THEN { ERROR CommanderOps.Failed[Rope.Cat["Please supply an extension for ", argi, " or use the -d switch to create a file with no extension"]]; }; }; { viewer: ViewerClasses.Viewer ~ TiogaMenuOps.Open[fileName: CurrentWorkingDirectory[], column: column]; noFiles ฌ FALSE; IF viewer = NIL THEN ERROR CommanderOps.Failed["Holey System, Batman, TiogaMenuOps.Open wouldn't even give me a Viewer!"]; viewer.name ฌ viewer.file ฌ PFS.RopeFromPath[path]; ViewerOps.PaintViewer[viewer, caption]; cmd.out.PutF1["\tCreated Viewer: %g\n", [rope[viewer.name]]]; } }; ENDLOOP; IF noFiles THEN OpenViewer[NIL, cmd.out, column]; }; ExpandStar: PROC [token: ROPE] RETURNS [LIST OF ROPE] = { IF Rope.Find[token, "*"] # -1 THEN { rawPath: PATH ~ PFS.PathFromRope[token]; stripVersion: BOOL ~ PFSNames.ShortName[rawPath].version.versionKind = none; path: PATH ~ IF stripVersion THEN PFSNames.SetVersionNumber[rawPath, [highest]] ELSE rawPath; listOfTokens: LIST OF ROPE ฌ NIL; ConsProc: PFS.NameProc = { IF stripVersion THEN name ฌ PFSNames.StripVersionNumber[name]; listOfTokens ฌ CONS[PFS.RopeFromPath[name], listOfTokens]; -- on front of list RETURN[continue: TRUE]; }; PFS.EnumerateForNames[pattern: path, proc: ConsProc ! PFS.Error => IF error.group # bug THEN CONTINUE]; RETURN[RopeList.DReverse[listOfTokens]]; } ELSE RETURN[LIST[token]]; }; ColumnFromSwitch: PROC [switchArg: ROPE, default: ViewerClasses.Column] RETURNS [column: ViewerClasses.Column ฌ left] ~ { MatchPrefix: PROC [s: LONG STRING] RETURNS [BOOL] ~ { run: INT ~ Rope.Run[s1: switchArg, s2: ConvertUnsafe.ToRope[s], case: FALSE]; RETURN [run = Rope.Size[switchArg] AND run > 1]; }; SELECT TRUE FROM MatchPrefix["-right"] => column ฌ right; MatchPrefix["-left"] => column ฌ left; MatchPrefix["-color"] => column ฌ color; ENDCASE => column ฌ default; }; OpenCommand: Commander.CommandProc = { ENABLE { PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; }; argv: CommanderOps.ArgumentVector ฌ CommanderOps.Parse[cmd]; column: ViewerClasses.Column ฌ left; noFiles: BOOL ฌ TRUE; FOR i: NAT IN [1..argv.argc) DO argi: ROPE ~ argv[i]; IF Rope.Match["-*", argi] THEN { column ฌ ColumnFromSwitch[argi, column] } ELSE { list: LIST OF ROPE ฌ ExpandStar[argi]; WHILE list # NIL DO name: ROPE ฌ list.first; OpenViewer[name, cmd.out, column]; list ฌ list.rest; ENDLOOP; noFiles ฌ FALSE; }; ENDLOOP; IF noFiles THEN OpenViewer[NIL, cmd.out, column] }; OpenImplViewer: PROC [name: ROPE, out: IO.STREAM, column: ViewerClasses.Column] = { viewer: ViewerClasses.Viewer; viewer ฌ TiogaMenuOps.OpenImpl[fileName: name, column: column]; IF viewer = NIL THEN out.PutF1["\tImpl not found for %g\n", [rope[name]]] ELSE out.PutF1["\tCreated Viewer: %g\n", [rope[viewer.name]]]; }; OpenImplCommand: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ฌ CommanderOps.Parse[cmd]; column: ViewerClasses.Column ฌ left; FOR i: NAT IN [1..argv.argc) DO argi: ROPE ~ argv[i]; IF Rope.Match["-*", argi] THEN { column ฌ ColumnFromSwitch[argi, column] } ELSE { OpenImplViewer[argi, cmd.out, column]; }; ENDLOOP; }; PrepareToDie: PROC [cmd: Commander.Handle, self: ViewerClasses.Viewer] ~ { Clean: PROC [stream: IO.STREAM] RETURNS [IO.STREAM] ~ { RETURN [IF ViewerIO.GetViewerFromStream[stream] = self THEN IO.noWhereStream ELSE stream]; }; IF self = NIL THEN RETURN; UNTIL cmd = NIL DO data: CommanderBackdoor.CommandToolData ~ CommanderBackdoor.GetCommandToolData[cmd]; cmd.err ฌ Clean[cmd.err]; cmd.out ฌ Clean[cmd.out]; IF ViewerIO.GetViewerFromStream[cmd.in] = self THEN { IO.Reset[cmd.in]; cmd.in ฌ IO.noInputStream; }; cmd ฌ data.parentCommander; ENDLOOP; }; ViewerCommand: Commander.CommandProc = { blink: BOOL ฌ FALSE; changeClose: BOOL ฌ FALSE; changeColumn: BOOL ฌ FALSE; close: BOOL ฌ FALSE; column: ViewerClasses.Column ฌ left; columnKey: ViewerClasses.Column ฌ static; columnMatch: BOOL ฌ FALSE; destroy: BOOL ฌ FALSE; edited: BOOL ฌ FALSE; flavor: ATOM ฌ NIL; grow: BOOL ฌ FALSE; height: INTEGER ฌ INTEGER.FIRST; names: BOOL ฌ FALSE; iconic: BOOL ฌ FALSE; noniconic: BOOL ฌ FALSE; icon: ROPE ฌ NIL; iconNumber: INT ฌ 0; save: BOOL ฌ FALSE; top: BOOL ฌ FALSE; unedited: BOOL ฌ FALSE; self: ViewerClasses.Viewer ~ ViewerIO.GetViewerFromStream[CommanderBackdoor.AdamOrEve[cmd].err]; Do: PROC [viewer: ViewerClasses.Viewer] ~ { IF viewer # NIL THEN { IF viewer.column # static THEN { IF changeClose AND close THEN ViewerOps.CloseViewer[viewer]; IF height # INTEGER.FIRST THEN { ViewerOps.SetOpenHeight[viewer, height]; IF NOT viewer.iconic THEN ViewerOps.ComputeColumn[viewer.column]; }; IF changeColumn THEN ViewerOps.ChangeColumn[viewer, column]; IF save THEN [] ฌ ViewerOps.SaveViewer[viewer]; IF changeClose AND NOT close THEN ViewerOps.OpenIcon[icon: viewer, closeOthers: grow, bottom: NOT top]; IF top AND NOT viewer.iconic THEN ViewerOps.TopViewer[viewer]; IF grow AND NOT viewer.iconic THEN ViewerOps.GrowViewer[viewer]; IF names THEN {IO.PutRope[cmd.out, Convert.RopeFromRope[viewer.name]]; IO.PutRope[cmd.out, " "]}; IF icon # NIL THEN viewer.icon ฌ Icons.NewIconFromFile[icon, iconNumber]; IF destroy THEN { IF blink THEN ViewerOps.BlinkViewer[viewer]; IF viewer = self THEN PrepareToDie[cmd, self]; ViewerOps.DestroyViewer[viewer]; }; }; IF blink THEN ViewerOps.BlinkViewer[viewer]; n ฌ n + 1; }; }; n: INT ฌ 0; FOR arg: ROPE ฌ CommanderOps.NextArgument[cmd], CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO MatchPrefix: PROC [s: LONG STRING] RETURNS [BOOL] ~ { run: INT ~ Rope.Run[s1: arg, s2: ConvertUnsafe.ToRope[s], case: FALSE]; RETURN [run = Rope.Size[arg] AND run > 1]; }; BadSwitch: PROC = { CommanderOps.Failed[Rope.Concat["Unknown or ambiguous switch: " , arg]] }; SELECT TRUE FROM MatchPrefix["-blink"] => { blink ฌ TRUE }; MatchPrefix["-c"] => { BadSwitch[] }; MatchPrefix["-close"] => { close ฌ TRUE; changeClose ฌ TRUE }; MatchPrefix["-color"] => { column ฌ left; changeColumn ฌ TRUE }; MatchPrefix["-destroy"] => { destroy ฌ TRUE }; MatchPrefix["-edited"] => { edited ฌ TRUE }; MatchPrefix["-flavor"] => { flavor ฌ Atom.MakeAtom[CommanderOps.NextArgument[cmd]] }; MatchPrefix["-grow"] => { grow ฌ TRUE }; MatchPrefix["-height"] => { h: ROPE ~ CommanderOps.NextArgument[cmd]; height ฌ Convert.IntFromRope[h ! Convert.Error => CommanderOps.Failed["bad -height number"]]; }; MatchPrefix["-iconic"] => { iconic ฌ TRUE }; MatchPrefix["-left"] => { column ฌ left; changeColumn ฌ TRUE }; MatchPrefix["-names"] => { names ฌ TRUE }; MatchPrefix["-o"] => { BadSwitch[] }; MatchPrefix["-onColor"] => { columnKey ฌ color; columnMatch ฌ TRUE }; MatchPrefix["-onLeft"] => { columnKey ฌ left; columnMatch ฌ TRUE }; MatchPrefix["-onRight"] => { columnKey ฌ right; columnMatch ฌ TRUE }; MatchPrefix["-open"] => { close ฌ FALSE; changeClose ฌ TRUE }; MatchPrefix["-right"] => { column ฌ right; changeColumn ฌ TRUE }; MatchPrefix["-s"] => { BadSwitch[] }; MatchPrefix["-save"] => { save ฌ TRUE }; MatchPrefix["-self"] => { IF self = NIL THEN CommanderOps.Failed["No Viewer!"] ELSE Do[self]; }; MatchPrefix["-t"] => { flavor ฌ $Text }; MatchPrefix["-text"] => { flavor ฌ $Text }; MatchPrefix["-top"] => { top ฌ TRUE }; MatchPrefix["-unedited"] => { unedited ฌ TRUE }; MatchPrefix["-~"] => { BadSwitch[] }; MatchPrefix["-~edited"] => { unedited ฌ TRUE }; MatchPrefix["-~iconic"] => { noniconic ฌ TRUE }; MatchPrefix["-setIcon"] => { icon ฌ CommanderOps.NextArgument[cmd]; iconNumber ฌ Convert.IntFromRope[CommanderOps.NextArgument[cmd] ! Convert.Error => CommanderOps.Failed["bad icon number"]]; }; Rope.Match["-*", arg] => { BadSwitch[] }; ENDCASE => { viewers: LIST OF REF ฌ NIL; Each: ViewerOps.EnumProc ~ { IF edited AND NOT v.newVersion THEN RETURN; IF unedited AND v.newVersion THEN RETURN; IF iconic AND NOT v.iconic THEN RETURN; IF noniconic AND v.iconic THEN RETURN; IF flavor#NIL AND v.class.flavor#flavor THEN RETURN; IF columnMatch AND v.column#columnKey THEN RETURN; IF Rope.Match[pattern: arg, object: v.name, case: FALSE] THEN { viewers ฌ CONS[v, viewers]; }; }; ViewerOps.EnumerateViewers[Each]; IF viewers=NIL AND Rope.Find[arg, "*"] < 0 THEN { CommanderOps.Failed[Rope.Concat["No viewers named ", arg]]; }; viewers ฌ List.Sort[viewers, CompareNames]; FOR tail: LIST OF REF ฌ viewers, tail.rest UNTIL tail = NIL DO WITH tail.first SELECT FROM v: ViewerClasses.Viewer => Do[v]; ENDCASE => NULL; ENDLOOP; List.Kill[viewers]; viewers ฌ NIL; }; n ฌ n + 1; ENDLOOP; IF n = 0 THEN CommanderOps.Failed[cmd.procData.doc]; IF names THEN msg ฌ "\n"; }; CompareNames: List.CompareProc ~ { WITH ref1 SELECT FROM v1: ViewerClasses.Viewer => { WITH ref2 SELECT FROM v2: ViewerClasses.Viewer => { RETURN Rope.Compare[v1.name, v2.name, FALSE] }; ENDCASE => NULL; }; ENDCASE => NULL; RETURN List.Compare[ref1, ref2]; }; ButtonImplRef: TYPE = REF ButtonImplObject; ButtonImplObject: TYPE = RECORD [ cmd: Commander.Handle, def: ROPE ฌ NIL ]; CreateButtonCommand: Commander.CommandProc = TRUSTED { commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine]; name: ROPE; def: ROPE; token: REF TEXT ฌ NEW[TEXT[30]]; br: ButtonImplRef ฌ NEW[ButtonImplObject ฌ [cmd: CommanderBackdoor.AdamOrEve[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; WITH ViewerIO.GetViewerFromStream[br.cmd.err] SELECT FROM viewer: ViewerClasses.Viewer => { menu: Menus.Menu ฌ viewer.menu; IF menu # NIL THEN { queue: MBQueue.Queue ฌ GetMBQueue[viewer]; new: Menus.MenuEntry ฌ IF def = NIL THEN NIL ELSE MBQueue.CreateMenuEntry[q: queue, 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]; }; }; ENDCASE => RETURN [$Failure, "Can't find viewer for CreateButton"]; }; GetMBQueue: PROC [viewer: ViewerClasses.Viewer] RETURNS [MBQueue.Queue] ~ { WITH ViewerOps.FetchProp[viewer, $ButtonQueue] SELECT FROM refQ: REF MBQueue.Queue => RETURN [refQญ]; ENDCASE => { queue: MBQueue.Queue ~ MBQueue.Create[]; ViewerOps.AddProp[viewer, $ButtonQueue, NEW[MBQueue.Queue ฌ queue]]; RETURN [queue] }; }; RepaintCommand: Commander.CommandProc = { ViewerOps.PaintEverything[]; }; ClearMenuCommand: Commander.CommandProc = TRUSTED { WITH ViewerIO.GetViewerFromStream[CommanderBackdoor.AdamOrEve[cmd].err] SELECT FROM viewer: ViewerClasses.Viewer => { 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]; viewer.menu ฌ newMenu; ViewerOps.PaintViewer[viewer: viewer, hint: menu, clearClient: FALSE]; }; RETURN; }; ENDCASE => NULL; }; createButtonSubstitutions: ROPE ~ " $CurrentSelection$ => replaced by the current selection up to but not including the first carriage return $CurrentEscapedSelection$ => replaced by a rope literal (minus the surrounding quotes) for the entire current selection $CurrentSpacedSelection$ => replaced by the whole current selection, with spaces substituted for newlines $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 $BaseFileNameSelection$ => same as $ShortFileNameSelection$ except that extensions are ommited $SpaceFileNameSelection$ => replaced by the current selection if it appears to be a file name (may include white space), otherwise replaced by the name of the selected viewer $ShortSpaceFileNameSelection$ => same as $SpaceFileNameSelection$ except that version number and directory are omitted $QuotedFileNameSelection$ => replaced by the current selection if it appears to be a file name (may include white space), otherwise replaced by the name of the selected viewer. The results will have double quotes around it. $ShortQuotedFileNameSelection$ => same as $QuotedFileNameSelection$ 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|right $ShiftKey$ => shift|noShift $ControlKey$ control|noControl"; ButtonImplButtonProc: Menus.ClickProc = { WITH clientData SELECT FROM br: ButtonImplRef => { def: ROPE; curSel, escaped, spaced: ROPE; viewer: ViewerClasses.Viewer ฌ NIL; start: TiogaOps.Location; viewerName: ROPE ฌ NIL; fileName: ROPE ฌ NIL; shortFileName: ROPE ฌ NIL; baseFileName: ROPE ฌ NIL; spaceFileName: ROPE ฌ NIL; shortSpaceFileName: ROPE ฌ NIL; quotedFileName: ROPE ฌ NIL; shortQuotedFileName: ROPE ฌ NIL; index: INT ฌ -1; 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, "!"]]; }; escaped ฌ Convert.RopeFromRope[from: curSel, quote: FALSE]; spaced ฌ RopeSubst[old: "\n", new: " ", base: curSel]; curSel ฌ Rope.Substr[base: curSel, start: 0, len: curSel.Index[s2: "\n"]]; fileName ฌ IF (Rope.SkipTo[s: curSel, pos: 0, skip: " \t"] = curSel.Length[]) AND (curSel.Length[] > 1) THEN curSel ELSE fileName; shortFileName ฌ GetShortName[fileName]; baseFileName ฌ GetBaseName[shortFileName]; spaceFileName ฌ IF (curSel.Length[] > 1) THEN curSel ELSE fileName; shortSpaceFileName ฌ GetShortName[spaceFileName]; quotedFileName ฌ Rope.Cat["\"", spaceFileName, "\""]; shortQuotedFileName ฌ Rope.Cat["\"", shortSpaceFileName, "\""]; IF Rope.SkipTo[def, 0, "$"] < Rope.Length[def] THEN { def ฌ RopeSubst[old: "$CurrentSelection$", new: curSel, base: def, case: TRUE]; def ฌ RopeSubst[old: "$CurrentEscapedSelection$", new: escaped, base: def, case: TRUE]; def ฌ RopeSubst[old: "$CurrentSpacedSelection$", new: spaced, 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: "$BaseFileNameSelection$", new: baseFileName, base: def, case: TRUE]; def ฌ RopeSubst[old: "$SpaceFileNameSelection$", new: spaceFileName, base: def, case: TRUE]; def ฌ RopeSubst[old: "$ShortSpaceFileNameSelection$", new: shortSpaceFileName, base: def, case: TRUE]; def ฌ RopeSubst[old: "$QuotedFileNameSelection$", new: quotedFileName, base: def, case: TRUE]; def ฌ RopeSubst[old: "$ShortQuotedFileNameSelection$", new: shortQuotedFileName, 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]; }; WITH ViewerIO.GetViewerFromStream[br.cmd.in] SELECT FROM v: ViewerClasses.Viewer => { bufferContents: REF TEXT ฌ ViewerIO.GetBuffer[br.cmd.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.cmd.in, nChars: bufferContents.length - n - 1]; EXIT; } REPEAT FINISHED => EditedStream.UnAppendBufferChars[stream: br.cmd.in, nChars: LAST[NAT]]; ENDLOOP; }; IF viewer = v THEN { ViewerTools.SetSelection[viewer: viewer, selection: NIL]; }; ViewerIO.TypeChars[editedViewerStream: br.cmd.in, chars: def]; }; ENDCASE => GOTO Failed; { }; }; ENDCASE => GOTO Failed; EXITS Failed => { MessageWindow.Append["*** Bug in CommanderViewerImpl.ButtonImplButtonProc ***", TRUE] }; }; 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]; }; 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]; }; CRBreak: IO.BreakProc = { IF char = '\l OR char = '\r THEN RETURN[break]; RETURN[other]; }; completeChar: CHAR ~ Ascii.ESC; queryChar: CHAR ~ '? + 200B; -- Meta-? autoHelpThreshhold: INT; ProfileChanged: UserProfile.ProfileChangedProc ~ { autoHelpThreshhold ฌ UserProfile.Number["Commander.AutoHelpThreshhold", 10]; }; MyDeliverWhen: EditedStream.DeliverWhenProc = { cmd: Commander.Handle ~ NARROW[context]; index: INT; partialName: ROPE; count: INT ฌ 0; fullNames, tail: LIST OF ROPE ฌ NIL; commonPrefix: ROPE; AddHit: PROC [rope: Rope.ROPE] ~ { IF tail = NIL THEN { fullNames ฌ tail ฌ CONS[rope, NIL]; commonPrefix ฌ rope; } ELSE { tail.rest ฌ CONS[rope, NIL]; tail ฌ tail.rest; commonPrefix ฌ commonPrefix.Substr[len: commonPrefix.Run[s2: rope, case: FALSE]]; }; count ฌ count + 1; }; MakeCommandList: PROC ~ { pattern: Rope.ROPE ~ partialName.Concat["*"]; EachCommand: Commander.EnumerateAction ~ { IF pattern.Match[key, FALSE] THEN AddHit[key]; }; [] ฌ Commander.Enumerate[EachCommand]; }; MakeFileList: PROC ~ { patternPath: PFS.PATH ~ PFS.PathFromRope[partialName.Concat["*!H"]]; wDir: PFS.PATH ~ PFS.GetWDir[]; EachName: PFS.NameProc = { isAPrefix: BOOL; relName: PFS.PATH; [isAPrefix, relName] ฌ PFSNames.IsAPrefix[wDir, name]; IF isAPrefix THEN AddHit[PFS.RopeFromPath[PFSNames.StripVersionNumber[relName]]] ELSE AddHit[PFS.RopeFromPath[PFSNames.StripVersionNumber[name]]]; }; PFS.EnumerateForNames[pattern: patternPath, proc: EachName]; }; OutputPreservingInputBuffer: PROC [action: PROC [out: IO.STREAM]] ~ { buffer: Rope.ROPE ~ Rope.FromRefText[ViewerIO.GetBuffer[cmd.in]]; data: CommanderBackdoor.CommandToolData ~ CommanderBackdoor.GetCommandToolData[cmd]; EditedStream.UnAppendBufferChars[stream: cmd.in, nChars: buffer.Length[]]; cmd.out.PutRope[buffer]; cmd.out.PutF["%l<%g>%l\n", [rope["bos"]], IF char = completeChar THEN [rope["COMPLETE"]] ELSE [rope["HELP"]], [rope["S"]]]; action[cmd.out]; cmd.out.PutF1["%l\n", [rope["BO"]]]; IF data.Prompt # NIL THEN data.Prompt[cmd]; EditedStream.AppendBufferChars[stream: cmd.in, chars: buffer]; }; SELECT char FROM completeChar, queryChar => NULL; ENDCASE => RETURN EditedStream.IsANL[char, buffer, stream, context]; IF buffer = NIL THEN { buffer ฌ ViewerIO.GetBuffer[stream]; }; IF buffer.length = 0 AND char = completeChar THEN -- Preserve ESC behavior on empty line RETURN [appendChar: TRUE, activate: FALSE]; FOR index ฌ buffer.length - 1, index - 1 WHILE index >= 0 DO SELECT buffer[index] FROM Ascii.SP, Ascii.TAB => EXIT; ENDCASE => NULL; ENDLOOP; index ฌ index + 1; -- index is now the index of the first character in the last "word" partialName ฌ Rope.FromRefText[buffer, index]; IF index = RefText.SkipOver[buffer, 0, " \t"] THEN -- First word on line MakeCommandList[] ELSE MakeFileList[ ! PFS.Error => { MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; GO TO done; }]; SELECT TRUE FROM count = 0 => { -- no names match the partial name Action: PROC [out: IO.STREAM] ~ { out.PutF["\t\tThere are %lno%l completion possibilities.", [rope["z"]], [rope["Z"]]]; }; OutputPreservingInputBuffer[Action]; }; char = completeChar AND count = 1 => { -- exactly one match IF partialName.Equal[commonPrefix] THEN { Action: PROC [out: IO.STREAM] ~ { out.PutF1["\t\t\"%g\" is the only completion possibility.", [rope[partialName]]]; }; OutputPreservingInputBuffer[Action]; } ELSE { EditedStream.UnAppendBufferChars[stream: stream, nChars: buffer.length - index]; EditedStream.AppendBufferChars[stream: stream, chars: fullNames.first]; }; }; char = completeChar AND commonPrefix.Length[] > partialName.Length[] => { -- complete it EditedStream.UnAppendBufferChars[stream: stream, nChars: buffer.length - index]; EditedStream.AppendBufferChars[stream: stream, chars: commonPrefix]; }; char = completeChar AND count > autoHelpThreshhold => { -- too many to print Action: PROC [out: IO.STREAM] ~ { out.PutF["\t\tThere are %g possibilities for completion; type %l%l to see them.", [integer[count]], [rope["s"]], [rope["S"]]]; }; OutputPreservingInputBuffer[Action]; }; ENDCASE => { -- print out the possibilities Action: PROC [out: IO.STREAM] ~ { out.PutChar['\t]; FOR list: LIST OF ROPE ฌ fullNames, list.rest UNTIL list = NIL DO out.PutF1["\t%g", [rope[list.first]]]; ENDLOOP; }; OutputPreservingInputBuffer[Action]; }; RETURN [appendChar: FALSE, activate: FALSE]; EXITS done => RETURN [appendChar: FALSE, activate: FALSE]; }; Commander.Register["New", NewCommand, "New (-left | -right | -color | -d | fileName)* open an empty viewer (with the given name, for each name, if any given, else one unnamed)"]; Commander.Register["Open", OpenCommand, "Open [-left | -right | -color ] fileName - open a viewer"]; Commander.Register["OpenImpl", OpenImplCommand, "OpenImpl [-left | -right | -color ] name - open a viewer on the implementation of name"]; Commander.Register[key: "CreateButton", proc: CreateButtonCommand, doc: Rope.Concat["Create a CommandTool herald button; substitutions are:", createButtonSubstitutions], interpreted: FALSE]; Commander.Register[key: "ClearMenu", proc: ClearMenuCommand, doc: "Reset the CommandTool menu"]; Commander.Register[key: "RemoveButton", proc: CreateButtonCommand, doc: "Remove a CommandTool herald button"]; Commander.Register[key: "Repaint", proc: RepaintCommand, doc: "Repaint all viewers"]; Commander.Register[key: "Viewer", proc: ViewerCommand, doc: "Push around viewers Usage: {switch | viewerNamePattern}* Action Switches: -blink -close -color -destroy -grow -height k (set open height) -setIcon <#> (set icon from named icon file and number) -left -names (write viewer names to standard out) -open -right -top Filter Switches: -edited -~edited or -unedited -flavor -iconic -~iconic -onColor -onLeft -onRight -save -text (short for -flavor Text) Object Switches: -self (the commander viewer itself) "]; Commander.Register[key: "CommanderViewer", proc: CommanderViewerCommand, doc: "Create a new Commander Viewer (arguments compose the initial command)"]; Commander.Register[key: "Fork", proc: Fork, doc: "Fork [-open] [-right]\n Create a new Commander Viewer (default: left column, iconic) (the last argument composes the initial command)", interpreted: FALSE]; Commander.Register[key: "ForkI", proc: Fork, doc: "ForkI [-open] [-right]\n Create a new Commander Viewer (interpreted command) (default: left column, iconic) (the last argument composes the initial command)", interpreted: TRUE]; [] ฌ Buttons.Create[info: [name: "Cmd"], proc: CreateCommanderButtonProc, fork: TRUE, documentation: "Create a Commander viewer"]; UserProfile.CallWhenProfileChanges[ProfileChanged]; END. dCommanderViewerImpl.mesa Copyright ำ 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Michael Plass, March 11, 1992 12:43 pm PST Last changed by Pavel on February 13, 1990 12:58 pm PST Christian Jacobi, August 7, 1990 3:53 pm PDT Bier, October 29, 1990 5:14 pm PST Willie-s, October 18, 1991 12:29 pm PDT Jules Bloomenthal June 25, 1993 2:18 pm PDT Create a brand-new independent commander Create a brand-new independent commander fork: BOOL ~ cmd.procData.clientData = $Fork; Create a brand-new independent commander [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] Too bad MBQueue does not allow us to queue a user action directly... PROC [name: PATH] RETURNS [continue: BOOL _ TRUE] PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]; 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] 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. The curSel is the spaceFileName if curSel is longer than one character. Add quotes for the quoted version. 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. if old is not found in base, then value = base. if allOccurrences THEN substitute for each occurrence of old, otherwise only for first. Completion Stuff [char: CHAR, buffer: REF TEXT, stream: STREAM, context: REF ANY] RETURNS [appendChar: BOOL, activate: BOOL] [key: ROPE, procData: CommandProcHandle] RETURNS [stop: BOOL _ FALSE] [name: PATH] RETURNS [continue: BOOL _ TRUE] Calls action, allowing it to do output to the handle, but preserving the state of the input buffer. Initialization ส'้–(cedarcode) style•NewlineDelimiter ˜codešœ™Kšœ ฯeœC™NK™*Kšœ7™7Kšœ,™,K™"K™'K™+K™—Kšฯk œžœ'žœ˜ลK˜Kšฯnœžœž˜"Kšžœižœ'žœ˜›Kšžœ˜šœž˜K˜Kšžœžœ žœ˜šžœžœžœ˜K˜—šŸœ˜1Kšœ(™(Kšœ žœ4˜AKšœ,˜,KšœCžœ˜]K˜K˜—šŸœ˜1Kšœ(™(Kšœžœ#™-KšœžœžœฯcH˜\šœ˜Kšœ˜Kšœžœžœžœ˜%Kšœ ˜ Kšœ˜—K˜K˜—š Ÿœžœ žœžœžœ˜UKšœ žœ˜Kšœžœ˜Kšœžœ˜ Kšœ žœžœ˜K˜Kšœ˜šžœžœžœž˜ šžœ žœ˜šžœž˜Kš žœžœžœžœžœ˜6Kšœ ˜:Kšžœ ˜8—K˜—šžœ ˜šžœž˜Kš žœžœžœžœ  ˜2Kšœžœ˜+šžœ ˜#Kšœ žœ˜Kšœ˜Kšžœ˜K˜——K˜—Kšžœ˜—Kšžœ žœžœ˜1šžœ˜Kš œ žœžœ$žœžœ˜PKš œ žœžœ!žœžœ˜MK˜—K˜K˜—šŸœ˜Kšœ(™(Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœžœ˜K˜Kšœ:˜:Kšœ3˜3Kšœ5˜5K˜šœ˜Kšœ˜Kšœžœžœžœ˜&Kšœžœ˜Kšœ˜—K˜K˜—šะbnœžœ˜#Kšœ žœžœ,žœ™Nšžœ žœž˜šœ˜KšœAžœ˜KKšœ˜—Kšžœ˜—Kšœ˜K˜—šŸœžœžœžœ˜0Kšœžœžœžœ ˜Kšžœžœ˜Kšœ˜K˜—š Ÿ œžœžœžœžœ˜2Kšœžœ˜Kšœžœ˜Kšœžœ˜šžœ ž˜Kšœžœ ˜Kšœžœ˜šžœž˜ Kšœ˜Kšœžœ$˜8Kšžœ˜—Kšœ ˜ Kšžœ˜—Kšžœ˜$Kšœ˜K˜—š Ÿ œžœ žœžœžœ˜6Kšœžœ˜(šžœ ˜ Kšžœžœ ˜Kšžœžœ%˜0—Kšœ˜K˜—Kšœžœ ˜ šŸ œžœ˜.šœžœžœ$žœž˜BKšœžœ ˜Kšžœ˜—Kšžœ1˜3šžœ&žœž˜5˜!Kšœžœ˜%šžœ>žœ˜FKšœ:˜:Kšœ žœ*˜LKšœ'˜'Kšœ˜—K˜—Kšžœ˜—Kšœ˜K˜—š Ÿœžœ@žœžœžœ˜ršŸœžœ˜šžœ žœžœ˜šœ$˜$Kšœ#žœžœ ˜IKšœ˜—Kšœ˜—Kšœ)˜)Kšœ˜—Kšžœ.žœ-˜aKšžœ$˜'Kšœ˜K˜—šœ žœ #˜KK˜—šŸœžœžœ,˜>KšœMžœžœ˜[Kšœ˜K˜—šŸœž œ*žœžœ˜eKšœDžœ˜JKšœ žœžœ˜Kšœ8žœ˜Mšž˜KšœI˜IKšœT˜TK˜4K˜KšœN˜NKšœ2˜2K˜(Kšžœžœ+žœ˜XKšžœ ˜Kšžœ˜—Kšœ˜K˜—šŸ œžœžœ˜!K™Dšžœžœž˜šœžœ˜"K˜TK˜—Kšžœ˜—Kšœ˜K˜—šŸœžœžœ;žœ7žœžœ žœžœ˜ฐK˜3Kšœ.žœ^žœžœp˜ขK˜K˜—š Ÿ œžœžœžœžœ$˜PKšœ˜Kšžœžœ"˜—K˜K˜—šŸ œ˜%Kš žœžœ žœžœžœ(˜\Kšœ<˜Kšœžœžœ% ˜OKšžœ žœ˜K˜—Kš žœ0žœžœ žœžœžœ˜gKšžœ"˜(K˜—Kšžœžœžœ ˜—K˜K˜—šŸœžœ žœ!žœ*˜yš Ÿ œžœž œžœžœ˜5K•StartOfExpansionO[s1: ROPE, pos1: INT _ 0, s2: ROPE, pos2: INT _ 0, case: BOOL _ TRUE]šœžœ>žœ˜MKšžœžœ ˜0Kšœ˜—šžœžœž˜Kšœ(˜(Kšœ&˜&Kšœ(˜(Kšžœ˜—Kšœ˜K˜—šŸ œ˜&šžœ˜Kšžœ žœžœžœ(˜UKšœ˜—Kšœ<˜K˜K˜—šŸœ˜*Kšœ<˜Kšžœžœžœžœ˜@Kšžœžœžœ6žœ˜aKšžœžœžœ7˜Išžœ žœ˜Kšžœžœ˜,Kšžœžœ˜.Kšœ ˜ Kšœ˜—Kšœ˜—Kšžœžœ˜,Kšœ ˜ Kšœ˜—Kšœ˜—Kšœžœ˜ š žœžœBžœžœž˜aš Ÿ œžœž œžœžœ˜5K–O[s1: ROPE, pos1: INT _ 0, s2: ROPE, pos2: INT _ 0, case: BOOL _ TRUE]šœžœ8žœ˜GKšžœžœ ˜*Kšœ˜—šŸ œžœ˜KšœG˜GKšœ˜—šžœžœž˜Kšœ#žœ˜*Kšœ%˜%Kšœ#žœžœ˜>Kšœ9žœ˜@Kšœ'žœ˜.Kšœ%žœ˜,KšœU˜UKšœ!žœ˜(šœ˜Kšœžœ"˜)Kšœ]˜]Kšœ˜—Kšœ%žœ˜,Kšœ8žœ˜?Kšœ#žœ˜*Kšœ%˜%Kšœ>žœ˜EKšœ<žœ˜CKšœ>žœ˜EKšœ"žœžœ˜>Kšœ:žœ˜AKšœ%˜%Kšœ!žœ˜(šœ˜šžœž˜ Kšžœ"˜&Kšžœ ˜—Kšœ˜—Kšœ(˜(Kšœ+˜+Kšœžœ˜&Kšœ)žœ˜0Kšœ%˜%Kšœ(žœ˜/Kšœ)žœ˜0˜K˜&˜?K˜;—K˜—Kšœ)˜)šžœ˜ Kš œ žœžœžœžœ˜šŸœ˜Kš žœžœžœžœžœ˜+Kšžœ žœžœžœ˜)Kš žœžœžœ žœžœ˜'Kšžœ žœ žœžœ˜&Kš žœžœžœžœžœ˜4Kšžœ žœžœžœ˜2šžœ0žœžœ˜?Kšœ žœ ˜Kšœ˜—Kšœ˜—Kšœ!˜!šžœ žœžœžœ˜1Kšœ;˜;Kšœ˜—Kšœ+˜+š žœžœžœžœžœžœž˜>šžœ žœž˜Kšœ!˜!Kšžœžœ˜—Kšžœ˜—Kšœžœ˜"Kšœ˜——Kšœ ˜ Kšžœ˜—Kšžœžœ'˜4Kšžœžœ ˜Kšœ˜K˜—šŸ œ˜"Kš žœžœžœžœžœžœ™@šžœžœž˜šœ˜šžœžœž˜šœ˜Kšžœ žœ˜,Kšœ˜—Kšžœžœ˜—Kšœ˜—Kšžœžœ˜—Kšžœ˜ Kšœ˜K˜—Kšœžœžœ˜+šœžœžœ˜!K˜Kšœžœž˜K˜K˜—šŸœžœ˜6K™ฒKš œžœžœžœžœ˜7Kšœžœ˜ Kšœžœ˜ Kš œžœžœžœžœ˜ Kšœžœ=˜T˜šžœ˜Kšžœžœ˜Kšžœ žœ˜K˜—K˜Kšœ#žœ˜>Kšœ˜Kšœ&žœ˜-K™K˜Kšœ4žœžœ˜Všž˜Kšœžœ˜ —K˜—šžœ˜K˜—Kšœ˜šžœžœž˜Kšœžœ˜"Kšœ˜Kšžœ!˜(—K˜ K˜šžœ*žœž˜9šœ!˜!Kšœ˜šœ™Kšœ.™.Kšœ9™9Kšœ7™7Kšœ9™9—šžœžœžœ˜Kšœ*˜*Kš œžœžœžœžœžœ[˜ŒKšœ3˜3šžœžœž˜Kšœžœ+˜4Kšœžœ(˜1Kšžœ˜—Kšœ?žœ˜FK˜—Kšœ˜—Kšžœžœ2˜C—K˜K˜—šŸ œžœ žœ˜Kšžœ+žœž˜:Kšœžœžœ ˜*šžœ˜ Kšœ(˜(Kšœ(žœ˜DKšžœ˜Kšœ˜——Kšœ˜K˜—šŸœ˜)K˜K˜K˜—šŸœžœ˜3K™UšžœDžœž˜Sšœ!˜!Kšœ"˜"šžœ žœžœ˜Kšœ1™1Kšœ*˜*Kšœ*˜*Kšœ)˜)Kšœ*˜*Kšœ˜Kšœ?žœ˜FK˜—Kšžœ˜Kšœ˜—Kšžœžœ˜—K˜K˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šœ#˜#Kšœi˜iK˜wK˜iK˜K˜lK˜^K˜ฎK˜vK˜เK˜yK˜CK˜QK˜"K˜K˜ K˜—šŸœ˜)Kšะck~™~šžœ žœž˜šœ˜Kšœžœ˜ Kšœžœ˜Kšœžœ˜#Kšœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜ Kšœžœ˜Kš œ žœžœ žœ žœ ˜?Kš œ žœžœžœ žœ ˜7Kšœ žœ˜šžœ ž˜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šœ4žœ˜;Kšœ6˜6™3KšœJ˜J—™]Kš œ žœAžœžœžœ ˜‚Kšœ'˜'Kšœ*˜*—šœG™GKšœžœžœžœ ˜CKšœ1˜1—™"Kšœ5˜5Kšœ?˜?—K˜šžœ-žœ˜5Kšœ-™-KšœIžœ˜OKšœQžœ˜WKšœOžœ˜UKšœLžœ˜RKšœVžœ˜\KšœTžœ˜ZKšœVžœ˜\Kšœ`žœ˜fKšœXžœ˜^Kšœbžœ˜hKšœOžœ˜UKšœMžœžœ˜lKšœHžœ˜NKšœHžœ˜NKšœDžœ˜JKšœ˜—šžœ)žœž˜8˜Kšœžœžœ!˜9š žœžœžœžœ1žœ˜pš žœžœž œžœž˜6šžœžœ˜!šœ!˜!Kšœ:˜:—Kšžœ˜K˜—Kšž˜Kšžœ@žœžœ˜SKšžœ˜—K˜—šžœ žœ˜Kšœo™oKšœ4žœ˜9Kšœ˜—Kšœ>˜>K˜—Kšžœžœ˜—˜K˜—Kšœ˜—Kšžœžœ˜—šžœ ˜KšœPžœ˜UKšœ˜—K˜K˜—šŸ œžœžœžœžœžœžœžœžœ˜jšœ/™/KšœW™W—Kšœžœ˜Kšœžœ˜Kšœžœ˜ šžœ>ž˜EKšœB˜BKšžœžœžœ˜K˜Kšžœ˜—Kšžœ˜ K˜K˜—šŸœžœžœ#˜CKšœ6˜6Kšžœžœžœ9˜JK˜K˜—šŸœžœ˜Kšžœ žœ žœžœ˜/Kšžœ˜K˜K˜——headšœ™Kšœžœ žœ˜Kšœ žœ  ˜&Kšœžœ˜K˜–o -- [char: CHAR, buffer: REF TEXT, stream: STREAM, context: REF ANY] RETURNS [appendChar: BOOL, activate: BOOL]šŸœ$˜2KšœL˜LKšœ˜—K˜šŸ œ"˜/Kšขk™kKšœžœ ˜(Kšœžœ˜ Kšœ žœ˜Kšœžœ˜Kš œžœžœžœžœ˜$Kšœžœ˜šŸœžœ žœ˜"šžœžœžœ˜Kšœžœžœ˜#Kšœ˜Kšœ˜—šžœ˜Kšœ žœžœ˜Kšœ˜K–O[s1: ROPE, pos1: INT _ 0, s2: ROPE, pos2: INT _ 0, case: BOOL _ TRUE]šœIžœ˜QKšœ˜—Kšœ˜Kšœ˜—šŸœžœ˜Kšœžœ˜-šŸ œ˜*Kš œžœžœžœžœ™Ešžœžœžœ˜"Kšœ ˜ —Kšœ˜—K˜Kšœ&˜&Kšœ˜—šŸ œžœ˜Kšœ žœžœžœ)˜DK–. -- [fullFName: ROPE] RETURNS [continue: BOOL]šœžœžœžœ ˜šฯbœžœ ˜Kšขœžข!™,Kšœ žœ˜Kšœ žœžœ˜K˜Kšœ6˜6šžœ žœ˜Kšœžœ4˜>—šž˜Kšœžœ2˜<—Kšœ˜—K˜Kšžœ9˜˜>Kšœ˜—K˜šžœž˜Kšœžœ˜ Kšžœžœ3˜D—K˜šžœ žœžœ˜Kšœ$˜$Kšœ˜—K˜šžœžœžœ &˜XKšžœžœ žœ˜+—K˜šžœ&žœ ž˜<šžœž˜Kšœžœžœžœ˜Kšžœžœ˜—Kšžœ˜—Kšœ C˜VK–([base: ROPE _ NIL, rest: ROPE _ NIL]šœ.˜.K˜šžœ,žœ ˜HKšœ˜—šž˜šœ ˜ šœžœ ˜Kšœ(žœ˜.Kšœ˜Kšžœžœ˜ Kšœ˜———K˜šžœžœž˜šœ "˜3šŸœžœžœžœ˜!Kšœะbo œฆœฆœ˜UKšœ˜—Kšœ$˜$Kšœ˜—šœžœ ˜;–![stream: STREAM, nChars: NAT]šžœ žœ˜)šŸœžœžœžœ˜!Kšœฆœฆ%œ˜QKšœ˜—Kšœ$˜$Kšœ˜—šžœ˜KšœP˜PK–[path: ROPE]šœG˜GKšœ˜—Kšœ˜—šœžœ3 ˜XK–![stream: STREAM, nChars: NAT]šœP˜PK–[path: ROPE]šœD˜DKšœ˜—šœžœ! ˜LšŸœžœžœžœ˜!Kš œฆ œฆ$œฅœฆ œ/˜„Kšœ˜—Kšœ$˜$Kšœ˜—šžœ ˜-šŸœžœžœžœ˜!Kšœ˜š žœžœžœžœžœžœž˜AKšœ&˜&Kšžœ˜—Kšœ˜—Kšœ$˜$Kšœ˜——K˜Kšžœžœ žœ˜,K˜šž˜Kšœžœžœ žœ˜4—Kšœ˜—K˜—šœ™Kšœฒ˜ฒKšœd˜dKšœŠ˜ŠKšœทžœ˜พKšœ`˜`Kšœn˜nK˜UšœP˜PKšœ$˜$Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜K˜K˜@Kšœ˜Kšœ-˜-Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ$˜$Kšœ˜Kšœ)˜)Kšœ˜K˜—šœ—˜—K˜—Kšœ0˜0šœ–žœ˜K˜—Kšœ1˜1Kšœญžœ˜ณK˜KšœPžœ.˜‚K˜Kšœ3˜3—K˜Kšžœ˜K˜—…—|dญฑ