File: pager.gi

package info (click to toggle)
gap 4.15.1-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 110,212 kB
  • sloc: ansic: 97,261; xml: 48,343; cpp: 13,946; sh: 4,900; perl: 1,650; javascript: 255; makefile: 252; ruby: 9
file content (354 lines) | stat: -rw-r--r-- 11,330 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#############################################################################
##
##  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 );