CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

| Download

GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it

Views: 418346
#############################################################################
##
#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;