<> <> <> 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: BOOL _ TRUE; nArgArrows: INT; maxWidth: INT _ 0; subDir: ROPE _ NIL; 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: BOOL _ TRUE; 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]; <> 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]; }.