Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
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
Project: cocalc-sagemath-dev-slelievre
Views: 418346############################################################################# ## #W cmdledit.g GAP library Frank Lübeck ## ## #Y Copyright (C) 2010 The GAP Group ## ## This file contains function for handling some keys in line edit mode. ## It is only used if the GAP kernel was compiled to use the GNU ## readline library. ## ## To avoid using the readline library, pass '--without-readline' to ## the configure script when compiling GAP. ## if GAPInfo.CommandLineOptions.E then ############################################################################ ## readline interface functions GAPInfo.UseReadline := true; ## <#GAPDoc Label="readline"> ## <Section Label="sec:readline"> ## <Heading>Editing using the <C>readline</C> library</Heading> ## ## The descriptions in this section are valid only if your &GAP; ## installation uses the <C>readline</C> library for command line editing. ## You can check by <C>IsBound(GAPInfo.UseReadline);</C> if this is the ## case. <P/> ## ## You can use all the features of ## <C>readline</C>, as for example explained ## in <URL>http://tiswww.case.edu/php/chet/readline/rluserman.html</URL>. ## Therefore the command line editing in &GAP; is similar to the ## <C>bash</C> shell and many other programs. On a Unix/Linux system you ## may also have a manpage, try <C>man readline</C>. <P/> ## ## Compared to the command line editing which was used in &GAP; up to ## version 4.4 (or compared to not using the <C>readline</C> library) ## using <C>readline</C> has several advantages: ## <List> ## <Item>Most keys still do the same as explained in ## <Ref Sect="Line Editing"/> (in the default configuration). ## </Item> ## <Item>There are many additional commands, e.g. undoing (<B>Ctrl-_</B>, ## keyboard macros (<B>Ctrl-x(</B>, <B>Ctrl-x)</B> and <B>Ctrl-xe</B>), ## file name completion (hit <B>Esc</B> two or four times), ## showing matching parentheses, ## <C>vi</C>-style key bindings, deleting and yanking text, ...</Item> ## <Item>Lines which are longer than a physical terminal row can be edited ## more conveniently.</Item> ## <Item>Arbitrary unicode characters can be typed into string literals. ## </Item> ## <Item>The key bindings can be configured, either via your ## <File>~/.inputrc</File> file or by &GAP; commands, see <Ref ## Subsect="ssec:readlineCustom"/>.</Item> ## <Item>The command line history can be saved to and read from a file, see ## <Ref Subsect="ssec:cmdlinehistory"/>.</Item> ## <!-- <Item>demo mode <Ref Subsect="ssec:demoreadline"/>???</Item> --> ## <Item>Adventurous users can even implement completely new ## command line editing functions on &GAP; level, see <Ref ## Subsect="ssec:readlineUserFuncs"/>.</Item> ## ## </List> ## <P/> ## ## <Subsection Label="ssec:readlineCustom"> ## <Index Key="ReadlineInitLine"><C>ReadlineInitLine</C></Index> ## <Heading>Readline customization</Heading> ## ## You can use your readline init file (by default <File>~/.inputrc</File> ## on Unix/Linux) to customize key bindings. If you want settings be used ## only within &GAP; you can write them between lines containing <C>$if ## GAP</C> and <C>$endif</C>. For a detailed documentation of the available ## settings and functions see <URL ## Text="here">http://tiswww.case.edu/php/chet/readline/rluserman.html</URL>. ## ## <Listing Type="From readline init file"> ## $if GAP ## set blink-matching-paren on ## "\C-n": dump-functions ## "\ep": kill-region ## $endif ## </Listing> ## ## Alternatively, from within &GAP; the command ## <C>ReadlineInitLine(<A>line</A>);</C> can be used, where <A>line</A> is ## a string containing a line as in the init file. ## <P/> ## ## Note that after pressing <B>Ctrl-v</B> the next special character is ## input verbatim. This is very useful to bind keys or key sequences. ## For example, binding the function key <B>F3</B> to the command ## <C>kill-whole-line</C> by using the sequence <B>Ctrl-v</B> <B>F3</B> ## looks on many terminals like this: ## <C>ReadlineInitLine("\"^[OR\":kill-whole-line");</C>. ## (You can get the line back later with <B>Ctrl-y</B>.) ## <P/> ## ## The <B>Ctrl-g</B> key can be used to type any unicode character by its code ## point. The number of the character can either be given as a count, or if the ## count is one the input characters before the cursor are taken (as decimal ## number or as hex number which starts with <C>0x</C>. For example, the ## double stroke character ℤ can be input by any of the three key ## sequences <B>Esc 8484 Ctrl-g</B>, <B>8484 Ctrl-g</B> or <B>0x2124 ## Ctrl-g</B>. ## <P/> ## ## Some terminals bind the <B>Ctrl-s</B> and <B>Ctrl-q</B> keys to stop and ## restart terminal output. Furthermore, sometimes <B>Ctrl-\</B> quits a ## program. To disable this behaviour (and maybe use these keys for command ## line editing) you can use <C>Exec("stty stop undef; stty start undef; ## stty quit undef");</C> in your &GAP; session or your <F>gaprc</F> file ## (see <Ref Sect="sect:gap.ini"/>). ## <P/> ## </Subsection> ## ## <Subsection Label="ssec:cmdlinehistory"> ## <Heading>The command line history</Heading> ## ## &GAP; can save your input lines for later reuse. The keys <B>Ctrl-p</B> ## (or <B>Up</B>), <B>Ctrl-n</B> (or <B>Down</B>), ## <B>ESC<</B> and <B>ESC></B> work as documented in <Ref ## Sect="Line Editing"/>, that is they scroll backward and ## forward in the history or go to its beginning or end. ## Also, <B>Ctrl-o</B> works as documented, it is useful for repeating a ## sequence of previous lines. ## (But <B>Ctrl-l</B> clears the screen as in other programs.) ## <P/> ## ## The command line history can be used across several instances of &GAP; ## via the following two commands. ## </Subsection> ## ## <ManSection > ## <Func Arg="[fname], [app]" Name="SaveCommandLineHistory" /> ## <Returns><K>fail</K> or number of saved lines</Returns> ## <Func Arg="[fname]" Name="ReadCommandLineHistory" /> ## <Returns><K>fail</K> or number of added lines</Returns> ## ## <Description> ## The first command saves the lines in the command line history to the ## file given by the string <A>fname</A>. The default for <A>fname</A> is ## <F>history</F> in the user's &GAP; root path <C>GAPInfo.UserGapRoot</C> ## or <F>"~/.gap_hist"</F> if this directory does not exist. ## If the optional argument <A>app</A> is ## <K>true</K> then the lines are appended to that file otherwise the file ## is overwritten. ## <P/> ## The second command is the converse, it reads the lines from file ## <A>fname</A> and <Emph>prepends</Emph> them to the current command line ## history. ## <P/> ## By default an arbitrary number of input lines is stored in the ## command line history. For very long &GAP; sessions or if <Ref ## Func="SaveCommandLineHistory"/> and <Ref Func="ReadCommandLineHistory"/> ## are used repeatedly it can be sensible to restrict the number of saved ## lines via <C>SetUserPreference("HistoryMaxLines", num);</C> ## to a non negative number <C>num</C> (the default is <K>infinity</K>). ## An automatic storing and restoring of the command line history can ## be configured via ## <C>SetUserPreference("SaveAndRestoreHistory", true);</C>. ## <P/> ## Note that these functions are only available if your &GAP; is configured ## to use the <C>readline</C> library. ## </Description> ## </ManSection> ## ## <Subsection Label="ssec:readlineUserFuncs"> ## <Index Key="InstallReadlineMacro"><C>InstallReadlineMacro</C></Index> ## <Index Key="InvocationReadlineMacro"><C>InvocationReadlineMacro</C></Index> ## ## <Heading>Writing your own command line editing functions</Heading> ## It is possible to write new command line editing functions in &GAP; as ## follows. ## <P/> ## The functions have one argument <A>l</A> which is a list with five ## entries of the form <C>[count, key, line, cursorpos, markpos]</C> where ## <C>count</C> and <C>key</C> are the last pressed key and its count ## (these are not so useful here because users probably do not want to ## overwrite the binding of a single key), then <C>line</C> is a string ## containing the line typed so far, <C>cursorpos</C> is the current ## position of the cursor (point), and <C>markpos</C> the current position ## of the mark. ## <P/> ## The result of such a function must be a list which can have various ## forms: ## <List > ## <Mark><C>[str]</C></Mark> ## <Item>with a string <C>str</C>. In this case the text <C>str</C> is ## inserted at the cursor position.</Item> ## <Mark><C>[kill, begin, end]</C></Mark> ## <Item> where <C>kill</C> is <K>true</K> or <K>false</K> and <C>begin</C> ## and <C>end</C> are positions on the input line. This removes the text ## from the lower position to before the higher position. If <C>kill</C> ## is <K>true</K> the text is killed, i.e. put in the kill ring for later ## yanking. ## </Item> ## <Mark><C>[begin, end, str]</C></Mark> ## <Item>where <C>begin</C> and <C>end</C> are positions on the input line ## and <C>str</C> is a string. ## Then the text from position <C>begin</C> to before <C>end</C> is ## substituted by <C>str</C>. ## </Item> ## <Mark><C>[1, lstr]</C></Mark> ## <Item> ## where <C>lstr</C> is a list of strings. Then these strings are displayed ## like a list of possible completions. The input line is not changed. ## </Item> ## <Mark><C>[2, chars]</C></Mark> ## <Item>where <C>chars</C> is a string. The characters from <C>chars</C> ## are used as the next characters from the input. (At most 512 characters ## are possible.)</Item> ## <Mark><C>[100]</C></Mark> ## <Item>This rings the bell as configured in the terminal.</Item> ## </List> ## ## In the first three cases the result list can contain a position as a ## further entry, this becomes the new cursor position. Or it ## can contain two positions as further entries, these become the new ## cursor position and the new position of the mark. ## <P/> ## ## Such a function can be installed as a macro for <C>readline</C> via ## <C>InstallReadlineMacro(name, fun);</C> where <C>name</C> is a string ## used as name of the macro and <C>fun</C> is a function as above. ## This macro can be called by a key sequence which is returned by ## <C>InvocationReadlineMacro(name);</C>. ## <P/> ## As an example we define a function which puts double quotes around the ## word under or before the cursor position. The space character, the ## characters in <C>"(,)"</C>, and the beginning and end of the line ## are considered as word boundaries. The function is then installed as a ## macro and bound to the key sequence <B>Esc</B> <B>Q</B>. ## <P/> ## <Example> ## gap> EditAddQuotes := function(l) ## > local str, pos, i, j, new; ## > str := l[3]; ## > pos := l[4]; ## > i := pos; ## > while i > 1 and (not str[i-1] in ",( ") do ## > i := i-1; ## > od; ## > j := pos; ## > while IsBound(str[j]) and not str[j] in ",) " do ## > j := j+1; ## > od; ## > new := "\""; ## > Append(new, str{[i..j-1]}); ## > Append(new, "\""); ## > return [i, j, new]; ## > end;; ## gap> InstallReadlineMacro("addquotes", EditAddQuotes); ## gap> invl := InvocationReadlineMacro("addquotes");; ## gap> ReadlineInitLine(Concatenation("\"\\eQ\":\"",invl,"\""));; ## </Example> ## </Subsection> ## ## </Section> ## <#/GAPDoc> ## if not IsBound(GAPInfo.CommandLineEditFunctions) then GAPInfo.CommandLineEditFunctions := rec( # This is the GAP function called by the readline handler function # handled-by-GAP (GAP_rl_func in src/sysfiles.c). KeyHandler := function(l) local macro, res, key; # remember this key key := l[2]; res:=[]; if l[2] >= 1000 then macro := QuoInt(l[2], 1000); if IsBound(GAPInfo.CommandLineEditFunctions.Macros.(macro)) then res := GAPInfo.CommandLineEditFunctions.Macros.(macro)(l); fi; else if IsBound(GAPInfo.CommandLineEditFunctions.Functions.(l[2])) then res := GAPInfo.CommandLineEditFunctions.Functions.(l[2])(l); fi; fi; GAPInfo.CommandLineEditFunctions.LastKey := key; return res; end, Macros := rec(), Functions := rec(), # here we save readline init lines for post restore RLInitLines := [], RLKeysGAPHandler := [] ); fi; # wrapper around kernel functions to store data for post restore function BindGlobal("ReadlineInitLine", function(str) READLINEINITLINE(ShallowCopy(str)); Add(GAPInfo.CommandLineEditFunctions.RLInitLines, str); end); BindGlobal("BindKeysToGAPHandler", function(str) BINDKEYSTOGAPHANDLER(ShallowCopy(str)); Add(GAPInfo.CommandLineEditFunctions.RLKeysGAPHandler, str); end); CallAndInstallPostRestore( function() local clef, l, a; clef := GAPInfo.CommandLineEditFunctions; l := clef.RLKeysGAPHandler; clef.RLKeysGAPHandler := []; for a in l do BindKeysToGAPHandler(a); od; l := clef.RLInitLines; clef.RLInitLines := []; for a in l do ReadlineInitLine(a); od; end); # bind macro to a key sequence BindGlobal("BindKeySequence", function(seq, subs) ReadlineInitLine(Concatenation("\"", seq, "\": \"", subs, "\"")); end); # general utility functions # ringing bell according to terminal configuration (rl_ding) GAPInfo.CommandLineEditFunctions.Functions.RingBell := function(arg) return [100]; end; # sends <Return> and so calls accept-line GAPInfo.CommandLineEditFunctions.Functions.AcceptLine := function(arg) return [101]; end; # cands is list of strings, this displays them as matches for completion # (rl_display_match_list) GAPInfo.CommandLineEditFunctions.Functions.DisplayMatches := function(cand) return [1, cand]; end; # this inserts a sequence of keys given as string into the input stream # (rl_stuff_char, up to 512 characters are accepted) GAPInfo.CommandLineEditFunctions.Functions.StuffChars := function(str) return [2, str]; end; GAPInfo.CommandLineEditFunctions.Functions.UnicodeChar := function(l) local helper, j, i, hc, hex, c, pos, k; # same as GAPDoc's UNICODE_RECODE.UTF8UnicodeChar helper := function ( n ) local res, a, b, c, d; res := ""; if n < 0 then return fail; elif n < 128 then Add( res, CHAR_INT( n ) ); elif n < 2048 then a := n mod 64; b := (n - a) / 64; Add( res, CHAR_INT( b + 192 ) ); Add( res, CHAR_INT( a + 128 ) ); elif n < 65536 then a := n mod 64; n := (n - a) / 64; b := n mod 64; c := (n - b) / 64; Add( res, CHAR_INT( c + 224 ) ); Add( res, CHAR_INT( b + 128 ) ); Add( res, CHAR_INT( a + 128 ) ); elif n < 2097152 then a := n mod 64; n := (n - a) / 64; b := n mod 64; n := (n - b) / 64; c := n mod 64; d := (n - c) / 64; Add( res, CHAR_INT( d + 240 ) ); Add( res, CHAR_INT( c + 128 ) ); Add( res, CHAR_INT( b + 128 ) ); Add( res, CHAR_INT( a + 128 ) ); else return fail; fi; return res; end; # if count=1 we consider the previous characters if l[1] = 1 then j := l[4]-1; i := j; hc := "0123456789abcdefABCDEF"; while i > 0 and l[3][i] in hc do i := i-1; od; if i>1 and l[3][i] = 'x' and l[3][i-1] = '0' then hex := true; i := i-1; else hex := false; i := i+1; fi; c := 0; if hex then for k in [i+2..j] do pos := Position(hc, l[3][k]); if pos > 16 then pos := pos-6; fi; c := c*16+(pos-1); od; else for k in [i..j] do pos := Position(hc, l[3][k]); c := c*10 + (pos-1); od; fi; return [i, j+1, helper(c)]; else return [helper(l[1])]; fi; end; GAPInfo.CommandLineEditFunctions.Functions.7 := GAPInfo.CommandLineEditFunctions.Functions.UnicodeChar; BindKeysToGAPHandler("\007"); # The history is stored within the GAPInfo record. Several GAP level # command line edit functions below deal with the history. The maximal # number of lines in the history is configurable via a user preference. if not IsBound(GAPInfo.History) then GAPInfo.History := rec(Lines := [], Pos := 0, Last := 0); fi; DeclareUserPreference( rec( name:= ["HistoryMaxLines", "SaveAndRestoreHistory"], description:= [ "HistoryMaxLines is the maximal amount of input lines held in GAPs \ command line history.", "If SaveAndRestoreHistory is true then GAP saves its command line history \ before terminating a GAP session, and prepends the stored history when GAP is \ started. If this is enabled it is suggested to set HistoryMaxLines to some \ finite value.", "These preferences are ignored if GAP was not compiled with \ readline support.", ], default:= [infinity, false], check:= function(max, save) return ((IsInt( max ) and 0 <= max) or max = infinity) and save in [true, false]; end ) ); ## We use key 0 (not bound) to add line to history. GAPInfo.CommandLineEditFunctions.Functions.AddHistory := function(l) local i, max, hist; max := UserPreference("HistoryMaxLines"); # no history if max <= 0 then return []; fi; # no trailing white space i := 0; while Length(l[3]) > 0 and l[3][Length(l[3])] in "\n\r\t " do Remove(l[3]); i := i + 1; od; if Length(l[3]) = 0 then return [false, 1, i+1, 1]; fi; hist := GAPInfo.History.Lines; while Length(hist) >= max do # overrun, throw oldest line away Remove(hist, 1); GAPInfo.History.Last := GAPInfo.History.Last - 1; od; Add(hist, l[3]); GAPInfo.History.Pos := Length(hist) + 1; if i = 0 then return []; else return [false, Length(l[3])+1, Length(l[3]) + i + 1]; fi; end; GAPInfo.CommandLineEditFunctions.Functions.0 := GAPInfo.CommandLineEditFunctions.Functions.AddHistory; ## C-p: previous line starting like current before point GAPInfo.CommandLineEditFunctions.Functions.BackwardHistory := function(l) local hist, n, start; if UserPreference("HistoryMaxLines") <= 0 then return []; fi; hist := GAPInfo.History.Lines; n := GAPInfo.History.Pos; # searching backward in history for line starting with input before cursor if l[4] = Length(l[3]) + 1 then start := l[3]; else start := l[3]{[1..l[4]-1]}; fi; while n > 1 do n := n - 1; if PositionSublist(hist[n], start) = 1 then GAPInfo.History.Pos := n; GAPInfo.History.Last := n; return [1, Length(l[3])+1, hist[n], l[4]]; fi; od; # not found, delete rest of line and wrap over GAPInfo.History.Pos := Length(hist)+1; if Length(start) = Length(l[3]) then return []; else return [false, l[4], Length(l[3])+1]; fi; end; # bind to C-p and map Up-key GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('P') mod 32) := GAPInfo.CommandLineEditFunctions.Functions.BackwardHistory; BindKeysToGAPHandler("\020"); ReadlineInitLine("\"\\eOA\": \"\\C-p\""); ReadlineInitLine("\"\\e[A\": \"\\C-p\""); ## C-n: next line starting like current before point GAPInfo.CommandLineEditFunctions.Functions.ForwardHistory := function(l) local hist, n, start; if UserPreference("HistoryMaxLines") <= 0 then return []; fi; hist := GAPInfo.History.Lines; n := GAPInfo.History.Pos; if n > Length(hist) then # special case on empty line, we don't wrap to the beginning, but # the position of the last history use if Length(l[3]) = 0 and GAPInfo.History.Last < Length(hist) then GAPInfo.History.Pos := GAPInfo.History.Last; n := GAPInfo.History.Pos; else n := 0; fi; fi; # searching forward in history for line starting with input before cursor if l[4] = Length(l[3]) + 1 then start := l[3]; else start := l[3]{[1..l[4]-1]}; fi; while n < Length(hist) do n := n + 1; if PositionSublist(hist[n], start) = 1 then GAPInfo.History.Pos := n; GAPInfo.History.Last := n; return [1, Length(l[3])+1, hist[n], l[4]]; fi; od; # not found, delete rest of line and wrap over GAPInfo.History.Pos := Length(hist)+1; if Length(start) = Length(l[3]) then return []; else return [false, l[4], Length(l[3])+1]; fi; end; # bind to C-n and map Down-key GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('N') mod 32) := GAPInfo.CommandLineEditFunctions.Functions.ForwardHistory; BindKeysToGAPHandler("\016"); ReadlineInitLine("\"\\eOB\": \"\\C-n\""); ReadlineInitLine("\"\\e[B\": \"\\C-n\""); ## ESC <: beginning of history GAPInfo.CommandLineEditFunctions.Functions.BeginHistory := function(l) if UserPreference("HistoryMaxLines") <= 0 or Length(GAPInfo.History.Lines) = 0 then return []; fi; GAPInfo.History.Pos := 1; GAPInfo.History.Last := 1; return [1, Length(l[3]), GAPInfo.History.Lines[1], 1]; end; GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('<')) := GAPInfo.CommandLineEditFunctions.Functions.BeginHistory; BindKeysToGAPHandler("\\e<"); ## ESC >: end of history GAPInfo.CommandLineEditFunctions.Functions.EndHistory := function(l) if UserPreference("HistoryMaxLines") <= 0 or Length(GAPInfo.History.Lines) = 0 then return []; fi; GAPInfo.History.Pos := Length(GAPInfo.History.Lines); GAPInfo.History.Last := GAPInfo.History.Pos; return [1, Length(l[3]), GAPInfo.History.Lines[GAPInfo.History.Pos], 1]; end; GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('>')) := GAPInfo.CommandLineEditFunctions.Functions.EndHistory; BindKeysToGAPHandler("\\e>"); ## C-o: line after last choice from history (for executing consecutive ## lines from the history GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('O') mod 32) := function(l) local n, cf; cf := GAPInfo.CommandLineEditFunctions; if IsBound(cf.ctrlo) then n := GAPInfo.History.Last + 1; if UserPreference("HistoryMaxLines") <= 0 or Length(GAPInfo.History.Lines) < n then return []; fi; GAPInfo.History.Last := n; Unbind(cf.ctrlo); return [1, Length(l[3]), GAPInfo.History.Lines[n], 1]; else cf.ctrlo := true; return GAPInfo.CommandLineEditFunctions.Functions.StuffChars("\015\017"); fi; end; BindKeysToGAPHandler("\017"); ## C-r: previous line containing text between mark and point (including ## the smaller, excluding the larger) GAPInfo.CommandLineEditFunctions.Functions.HistorySubstring := function(l) local hist, n, txt, pos; if UserPreference("HistoryMaxLines") <= 0 then return []; fi; hist := GAPInfo.History.Lines; n := GAPInfo.History.Pos; # text to search if l[4] < l[5] then if l[5] > Length(l[3])+1 then l[5] := Length(l[3])+1; fi; txt := l[3]{[l[4]..l[5]-1]}; else if l[5] < 1 then l[5] := 1; fi; txt := l[3]{[l[5]..l[4]-1]}; fi; while n > 1 do n := n - 1; pos := PositionSublist(hist[n], txt); if pos <> fail then GAPInfo.History.Pos := n; return [1, Length(l[3])+1, hist[n], pos + Length(txt), pos]; fi; od; # not found, do nothing and wrap over GAPInfo.History.Pos := Length(hist)+1; return []; end; GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('R') mod 32) := GAPInfo.CommandLineEditFunctions.Functions.HistorySubstring; BindKeysToGAPHandler("\022"); ############################################################################ ## #F SaveCommandLineHistory( [<fname>], [append] ) #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 'history' ## in GAPInfo.UserGapRoot or '~/.gap_hist' is used. ## The second command prepends the lines from <fname> to the current ## command line history (as much as possible when the user preference ## HistoryMaxLines is less than infinity). ## BindGlobal("SaveCommandLineHistory", function(arg) local fnam, append, hist, out, i; if Length(arg) > 0 then fnam := arg[1]; else if IsExistingFile(GAPInfo.UserGapRoot) then fnam := Concatenation(GAPInfo.UserGapRoot, "/history"); else fnam := USER_HOME_EXPAND("~/.gap_hist"); fi; fi; if true in arg then append := true; else append := false; fi; hist := GAPInfo.History.Lines; out := OutputTextFile(fnam, append); if out = fail then return fail; fi; SetPrintFormattingStatus(out, false); for i in [1..Length(hist)] do AppendTo(out, hist[i], "\n"); od; CloseStream(out); return Length(hist); end); BindGlobal("ReadCommandLineHistory", function(arg) local hist, max, fnam, s; hist := GAPInfo.History.Lines; max := UserPreference("HistoryMaxLines"); if Length(hist) >= max then return 0; fi; if Length(arg) > 0 and IsString(arg[1]) then fnam := arg[1]; else if IsExistingFile(GAPInfo.UserGapRoot) then fnam := Concatenation(GAPInfo.UserGapRoot, "/history"); else fnam := USER_HOME_EXPAND("~/.gap_hist"); fi; fi; s := StringFile(fnam); if s = fail then return fail; fi; GAPInfo.History.Last := 0; s := SplitString(s, "", "\n"); if Length(s) + Length(hist) > max then s := s{[Length(s)-max+Length(hist)+1..Length(s)]}; fi; hist{[Length(s)+1..Length(s)+Length(hist)]} := hist; hist{[1..Length(s)]} := s; return Length(s); end); ### Free: C-g, C-^ ## This deletes the content of current buffer line, when appending a space ## would result in a sequence of space- and tab-characters followed by the ## current prompt. Otherwise a space is inserted at point. GAPInfo.DeletePrompts := true; GAPInfo.CommandLineEditFunctions.Functions.SpaceDeletePrompt := function(l) local txt, len, pr, i; if GAPInfo.DeletePrompts <> true or l[4] = 1 or l[3][l[4]-1] <> '>' then return [" "]; fi; txt := l[3]; len := Length(txt); pr := CPROMPT(); Remove(pr); i := 1; while txt[i] in "\t " do i := i+1; od; if len - i+1 = Length(pr) and txt{[i..len]} = pr then return [false, 1, i+Length(pr), 1]; fi; return [" "]; end; GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR(' ')) := GAPInfo.CommandLineEditFunctions.Functions.SpaceDeletePrompt; BindKeysToGAPHandler(" "); # C-i: Completion as GAP level function GAPInfo.CommandLineEditFunctions.Functions.Completion := function(l) local cf, pos, word, idbnd, i, cmps, r, searchlist, cand, c, j; # check if Ctrl-i was hit repeatedly in a row cf := GAPInfo.CommandLineEditFunctions; if Length(l)=6 and l[6] = true and cf.LastKey = 9 then cf.tabcount := cf.tabcount + 1; else cf.tabcount := 1; Unbind(cf.tabrec); Unbind(cf.tabcompnam); fi; pos := l[4]-1; # in whitespace in beginning of line \t is just inserted while pos > 0 and l[3][pos] in " \t" do pos := pos-1; od; if pos = 0 then return ["\t"]; fi; pos := l[4]-1; # find word to complete while pos > 0 and l[3][pos] in IdentifierLetters do pos := pos-1; od; word := l[3]{[pos+1..l[4]-1]}; # see if we are in the case of a component name while pos > 0 and l[3][pos] in " \n\t\r" do pos := pos-1; od; idbnd := IDENTS_BOUND_GVARS(); if pos > 0 and l[3][pos] = '.' then cf.tabcompnam := true; if cf.tabcount = 1 then # try to find name of component object i := pos; while i > 0 and (l[3][i] in IdentifierLetters or l[3][i] in ".!") do i := i-1; od; cmps := SplitString(l[3]{[i+1..pos]},"","!."); if Length(cmps) > 0 and cmps[1] in idbnd then r := ValueGlobal(cmps[1]); if not (IsRecord(r) or IsComponentObjectRep(r)) then r := fail; else for j in [2..Length(cmps)] do if IsBound(r!.(cmps[j])) then r := r!.(cmps[j]); if IsRecord(r) or IsComponentObjectRep(r) then continue; fi; fi; r := fail; break; od; fi; else r := fail; fi; if r <> fail then cf.tabrec := r; fi; fi; fi; # now produce the searchlist if IsBound(cf.tabrec) then # the first two <TAB> hits try existing component names only first searchlist := ShallowCopy(NamesOfComponents(cf.tabrec)); if cf.tabcount > 2 then Append(searchlist, ALL_RNAMES()); fi; else # complete variable name searchlist := idbnd; fi; cand := Filtered(searchlist, a-> PositionSublist(a, word) = 1); # in component name search we try again with all names if this is empty if IsBound(cf.tabcompnam) and Length(cand) = 0 and cf.tabcount < 3 then searchlist := ALL_RNAMES(); cand := Filtered(searchlist, a-> PositionSublist(a, word) = 1); fi; if (not IsBound(cf.tabcompnam) and cf.tabcount = 2) or (IsBound(cf.tabcompnam) and cf.tabcount in [2,4]) then if Length(cand) > 0 then return GAPInfo.CommandLineEditFunctions.Functions.DisplayMatches( Set(cand)); else # ring the bell return GAPInfo.CommandLineEditFunctions.Functions.RingBell(); fi; fi; if Length(cand) = 0 then return []; elif Length(cand) = 1 then return [cand[1]{[Length(word)+1..Length(cand[1])]}]; fi; i := Length(word); while true do if i = Length(cand[1]) then break; fi; c := cand[1][i+1]; for j in [2..Length(cand)] do if Length(cand[j]) > i and cand[j][i+1] = c then j := j+1; else break; fi; od; if j <= Length(cand) then break; else i := i+1; fi; od; if i > Length(word) then return [cand[1]{[Length(word)+1..i]}]; else return []; fi; end; GAPInfo.CommandLineEditFunctions.Functions.(INT_CHAR('I') mod 32) := GAPInfo.CommandLineEditFunctions.Functions.Completion; BindKeysToGAPHandler("\011"); ############################################################################# ## ## Simple utilities to create an arbitrary number of macros ## # name a string, fun a function InstallReadlineMacro := function(name, fun) local cfm, pos; cfm := GAPInfo.CommandLineEditFunctions.Macros; if not IsBound(cfm.Names) then cfm.Names := []; fi; pos := Position(cfm.Names, name); if pos = fail then pos := Length(cfm.Names)+1; fi; cfm.(pos) := fun; cfm.Names[pos] := name; end; # A sequence to invoce macro name ('ESC num C-x C-g' sets GAPMacroNumber in # kernel and then any key that calls handled-by-GAP will do it) # We assume that 'C-xC-g' and <TAB> are not overwritten. InvocationReadlineMacro := function(name) local cfm, pos; cfm := GAPInfo.CommandLineEditFunctions.Macros; if not IsBound(cfm.Names) then cfm.Names := []; fi; pos := Position(cfm.Names, name); if pos = fail then return fail; fi; return Concatenation("\033", String(pos), "\030\007\t"); end; ## # Example ## gap> InstallReadlineMacro("My Macro", function(l) return ["my text"]; end); ## gap> InvocationReadlineMacro("My Macro"); ## "\0331\030\007\t" ## gap> BindKeySequence("^[OR",last); # first arg with C-v<F3> # for compatibility with the non-readline kernel code LineEditKeyHandlers := []; LineEditKeyHandler := function(l) return [l[1], l[3], l[5]]; end; else # some experimental code, use readline instead ReadLib("cmdleditx.g"); fi;