GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
#############################################################################
##
#W cmdleditx.g GAP library Frank Lübeck
##
##
#Y Copyright (C) 2010 The GAP Group
##
## This is outdated experimental code for command line editing and a history
## and demo mechanism which is only used when GAP is not compiled with
## libreadline. It is kept temporarily for people who have difficulties
## compiling with libreadline.
##
############################################################################
##
#F LineEditKeyHandler( <l> )
##
## This function is called from the kernel in command line editing mode
## if a key number <n> is pressed for which `LineEditKeyHandlers[ <n> + 1 ]
## is bound to some key handler function. It does some checking of the result
## of the key handler functions.
##
## The argument <l> for this and for the key handler functions is a list of
## the form `[linestr, ch, ppos, length, yankstr]' where `linestr' is a string
## with the content of the current input line, `ch' is the key pressed (as
## integer), `ppos' is the position of the cursor on the input line, `length'
## is the maximal length of the current input line and `yankstr' is a string
## with the content of the current yank buffer.
##
## The handler functions usually must return a list `[linestr, ppos, yankstr]'
## where `linestr' is a string containing the new content of the input line,
## `ppos' is the new position of the cursor (in [1..Length(linestr)+1]) and
## `yankstr' is the new value of the yank buffer.
##
## The exception is that a handler function can also return a positive small
## integer <n>. In that case the next <n> input lines (including the current
## line) are read by {\GAP} by calling <n> times the key handler for `<ESC>-N'.
##
## The default handler for `<ESC>-N' does the following: It assumes that
## `AutomaticInputLines' is a list of strings and that
## `AutomaticInputLinesCounter' is a positive integer, it returns as current
## input line entry number `AutomaticInputLinesCounter' of
## `AutomaticInputLines' and increases the counter by one.
## The key `<ESC>-S' is bound to deliver all lines currently bound to
## `AutomaticInputLines' as input to {\GAP}. Typing `<ESC>-S' on the second
## input line below, leads to the following:
##
## \beginexample
## gap> AutomaticInputLines := ["a:=1;", "b:=2;", "c:=a+b;"];;
## gap> a:=1;
## 1
## gap> b:=2;
## 2
## gap> c:=a+b;
## 3
## \endexample
##
## The key numbers are computed as follows: For ascii characters <k> they
## are given by `INT_CHAR(<k>)'. Combined with the `Ctrl' key has number
## `INT_CHAR(<k>) mod 32' and combined with the `Esc' key (pressed before)
## the number is `INT_CHAR(<k>) + 256'.
##
BindGlobal("LineEditKeyHandlers", []);
# args: [linestr, ch, ppos, length, yankstr]
# returns: [linestr, ppos, yankstr]
BindGlobal("LineEditKeyHandler", function(l)
local res, lin;
if not IsBound(LineEditKeyHandlers[l[2]+1]) then
return [l[1], l[3], l[5]];
fi;
res := LineEditKeyHandlers[l[2]+1](l);
## if not IS_INT(res) and not (IS_STRING_REP(res[1]) and
## LENGTH(res[1]) < l[4]-1 and
## IS_STRING_REP(res[3]) and LENGTH(res[3]) < 32768 and
## res[2] < l[4] and res[2] <= LENGTH(res[1])+1) then
if not (IsSmallIntRep(res) and res >= 0) and not (IsStringRep(res[1]) and
Length(res[1]) < l[4]-1 and
IsStringRep(res[3]) and Length(res[3]) < 32768 and
res[2] < l[4] and res[2] <= Length(res[1])+1) then
Error("Key handler for line editing produced invalid result.");
fi;
return res;
end);
############################################################################
##
#V CommandLineHistory
#V MaxCommandLineHistory
##
## The input lines from a {\GAP} session with command line editing switched on
## are stored in the list `CommandLineHistory'. This list is of form
## `[pos, line1, line2, ..., lastline]' where pos is an integer which defines
## a current line number in the history, and the remaining entries are input
## lines for {\GAP} (without a trailing '\n').
##
## If the integer `MaxCommandLineHistory' is equal to `0' all input lines of
## a session are stored. If it has a positive value then it specifies the
## maximal number of input lines saved in the history.
##
# init empty history
BindGlobal("CommandLineHistory", [1]);
MaxCommandLineHistory := 0;
# history position from previous line
LastPosCLH := 1;
# here we implement the command line handlers for the keys
# Ctrl-P, Ctrl-N, Ctrl-L, Esc-<, Esc->
# key number 0 is used as a hook for saving a new line in the history
BindGlobal("CommandLineHistoryHandler", function(l)
local key, hist, n, m, start, res, i;
key := l[2];
hist := CommandLineHistory;
if key = 0 then # save line data
# no trailing white space
while Length(l[1]) > 0 and l[1][Length(l[1])] in "\n\r\t " do
Unbind(l[1][Length(l[1])]);
od;
MaxCommandLineHistory := UserPreference("HistoryMaxLines");
if not IsInt(MaxCommandLineHistory) then
MaxCommandLineHistory := 0;
fi;
if MaxCommandLineHistory > 0 and
Length(hist) >= MaxCommandLineHistory+1 then
# overrun, throw oldest line away
for i in [2..Length(hist)-1] do
hist[i] := hist[i+1];
od;
hist[Length(hist)] := l[1];
if hist[1] > 2 then
hist[1] := hist[1]-1;
else
hist[1] := Length(hist)+1;
fi;
else
Add(hist, l[1]);
fi;
LastPosCLH := hist[1];
hist[1] := Length(hist)+1;
return [l[1], l[3], l[5]];
elif key = 16 then # CTR('P')
# searching backward in history for line starting with input before
# cursor
n := hist[1];
if n < 2 then n := Length(hist)+1; fi;
m := l[3]-1;
start := l[1]{[1..m]};
for i in [n-1,n-2..2] do
hist[1] := i;
if Length(hist[i]) >= m and hist[i]{[1..m]} = start then
if hist[1] < 2 then
hist[1] := Length(hist)+1;
fi;
return [hist[i], l[3], l[5]];
fi;
od;
# not found, point to last line
hist[1] := Length(hist)+1;
return [start, l[3], l[5]];
elif key = 14 then # CTR('N')
# searching forward in history for line starting with input before
# cursor; first time for current line we start at last history pointer
# from previous line (so one can repeat a sequence of lines by
# repeated ctrl-N.
if Length(hist) = 1 then return [l[1],l[3],l[5]]; fi;
if hist[1] = Length(hist)+1 then
if LastPosCLH < hist[1]-1 then
hist[1] := LastPosCLH;
LastPosCLH := Length(hist)+1;
else
hist[1] := 2;
fi;
fi;
m := l[3]-1;
start := l[1]{[1..m]};
for i in [hist[1]+1..Length(hist)] do
hist[1] := i;
if Length(hist[i]) >= m and hist[i]{[1..m]} = start then
return [hist[i], l[3], l[5]];
fi;
od;
# not found, point after newest line
hist[1] := Length(hist)+1;
return [start, l[3], l[5]];
elif key = 12 then # CTR('L')
if Length(hist) = 1 then return [l[1],l[3],l[5]]; fi;
res := l[1]{[1..l[3]-1]};
Append(res, hist[Length(hist)]);
Append(res, l[1]{[l[3]..Length(l[1])]});
return [res, l[3] + Length(hist[Length(hist)]), l[5]];
elif key = 316 then # ESC('<')
if hist[1] > 1 then
hist[1] := 2;
return [hist[2], 1, l[5]];
else
return ["", 1, l[5]];
fi;
elif key = 318 then # ESC('>')
if hist[1] > 1 then
hist[1] := Length(hist)+1;
fi;
return ["", 1, l[5]];
else
Error("Cannot handle command line history with key ", key);
fi;
end);
# install the handlers for the history commands
for tmpclh in [0, 16, 14, 12, 316, 318] do
LineEditKeyHandlers[tmpclh+1] := CommandLineHistoryHandler;
od;
Unbind(tmpclh);
############################################################################
##
#F SaveCommandLineHistory( [<fname>] )
#F ReadCommandLineHistory( [<fname>] )
##
## Use the first command to write the currently saved command lines in the
## history to file <fname>. If not given the default file name `~/.gap_hist'
## is used. The second command prepends the lines from <fname> to the current
## command line history.
##
BindGlobal("SaveCommandLineHistory", function(arg)
local fnam, hist, max, start, i;
if Length(arg) > 0 then
fnam := arg[1];
else
fnam := "~/.gap_hist";
fi;
hist := CommandLineHistory;
max := UserPreference("HistoryMaxLines");
if IsInt(max) and max > 0 and Length(hist)+1 > max then
start := Length(hist)-max+1;
else
start := 2;
fi;
PrintTo(fnam,"");
for i in [start..Length(hist)] do
AppendTo(fnam, hist[i], "\n");
od;
end);
BindGlobal("ReadCommandLineHistory", function(arg)
local fnam, hist, s, n;
if Length(arg) > 0 then
fnam := arg[1];
else
fnam := "~/.gap_hist";
fi;
hist := CommandLineHistory;
s := StringFile(fnam);
if IsString(s) then
s := SplitString(s,"","\n");
MaxCommandLineHistory := UserPreference("HistoryMaxLines");
if not IsInt(MaxCommandLineHistory) then
MaxCommandLineHistory := 0;
fi;
if MaxCommandLineHistory > 0 and
Length(s) + Length(hist) - 1 > MaxCommandLineHistory then
n := MaxCommandLineHistory + 1 - Length(hist);
s := s{[Length(s)-n+1..Length(s)]};
fi;
hist{[Length(s)+2..Length(s)+Length(hist)]} := hist{[2..Length(hist)]};
hist{[2..Length(s)+1]} := s;
fi;
hist[1] := Length(hist) + 1;
end);
# Implementation of the default ESC-N and ESC-S behaviour described above.
AutomaticInputLines := [];
AutomaticInputLinesCounter := 1;
BindGlobal("DefaultEscNHandler", function(arg)
local res;
if AutomaticInputLinesCounter <= Length(AutomaticInputLines) then
res := AutomaticInputLines[AutomaticInputLinesCounter];
AutomaticInputLinesCounter := AutomaticInputLinesCounter + 1;
return [res, Length(res)+1, arg[1][5]];
else
return ["",1,arg[1][5]];
fi;
end);
LineEditKeyHandlers[334+1] := DefaultEscNHandler;
# ESC('S') calls Length(AutomaticInputLines) often ESC('N')
LineEditKeyHandlers[339+1] := function(arg)
AutomaticInputLinesCounter := 1;
return Length(AutomaticInputLines);
end;
## Standard behaviour to insert a character for the key.
## We don't install this directly in LineEditKeyHandlers but this can be
## useful for writing other key handlers)
BindGlobal("LineEditInsert", function(l)
local line;
line := l[1]{[1..l[3]-1]};
Add(line, CHAR_INT(l[2]));
Append(line, l[1]{[l[3]..Length(l[1])]});
return [line, l[3]+1, l[5]];
end);
## This will be installed as handler for the space-key, it removes the prompts
## "gap> ", "> ", "brk> " in beginning of lines when the trailing space is
## typed. This makes a special hack in the kernel unnecessary and it can
## be switched off by setting 'GAPInfo.DeletePrompts := false;'.
GAPInfo.DeletePrompts := true;
BindGlobal("LineEditDelPrompt", function(l);
if GAPInfo.DeletePrompts and l[1]{[1..l[3]-1]} in ["gap>", ">", "brk>"] then
return [l[1]{[l[3]..Length(l[1])]}, 1, l[5]];
else
return LineEditInsert(l);
fi;
end);
LineEditKeyHandlers[33] := LineEditDelPrompt;
############################################################################
## readline interface functions
if not IsBound(GAPInfo.History) then
GAPInfo.History := rec(MaxLines := -1, Lines := [], Pos := 0);
fi;
GAPInfo.History.AddLine := function(l)
local hist, len;
hist := GAPInfo.History;
# if history switched off
if hist.MaxLines = 0 then
return;
fi;
# no trailing white space
len := Length(l);
## while len > 0 and l[len] in "\n\r\t " do
## Remove(l);
## len := len - 1;
## od;
# no empty lines
if len = 0 then
return;
fi;
if hist.MaxLines > 0 and Length(hist.Lines) >= hist.MaxLines then
# overrun, throw oldest line away
Remove(hist.Lines, 1);
fi;
Add(hist.Lines, l);
hist.Pos := Length(hist.Lines) + 1;
end;
GAPInfo.History.PrevLine := function(start)
local hist, len, pos, first;
hist := GAPInfo.History;
len := Length(start);
pos := hist.Pos - 1;
if pos = 0 then
pos := Length(hist.Lines);
fi;
first := pos;
repeat
if PositionSublist(hist.Lines[pos], start) = 1 then
hist.Pos := pos;
return hist.Lines[pos];
fi;
if pos > 1 then
pos := pos - 1;
else
pos := Length(hist.Lines);
fi;
until pos = first;
end;