|
#############################################################################
##
## This file is part of GAP, a system for computational discrete algebra.
## This file's authors include Frank Lübeck.
##
## Copyright of GAP belongs to its developers, whose names are too numerous
## to list here. Please refer to the COPYRIGHT file for details.
##
## SPDX-License-Identifier: GPL-2.0-or-later
##
## The files pager.g{d,i} contain the `Pager' utility. A rudimentary
## version of this was integrated in first versions of GAP's help system.
## But this utility is certainly useful for other purposes as well.
##
##
## There is a builtin pager `PAGER_BUILTIN', but at least under UNIX one
## should use an external one. This can be set via the variable
## `SetUserPreference("Pager", ...);'
## (e.g., SetUserPreference("Pager", "less");).
## Here, `less' should be in the executable
## PATH of the user and we assume that it supports an argument `+num' for
## starting display in line number `num'. Additional options can be
## given by `SetUserPreference("PagerOptions", ...);' as list of strings.
##
## The user function is `Pager'.
##
## The input of `Pager( lines )' can have one the following forms:
##
## (1) a string (i.e., lines are separated by '\n')
## (2) a list of strings (without '\n') interpreted as lines of output
## (3) a record with component .lines as in (1) or (2) and optional further
## components
##
## In (3) currently the following components are used:
##
## .formatted (true/false) If true, the builtin pager tries to avoid
## line breaks by GAP's Print.
## .start (number/string) The display is started with line .start or
## at the first line containing .start, but
## beginning is available via back scrolling.
## .exitAtEnd (true/false) If true (default), the builtin pager is
## terminated as soon as the end of the list is
## reached;
## if false, entering 'q' is necessary in order to
## return from the pager.
##
# The preferred pager can be specified via a user preference.
DeclareUserPreference( rec(
name:= [ "Pager", "PagerOptions" ],
description:= [
"For displaying help pages on screen and other things ⪆ has a rudimentary \
builtin pager. We recommend using a more sophisticated external program. \
For example, when you have the program <C>less</C> on your computer we recommend:",
" <C>Pager := \"less\";</C>",
" <C>PagerOptions := [\"-f\", \"-r\", \"-a\", \"-i\", \"-M\", \"-j2\"];</C>",
"If you want to use <C>more</C>, we suggest to use the <C>-f</C> option. \
If you want to use the pager defined in your environment then \
leave the <C>Pager</C> and <C>PagerOptions</C> preferences empty."
],
default:= function() # copied from GAPInfo.READENVPAGEREDITOR
local str, sp, pager, options;
if IsBound(GAPInfo.KernelInfo.ENVIRONMENT.PAGER) then
str := GAPInfo.KernelInfo.ENVIRONMENT.PAGER;
sp := SplitStringInternal(str, "", " \n\t\r");
if Length(sp) > 0 then
pager:= sp[1];
options:= sp{ [ 2 .. Length( sp ) ] };
# 'less' could have options in variable 'LESS'
if "less" in SplitStringInternal(sp[1], "", "/\\") then
if IsBound(GAPInfo.KernelInfo.ENVIRONMENT.LESS) then
str := GAPInfo.KernelInfo.ENVIRONMENT.LESS;
sp := SplitStringInternal(str, "", " \n\t\r");
Append( options, sp );
fi;
# make sure -r is used
Add( options, "-r" );
elif "more" in SplitStringInternal(sp[1], "", "/\\") then
if IsBound(GAPInfo.KernelInfo.ENVIRONMENT.MORE) then
# similarly for 'more'
str := GAPInfo.KernelInfo.ENVIRONMENT.MORE;
sp := SplitStringInternal(str, "", " \n\t\r");
Append( options, sp );
fi;
# make sure -f is used
Add( options, "-f" );
fi;
return [ pager, options ];
fi;
fi;
# The builtin pager does not work in HPCGAP
if IsHPCGAP then
return [ "less" , ["-f","-r","-a","-i","-M","-j2"] ];
else
return [ "builtin", [] ];
fi;
end,
) );
#############################################################################
##
#F PAGER_BUILTIN( <lines> ) . . . . . . . . . . . . . . . . format lines
##
## Show <A>lines</A> in a rudimentary pager inside the terminal.
## The argument can be either a string or a list of strings or a record.
## In the latter case, the component <C>lines</C> must be a string or a
## list of strings,
## and the following additional components are supported.
## <List>
## <Mark>formatted</Mark>
## <Item>
## <K>true</K> or <K>false</K> (default <K>false</K>),
## <K>true</K> means that lines not fitting into one terminal line
## (see <Ref Func="SizeScreen"/>) will be distributed to several lines,
## </Item>
## <Mark>start</Mark>
## <Item>
## a positive integer denoting the position of the first line that is
## shown,
## or a string meaning that the first line containing this string
## shall be shown;
## the default is 1,
## </Item>
## <Mark>exitAtEnd</Mark>
## <Item>
## <K>true</K> or <K>false</K> (default <K>true</K>),
## meaning whether the pager should terminate automatically
## once the last line is shown.
## </Item>
## </List>
##
# If the text contains ANSI color sequences we reset the terminal before
# we print the last line.
BindGlobal("PAGER_BUILTIN", function( r )
local formatted, linepos, exitAtEnd, lines, size, wd, pl, count, i, stream,
halt, lenhalt, delhaltline, from, len, emptyline, char, out;
formatted := false;
linepos := 1;
exitAtEnd:= true;
# don't print this to LOG files
out := OutputTextUser();
if IsRecord(r) then
lines := r.lines;
else
lines := r;
fi;
if IsString(lines) then
lines := SplitString(lines, "\n", "");
elif not formatted then
lines := ShallowCopy(lines);
fi;
if Length( lines ) = 0 then
return;
fi;
if IsRecord(r) then
if IsBound(r.formatted) then
formatted := r.formatted;
if formatted <> true and formatted <> false then
Error("unsupported r.formatted value: ", formatted);
fi;
fi;
if IsBound(r.start) then
if IsPosInt(r.start) then
linepos := r.start;
elif IsString(r.start) then
linepos := PositionProperty(lines,
l -> PositionSublist(l, r.start) <> fail);
if linepos = fail then
linepos := 1;
fi;
else
Error("unsupported r.start value: ", r.start);
fi;
fi;
if IsBound( r.exitAtEnd ) then
exitAtEnd:= r.exitAtEnd;
if exitAtEnd <> true and exitAtEnd <> false then
Error("unsupported r.exitAtEnd value: ", exitAtEnd);
fi;
fi;
fi;
size := SizeScreen();
wd := QuoInt(size[1]+2, 2);
# really print line without breaking it
pl := function(l, final)
local r;
r := 1;
while r*wd<=Length(l) do
PrintTo(out, l{[(r-1)*wd+1..r*wd]}, "\c");
r := r+1;
od;
if (r-1)*wd < Length(l) then
PrintTo(out, l{[(r-1)*wd+1..Length(l)]});
fi;
PrintTo(out, final);
end;
if not formatted then
# cope with overfull lines
count:=1;
while count<=Length(lines) do
if Length(lines[count])>size[1]-2 then
# find the last blank before this position
i:=size[1]-2;
while i>0 and lines[count][i]<>' ' do
i:=i-1;
od;
if i>0 then
if not IsBound(lines[count+1]) then
lines[count+1]:="";
fi;
lines[count+1]:=Concatenation(
lines[count]{[i+1..Length(lines[count])]}," ", lines[count+1]);
lines[count]:=lines[count]{[1..i-1]};
fi;
fi;
count:=count+1;
od;
fi;
stream := InputTextFile("*errin*");
count := 0;
halt :=
" -- <space> page, <n> next line, <b> back, <p> back line, <q> quit --\c";
# remember number of visible characters
lenhalt := Length(halt)-1;
if UserPreference("UseColorsInTerminal") = true then
halt := Concatenation("\033[0m", halt);
fi;
delhaltline := function()
local i;
for i in [1..lenhalt] do
PrintTo(out, "\b\c \c\b\c" );
od;
end;
from := linepos;
len := Length(lines);
emptyline:= String( "", size[1]-2 );
if len < from then
# Ignore the start line.
# (The pager 'less' shows a warning and then goes to the first line.)
from:= 1;
fi;
repeat
for i in [from..Minimum(len, from+size[2]-2)] do
pl(lines[i], "\n");
od;
if len = i then
if exitAtEnd then
break;
fi;
for i in [ len+1 .. from+size[2]-2 ] do
pl( emptyline, "\n" );
od;
fi;
pl(halt, "");
repeat
char := ReadByte(stream);
if char = fail then
char := 'q';
else
char := CHAR_INT(char);
fi;
until char in " nbpq";
if char = ' ' and i < len then
from := from+size[2]-1;
elif char = 'n' and i < len then
from := from+1;
elif char = 'p' and from>1 then
from := from-1;
elif char = 'b' then
from := Maximum(1, from-size[2]+1);
fi;
delhaltline();
until char = 'q';
CloseStream(stream);
end);
# for using `more' or `less' or ... (read from `UserPreference("Pager")')
# we assume that UserPreference("Pager") allows command line option
# +num for starting display in line num
BindGlobal("PAGER_EXTERNAL", function( lines )
local pager, linepos, str, i, cmdargs, stream;
pager := UserPreference("Pager");
if not (Length(pager) > 0 and pager[1] = '/' and IsExecutableFile(pager))
then
pager := PathSystemProgram( UserPreference("Pager") );
fi;
if pager=fail then
Error( "Pager ", UserPreference("Pager"),
" not found, reset with `SetUserPreference(\"Pager\", ...);'." );
fi;
linepos := 1;
if IsRecord(lines) then
if IsBound(lines.start) then
linepos := lines.start;
if not (IsPosInt(lines.start) or IsString(lines.start)) then
Error("unsupported lines.start value: ", linepos);
fi;
fi;
lines := lines.lines;
fi;
if not IsString(lines) then
str:="";
for i in lines do
Append(str,i);
Add(str,'\n');
od;
lines := str;
fi;
if IsString(linepos) then
cmdargs := [Concatenation("+/", linepos)];
elif linepos > 1 then
cmdargs := [Concatenation("+", String(linepos))];
else
cmdargs := [];
fi;
stream:=InputTextString(lines);
Process(DirectoryCurrent(), pager, stream, OutputTextUser(),
Concatenation( UserPreference("PagerOptions"), cmdargs ));
end);
InstallGlobalFunction("Pager", function(lines)
if UserPreference("Pager") = "builtin" then
PAGER_BUILTIN(lines);
else
PAGER_EXTERNAL(lines);
fi;
end);
BindGlobal( "PagerAsHelpViewer", function( lines )
if UserPreference( "Pager" ) = "builtin" then
if IsRecord( lines ) then
lines.exitAtEnd:= false;
else
lines:= rec( lines:= lines, exitAtEnd:= false );
fi;
PAGER_BUILTIN( lines );
else
PAGER_EXTERNAL( lines );
fi;
end );
[ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet)
]
|