ColumnLsImpl.mesa: concise list program
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Bloomenthal, November 6, 1985 1:17:12 pm PST
DIRECTORY
Commander, CommandTool, FS, IO, List, ReadEvalPrint,
Real, RefText, Rope, VFonts, ViewerClasses, Process, FileNames;
ColumnLsImpl: CEDAR PROGRAM
IMPORTS
Commander, CommandTool, FS, IO, List, Real, RefText, Rope, VFonts, Process, FileNames
~ {
once: BOOL;
printStream: IO.STREAM;
ROPE: TYPE ~ Rope.ROPE;
Mode: TYPE ~ RECORD[sep, dir, rag, minus1: BOOL];
font: VFonts.Font ~ VFonts.EstablishFont["Tioga", 10];
NameRecord: TYPE = RECORD[name: ROPE, width: INTEGER, next: REF NameRecord];
normSpaceWidth: INT ~ VFonts.StringWidth[" ", font];
smallSpaceWidth: INT ~ VFonts.StringWidth[" ", VFonts.EstablishFont["Timesroman", 8]];
ListCommand: PROC [
cmd: Commander.Handle, lsArg: ROPE, vWidth: INT, mode: Mode] ~ {
dir: BOOL;
arg: ROPE;
ok: BOOLTRUE;
nArgArrows: INT;
maxWidth: INT ← 0;
subDir: ROPENIL;
nameRecords: REF NameRecord ← NIL;
nameRecordsTail: REF NameRecord ← NIL;
EachName: FS.NameProc ~ {
pos: INT;
width: INT;
name: ROPE;
long: BOOL ← NChars[fullFName, '>] > nArgArrows;
Process.CheckForAbort[];
IF mode.minus1 AND FS.FileInfo[fullFName].bytes # -1 THEN RETURN[TRUE];
IF dir AND long THEN {   -- get subdirectory
name ← MultiSkip[fullFName, '>, nArgArrows];
name ← RopePiece[name, 0, name.SkipTo[0, ">"]-1];
IF ~mode.dir THEN name ← name.Concat["/"];
IF name.Equal[subDir, FALSE] THEN RETURN[TRUE];
subDir ← name;
}
ELSE {     -- name is a file
IF mode.dir THEN RETURN[TRUE];
name ← FileNames.ConvertToSlashFormat[fullFName];
IF ~dir AND long THEN pos ← 0
ELSE pos ← RevCharFind[name, '/]+1;
name ← RopePiece[name, pos, name.SkipTo[pos, "!"]-1];
};
width ← VFonts.StringWidth[name, font];
IF width > maxWidth THEN maxWidth ← width;
IF nameRecords = NIL THEN
nameRecordsTail ← nameRecords ← NEW[NameRecord ← [name, width, NIL]]
ELSE
nameRecordsTail ← nameRecordsTail.next ← NEW[NameRecord ← [name, width, NIL]];
RETURN[TRUE];
};
[arg, nArgArrows, dir, ok] ← Arg[lsArg];
IF ~ok THEN RETURN;
IF mode.sep AND dir THEN {
IF once THEN IO.PutRope[printStream, "\n"];
printStream.PutRope[lsArg.Concat[":\n"]];
};
once ← TRUE;
FS.EnumerateForNames[arg, EachName ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN Columnate[cmd, nameRecords, maxWidth, vWidth, mode];
};
Arg: PROC [arg: ROPE] RETURNS [ROPE, INT, BOOL, BOOL] ~ {
dir: BOOL;
ok: BOOLTRUE;
star: BOOL ← LastChar[arg, '*];
WHILE LastChar[arg, '*] DO arg ← RopeShorten[arg, 1]; ENDLOOP;
dir ← arg.IsEmpty[] OR LastChar[arg, '/] OR LastChar[arg, '>];
arg ← FileNames.ResolveRelativePath[arg];          -- handle .> or ..>
arg ← arg.Concat["*"];                -- ensure FName
arg ← FS.ExpandName[arg ! FS.Error => {ok ← FALSE; CONTINUE}].fullFName;
IF ~ok THEN RETURN[arg, 0, dir, FALSE];
IF ~dir AND ~star THEN arg ← RopeShorten[arg, 1];       -- remove '*
arg ← arg.Concat["!H"];
RETURN[arg, NChars[arg, '>], dir, TRUE];
};
Columnate: PROC [cmd: Commander.Handle, nameRecords: REF NameRecord,
columnWidth, viewerWidth: INT, mode: Mode] ~ {
nPixels: INT;
totWidth: INT;
nextTotal: INT;
nColumns: INT;
nNormSpaces: INT;
nSmallSpaces: INT;
record: REF NameRecord ← nameRecords;
normSpaces: REF TEXT ← RefText.ObtainScratch[100];
smallSpaces: REF TEXT ← RefText.ObtainScratch[100];
columnWidth ← columnWidth+2*normSpaceWidth;
nColumns ← (viewerWidth-20)/columnWidth;
WHILE record # NIL DO
nextTotal ← totWidth ← 0;
FOR i: INT IN [1..nColumns] WHILE record # NIL DO
nextTotal ← nextTotal+columnWidth;
totWidth ← totWidth+record.width;
nPixels ← nextTotal-totWidth;
IF i = nColumns THEN nNormSpaces ← nSmallSpaces ← 0
ELSE IF mode.rag THEN {
nNormSpaces ← Real.RoundI[Real.Float[nextTotal-totWidth]/Real.Float[normSpaceWidth]];
nSmallSpaces ← 0;
}
ELSE {
nNormSpaces ← nPixels MOD smallSpaceWidth;
nSmallSpaces ← nPixels/smallSpaceWidth-nNormSpaces;
IF nSmallSpaces < 0 THEN { nSmallSpaces ← 0; nNormSpaces ← nNormSpaces-1; }
};
totWidth ← totWidth+nNormSpaces*normSpaceWidth+nSmallSpaces*smallSpaceWidth;
SpaceText[normSpaces, nNormSpaces];
SpaceText[smallSpaces, nSmallSpaces];
IF mode.rag OR nSmallSpaces = 0 THEN cmd.out.PutF["%g%g", IO.rope[record.name], IO.text[normSpaces]]
ELSE cmd.out.PutF["%g%g%l%g%l", IO.rope[record.name], IO.text[normSpaces], IO.rope["s"], IO.text[smallSpaces], IO.rope["S"]];
record ← record.next;
ENDLOOP;
cmd.out.PutF["\n"];
ENDLOOP;
RefText.ReleaseScratch[smallSpaces];
RefText.ReleaseScratch[normSpaces];
};
SpaceText: PROC [text: REF TEXT, nSpaces: INT] ~ {
text.length ← 0;
THROUGH[1..nSpaces] DO
text ← RefText.AppendChar[text, ' ];
ENDLOOP;
};
NChars: PUBLIC PROC [rope: Rope.ROPE, char: CHAR] RETURNS [retval: INT ← 0] ~ {
length: INT ← rope.Length[];
FOR n: INT ← 0, n+1 WHILE n < length DO
IF rope.Fetch[n] = char THEN retval ← retval+1;
ENDLOOP;
RETURN[retval];
};
RopePiece: PUBLIC PROC [rope: Rope.ROPE, n1: INT, n2: INT] RETURNS [Rope.ROPE] ~ {
IF n1 < 0 OR n2 < 0 THEN RETURN[NIL];
IF n2 > n1 THEN RETURN[rope.Substr[n1, n2-n1+1]];
RETURN[rope.Substr[n2, n1-n2+1]];
};
RevCharFind: PUBLIC PROC [rope: Rope.ROPE, char: CHAR] RETURNS [INT] ~ {
n: INT ← rope.Length[]-1;
WHILE n >= 0 AND rope.Fetch[n] # char DO n ← n-1; ENDLOOP;
RETURN[n];
};
FirstChar: PUBLIC PROC [rope: Rope.ROPE, char: CHAR] RETURNS [BOOL] ~ {
RETURN[rope.Length[] > 0 AND rope.Fetch[0] = char];
};
LastChar: PUBLIC PROC [rope: Rope.ROPE, char: CHAR] RETURNS [BOOL] ~ {
ropeLength: INT ← rope.Length[];
RETURN[ropeLength > 0 AND rope.Fetch[ropeLength-1] = char];
};
RopeShorten: PUBLIC PROC [rope: Rope.ROPE, n: INT] RETURNS [Rope.ROPE] ~ {
ropeLength: INT ← rope.Length[];
IF ropeLength >= n THEN rope ← rope.Substr[0, ropeLength-n];
RETURN [rope];
};
MultiSkip: PUBLIC PROC [rope: Rope.ROPE, char: CHAR, nSkip: INT] RETURNS [Rope.ROPE] ~ {
n: INT;
limit: INT ← rope.Length[]-1;
FOR n ← 0, n+1 WHILE n < limit DO
IF rope.Fetch[n] = char THEN nSkip ← nSkip-1;
IF nSkip = 0 THEN EXIT;
ENDLOOP;
RETURN[rope.Substr[n+1]];
};
ListCommandProc: Commander.CommandProc ~ {
nArgs: INT ← 0;
mode: Mode ← [TRUE, FALSE, FALSE, FALSE];
v: ViewerClasses.Viewer ~ CommandTool.GetViewer[cmd];
v: ViewerClasses.Viewer ← NARROW[List.Assoc[$ReadEvalPrintHandle, cmd.propertyList], ReadEvalPrint.Handle].viewer;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd];
once ← FALSE;
printStream ← cmd.out;
FOR i: INT IN [1..argv.argc) DO
IF FirstChar[argv[i], '-] THEN SELECT TRUE FROM
argv[i].Equal["-s", FALSE] => mode.sep ← FALSE;
argv[i].Equal["-d", FALSE] => mode.dir ← TRUE;
argv[i].Equal["-m", FALSE] => mode.minus1 ← TRUE;
argv[i].Equal["-q", FALSE] => mode.rag ← TRUE;
ENDCASE
ELSE nArgs ← nArgs+1;
ENDLOOP;
mode.sep ← mode.sep AND nArgs>1;
FOR i: INT IN [1..argv.argc) DO
IF ~FirstChar[argv[i], '-] THEN ListCommand[cmd, argv[i], v.cw, mode];
ENDLOOP;
IF ~once THEN ListCommand[cmd, "", v.cw, mode];
};
msg: ROPE ~ "\nls {switch | pattern}*\na concise listing program\n\t-d for directories only\n\t-s for separation of multiple directories\n\t-q for quicker (somewhat ragged) columnation\n\t-m for listing only files with -1 byte counts.";
Commander.Register["ls", ListCommandProc, msg];
Commander.Register["ColumnLs", ListCommandProc, msg];
}.