Hi Bernd! Am Donnerstag, den 08.12.2011, 20:44 +0100 schrieb Bernd: > 2011/5/1 Johann Glaser <johann.gla...@gmx.at>: > > > If you find any improvements or comments don't hesitate to send me an > > EMail. > > I just used it. I only needed readline() and add_history(), these two > functions are already enough to make a ReadLn() substitute that really > works and It works like a charm! Thank you! > > I think this code really deserves to be included in FPC.
Thanks for your feedback and support! In the meantime I've added an OOP wrapper which greatly simplifies the usage of the history and readline and helps to implement custom completion. Please find attached the OOP wrappers and a small example. I've also used this OOP wrapper for a command line tool which integrates the TCL OOP wrapper (mentioned in this list on 2011-05-05). The completion correctly identifies whether it is asked for commands or variables, and even works inside of the TCL equivalent of backticks. Due to shortage of time it's still a bit messy, but will be released under the terms of the GPL too. Bye Hansi
(* Copyright (C) 1989-2011 Free Software Foundation, Inc. This file contains the FreePascal header translations for the GNU History Library (History), a set of routines for managing the text of previously typed lines. History is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. History is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with History. If not, see <http://www.gnu.org/licenses/>. *) Unit HistoryOOP; {$mode objfpc}{$H+} Interface Uses Classes, SysUtils, History; Type { THistory } THistory = class private FFilename : String; FAddDuplicates : Boolean; public Constructor Create(AFilename:String); Destructor Destroy; override; Procedure Read; Procedure Write; Procedure Print; Function List : PPHIST_ENTRY; inline; Function Length : Integer; inline; Function GetLast : PHIST_ENTRY; inline; Procedure Add(St:String); property AddDuplicates : Boolean read FAddDuplicates write FAddDuplicates; End; Implementation // signelton static variable Const HistoryInst : THistory = Nil; { THistory } (** * Constructor * * AFilename: path to history file, if its empty, no history file is used, if * it doesn't contain a '/', it is interpreted relative to the user's home * directory. *) Constructor THistory.Create(AFilename:String); Begin { History is not thread-save } if assigned(HistoryInst) then raise Exception.Create('You can only have a single THistory instance!'); HistoryInst := Self; // set singleton static variable inherited Create; if AFilename <> '' then Begin if Pos('/',AFilename) > 0 then Begin // filename contains a '/' -> absolute path FFilename := AFilename; End else Begin // filename doesn't contain a '/' -> relative to home directory FFilename := GetEnvironmentVariable('HOME') + '/' + AFilename; End; End; End; Destructor THistory.Destroy; Begin { clean up singleton static variable } if HistoryInst <> Self then raise Exception.Create('Internal Error: HistoryInst <> Self'); HistoryInst := Nil; Inherited Destroy; End; Procedure THistory.Read; Begin if FFilename = '' then Exit; read_history(PChar(FFilename)); End; Procedure THistory.Write; Begin if FFilename = '' then Exit; write_history(PChar(FFilename)); End; (** * Print the full history *) Procedure THistory.Print; Var H : PPHIST_ENTRY; I : Integer; W : Integer; Begin // get the required width for the enumeration W := 0; I := Length; While I > 0 do Begin Inc(W); I := I div 10; End; // print the whole history list H := List; if H = Nil then Exit; I := 0; While H^[I] <> Nil do Begin WriteLn(' ',(I+1):W,' ',H^[I]^.Line); Inc(I); End; End; Function THistory.List:PPHIST_ENTRY; inline; Begin Result := history_list; End; Function THistory.Length:Integer; inline; Begin Result := history_length; End; Function THistory.GetLast:PHIST_ENTRY;Inline; Begin Result := history_get(Length); End; Procedure THistory.Add(St:String); Var H : PHIST_ENTRY; Begin if not FAddDuplicates then Begin H := GetLast; if (H <> Nil) and (St = H^.Line) then Exit; End; add_history(PChar(St)); End; End.
(* Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of the FreePascal header translations for the GNU Readline Library (Readline), a library for reading lines of text with interactive input and history editing. Readline is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Readline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Readline. If not, see <http://www.gnu.org/licenses/>. *) (* http://tiswww.case.edu/php/chet/readline/rltop.html * http://tiswww.case.edu/php/chet/readline/readline.html * http://tiswww.case.edu/php/chet/readline/history.html *) Unit ReadlineOOP; {$mode objfpc}{$H+} Interface Uses Classes, SysUtils, Readline, HistoryOOP; Type TDynArrString = Array of String; TGetCompletionsFunc = Function(Text:PChar;Start,TheEnd:Integer) : TDynArrString of object; { TReadline } TReadline = class private FName : String; // for conditions in the ~/.inputrc file FPrompt : String; FQuitCmd : String; FHistory : THistory; FSigIntHandler : Boolean; // if true a ^C signal handler is set before readline() is called FSigIntConvert : Boolean; // raise EReadlineSigInt exception in signal handler to catch by higher-level FGetCompletions : TGetCompletionsFunc; Function GetBasicWordBreakCharacters:String; Procedure SetBasicWordBreakCharacters(Const AValue: String); Function GetSpecialPrefixes:String; Procedure SetSpecialPrefixes(Const AValue: String); Function GetLineBuffer:PChar; // TODO: rl_instream, rl_outstream public Constructor Create(AName:String); Destructor Destroy; override; { the main function } Function Read : String; { helper functions } Procedure Show(St:String); Procedure CRLF; inline; { completion helper functions } Procedure UseFilenames; inline; { properties } property QuitCmd : String read FQuitCmd write FQuitCmd; property Prompt : String read FPrompt write FPrompt; property History : THistory read FHistory write FHistory; property SigIntHandler : Boolean read FSigIntHandler write FSigIntHandler; property SigIntConvert : Boolean read FSigIntConvert write FSigIntConvert; property GetCompletions : TGetCompletionsFunc read FGetCompletions write FGetCompletions; { Readline variables } property BasicWordBreakCharacters : String read GetBasicWordBreakCharacters write SetBasicWordBreakCharacters; property SpecialPrefixes : String read GetSpecialPrefixes write SetSpecialPrefixes; property LineBuffer : PChar read GetLineBuffer; End; EReadline = class(Exception); // errors encountered in TReadline EReadlineSigInt = class(EReadline); // special class raised in the SIGINT handler Implementation Uses BaseUnix; // signelton static variable Const ReadlineInst : TReadline = Nil; (* * rl_completion_matches() is very simple: It calls the entry_function * repeatedly until it returns Nil and places its return value into a growing * array starting at [1] (growing by 10 to avoid too frequency realloc). Then * it calls the compute_lcd_of_matches() to sort the entries and compute the * least common deniminator of all possible completions. Unfortunately this * function is a private, therefore we can't use it to generate our own LCD. * * Plan: We set AttemptCompletion as rl_attempted_completion_function. This * calls a user-defined function of object, which returns an Array of String * (trust in reference counting!). This is then stored to the global variable * GlobalCompletions. Then it calls rl_completion_matches() for which we * provide the callback function GetGlobalCompletions. This simply returns one * entry from GlobalCompletions after each other. * * AttemptCompletion and therefore the user-defined function get a lot more * information as GetGlobalCompletions, so it is more powerful to choose smart * completions. *) // global variables to store completions Const GlobalCompletions : TDynArrString = Nil; GlobalCompletionsIdx : Integer = 0; // rl_completion_matches() wants the strings to be malloc()ed and will be freed // later, therefore we link to the libc strdup() (which is basically a wrapper // for malloc()). Function StrDup(para1:Pchar):Pchar;cdecl;external 'c' name 'strdup'; Function GetGlobalCompletions(TheText : PChar; Matches : CInt) : PChar; CDecl; Begin // first run: reset the index if Matches = 0 then GlobalCompletionsIdx := 0; // no more completions? if GlobalCompletionsIdx >= Length(GlobalCompletions) then Exit(Nil); // return the string (which must be malloc()ed!) Result := StrDup(PChar(GlobalCompletions[GlobalCompletionsIdx])); Inc(GlobalCompletionsIdx); End; Function AttemptedCompletion(Text:PChar;Start,TheEnd:Integer): PPChar; CDecl; Begin Result := Nil; // by default we don't want to complete filenames if we don't have completions rl_attempted_completion_over := 1; // don't return any completions if no callback is defined if not assigned(ReadlineInst.GetCompletions) then Exit; // get all completions GlobalCompletions := ReadlineInst.GetCompletions(Text,Start,TheEnd); // make an appropriate array Result := rl_completion_matches(Text,@GetGlobalCompletions); End; { TReadline } Constructor TReadline.Create(AName:String); Begin { readline is not thread-save } if assigned(ReadlineInst) then raise EReadline.Create('You can only have a single TReadline instance!'); ReadlineInst := Self; // set singleton static variable inherited Create; // set defaults FName := AName; FPrompt := '> '; FQuitCmd := 'quit'; FSigIntHandler := true; // setup readline rl_readline_name := PChar(FName); rl_attempted_completion_function := @AttemptedCompletion; End; Destructor TReadline.Destroy; Begin { clean up singleton static variable } if ReadlineInst <> Self then raise EReadline.Create('Internal Error: ReadlineInst <> Self'); ReadlineInst := Nil; Inherited Destroy; End; Procedure SigCtrlC(Signal:Longint;Info:PSigInfo;Context:PSigContext); CDecl; Begin rl_crlf(); // readline only prints '^C' -> print newline // rl_done() doesn't work as I'd like: immedately return from readline(). // Instead the user has to push at least one key to return. // // But how is bash doing this? They install a setjmp() position before the // readline() and then do a longjmp() from within the signal handler. // // We model this with exceptions and create the dedicated EReadlineSigInt for // it. // // There is another problem: Since this signal handler function is not // finished the sigreturn() call (injected by the kernel) is not executed. // Somehow the SIGINT stays blocked and no further ^C presses work. To work // around this problem, we clear the set of blocked signals directly before // readline() (see below). // "convert" signal to exception if ReadlineInst.FSigIntConvert then raise EReadlineSigInt.Create('SIGINT') else Begin // readline() continues to run, so clean information and redraw the screen rl_free_line_state(); // free partial history entry, keyboard macro, numeric argument rl_replace_line('', 1); // clear line buffer rl_forced_update_display(); // force the line to be updated and redisplayed End; End; Function TReadline.Read:String; Var InputLine : PChar; NewAct,OldAct : SigActionRec; NewSig,OldSig : TSigSet; Begin // install signal handler to ignore ^C // Readline by default catches many signals (including SIGINT), does some // terminal cleanup, and resend the signal to the calling application. So we // have to catch it ourselves. if FSigIntHandler then Begin NewAct.sa_handler := @SigCtrlC; NewAct.sa_flags := SA_RESTART or SA_SIGINFO; // SA_SIGINFO required that sa_sigaction(int, siginfo_t*, void*) instead of sa_handler(int) is called FpSigEmptySet(NewAct.sa_mask); if FpSigAction(SIGINT,@NewAct,@OldAct) <> 0 then raise EReadline.CreateFmt('Error installing SIGINT handler: %d (%s)',[FpGetErrno, '']); // un-block all signals FpSigEmptySet(NewSig); if FpSigProcMask(SIG_SETMASK,NewSig,OldSig) <> 0 then raise EReadline.CreateFmt('Error unblocking all signals: %d (%s)',[FpGetErrno, '']); End; // read a line InputLine := Readline.readline(PChar(FPrompt)); // remove signal handler if FSigIntHandler then Begin if FpSigAction(SIGINT,@OldAct,Nil) <> 0 then raise EReadline.CreateFmt('Error installing old SIGINT handler: %d (%s)',[FpGetErrno, '']); End; // handle ^D if InputLine = Nil then Begin Show(FQuitCmd); CRLF; // print a newline because after pressing ^D there is none InputLine := PChar(FQuitCmd); End; Result := Trim(InputLine); // if the string was non-empty process it if Result > '' then Begin // Add line to history (only if its not a duplicate) if assigned(FHistory) then FHistory.Add(Result); End; End; Procedure TReadline.Show(St:String); Var I : Integer; Begin For I := 1 to Length(St) do rl_show_char(Ord(St[I])); End; Procedure TReadline.CRLF;Inline; Begin rl_crlf; End; Procedure TReadline.UseFilenames; inline; Begin // Called from the user-defined function to return a list of completions. // Re-enable filename completions. rl_attempted_completion_over := 0; End; Function TReadline.GetBasicWordBreakCharacters:String; Begin Result := rl_basic_word_break_characters; End; Procedure TReadline.SetBasicWordBreakCharacters(Const AValue: String); Begin rl_basic_word_break_characters := PChar(AValue); End; Function TReadline.GetSpecialPrefixes:String; Begin Result := rl_special_prefixes; End; Procedure TReadline.SetSpecialPrefixes(Const AValue: String); Begin rl_special_prefixes := PChar(AValue); End; Function TReadline.GetLineBuffer:PChar; Begin Result := rl_line_buffer; End; End.
Program OOTest; {$mode objfpc}{$H+} Uses Classes, SysUtils, HistoryOOP, ReadlineOOP; Type { TCompleter } TCompleter = class private FReadline : TReadline; public Constructor Create(AReadline:TReadline); Function GetCompletions(Text:PChar;Start,TheEnd:Integer) : TDynArrString; Function GetCommands (Text:PChar;Start,TheEnd:Integer) : TDynArrString; End; { TCompleter } Constructor TCompleter.Create(AReadline:TReadline); Begin inherited Create; FReadline := AReadline; End; Function TCompleter.GetCompletions(Text:PChar;Start,TheEnd:Integer):TDynArrString; Begin Result := Nil; { context dependent completion } if Start = 0 then Begin { first word -> command } Result := GetCommands(Text,Start,TheEnd); End else Begin { 2nd or later word -> filename } FReadline.UseFilenames; // re-enable filename completions End; End; Function TCompleter.GetCommands(Text:PChar;Start,TheEnd:Integer):TDynArrString; Const Commands : Array[0..6] of String = ('list','history','quit','help','linux','open','ootest-1'); Var I : Integer; N : Integer; Begin SetLength(Result,Length(Commands)); N := 0; // WriteLn; // WriteLn('Text = ''',Text,''', Start = ',Start,', End = ',TheEnd); For I := Low(Commands) to High(Commands) do if Text = Copy(Commands[I],1,TheEnd-Start) then Begin // WriteLn('Possible match: ',Commands[I]); Result[N] := Commands[I]; Inc(N); End; // set correct length of array SetLength(Result,N); End; Var H : THistory; R : TReadline; St : String; C : TCompleter; Begin H := THistory.Create('histtest.delme'); H.Read; // read history R := TReadline.Create('OOTest'); R.History := H; C := TCompleter.Create(R); R.GetCompletions := @C.GetCompletions; // command line repeat St := R.Read; Until St = 'quit'; C.Free; H.Print; // print history to screen H.Write; // write history to file H.Free; R.Free; End.
_______________________________________________ fpc-pascal maillist - fpc-pascal@lists.freepascal.org http://lists.freepascal.org/mailman/listinfo/fpc-pascal