./libtecla/0040755000076400007640000000000010142301753011130 5ustar mcsmcs./libtecla/html/0040755000076400007640000000000010141253664012102 5ustar mcsmcs./libtecla/html/cpl_complete_word.html0100644000076400007640000004606010141252545016471 0ustar mcsmcs Manual Page
cpl_complete_word              cpl_complete_word



NAME

       cpl_complete_word,         cfc_file_start,         cfc_literal_escapes,
       cfc_set_check_fn,       cpl_add_completion,       cpl_file_completions,
       cpl_last_error,        cpl_list_completions,        cpl_recall_matches,
       cpl_record_error, del_CplFileConf, del_WordCompletion, new_CplFileConf,
       new_WordCompletion - lookup possible completions for a word

SYNOPSIS

       #include <stdio.h>
       #include <libtecla.h>

       WordCompletion *new_WordCompletion(void);

       WordCompletion *del_WordCompletion(WordCompletion *cpl);


       #define CPL_MATCH_FN(fn) int (fn)(WordCompletion *cpl, \
                                         void *data, \
                                         const char *line, \
                                         int word_end)
       typedef CPL_MATCH_FN(CplMatchFn);

       CPL_MATCH_FN(cpl_file_completions);


       CplMatches *cpl_complete_word(WordCompletion *cpl,
                                     const char *line,
                                     int word_end, void *data,
                                     CplMatchFn *match_fn);

       CplMatches *cpl_recall_matches(WordCompletion *cpl);

       int cpl_list_completions(CplMatches *result, FILE *fp,
                                int term_width);

       int cpl_add_completion(WordCompletion *cpl,
                              const char *line, int word_start,
                              int word_end, const char *suffix,
                              const char *type_suffix,
                              const char *cont_suffix);

       void cpl_record_error(WordCompletion *cpl,
                             const char *errmsg);

       const char *cpl_last_error(WordCompletion *cpl);


       #define CPL_CHECK_FN(fn) int (fn)(void *data, \
                                         const char *pathname)

       typedef CPL_CHECK_FN(CplCheckFn);

       CPL_CHECK_FN(cpl_check_exe);

       CplFileConf *new_CplFileConf(void);

       CplFileConf *del_CplFileConf(CplFileConf *cfc);

       void cfc_literal_escapes(CplFileConf *cfc, int literal);

       void cfc_file_start(CplFileConf *cfc, int start_index);

       void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn,
                             void *chk_data);



DESCRIPTION

       The  cpl_complete_word() function is part of the tecla library (see the
       libtecla man page). It  is  usually  called  behind  the
       scenes  by  gl_get_line,  but  can  also be called sepa-
       rately.

       Given an input line containing an incomplete word to be  completed,  it
       calls  a  user-provided callback function (or the provided file-comple-
       tion callback function) to look up all possible completion suffixes for
       that  word.  The  callback function is expected to look backward in the
       line, starting from the specified cursor position, to find the start of
       the  word  to be completed, then to look up all possible completions of
       that word and record them, one at a  time  by  calling  cpl_add_comple-
       tion().


       Descriptions of the functions of this module are as follows:

         WordCompletion *new_WordCompletion(void)

       This  function  creates  the  resources used by the cpl_complete_word()
       function. In particular, it maintains the memory that is used to return
       the results of calling cpl_complete_word().

         WordCompletion *del_WordCompletion(WordCompletion *cpl)

       This  function  deletes  the resources that were returned by a previous
       call to new_WordCompletion(). It always returns  NULL  (ie.  a  deleted
       object). It does nothing if the cpl argument is NULL.

       The  callback  functions  which  lookup  possible completions should be
       defined with the following macro (which is defined in libtecla.h).

         #define CPL_MATCH_FN(fn) int (fn)(WordCompletion *cpl, \
                                           void *data, \
                                           const char *line, \
                                           int word_end)

       Functions of this type are called by cpl_complete_word(),  and  all  of
       the  arguments of the callback are those that were passed to said func-
       tion. In particular, the line argument contains the input line contain-
       ing  the word to be completed, and word_end is the index of the charac-
       ter that follows the last character of the incomplete word within  this
       string.  The  callback  is expected to look backwards from word_end for
       the start of the incomplete word. What constitutes the start of a  word
       clearly  depends on the application, so it makes sense for the callback
       to take on this responsibility. For example, the builtin filename  com-
       pletion  function  looks backwards until it hits an unescaped space, or
       the start of the line.  Having found the start of the word,  the  call-
       back  should  then  lookup  all  possible completions of this word, and
       record each completion via separate calls to  cpl_add_completion().  If
       the  callback  needs access to an application-specific symbol table, it
       can pass it and any other data that it needs, via  the  data  argument.
       This removes any need for globals.

       The callback function should return 0 if no errors occur. On failure it
       should return 1, and register a terse description of the error by call-
       ing cpl_record_error().

         void cpl_record_error(WordCompletion *cpl,
                               const char *errmsg);

       The last error message recorded by calling cpl_record_error(), can sub-
       sequently be queried by calling cpl_last_error(), as described later.

         int cpl_add_completion(WordCompletion *cpl,
                                const char *line, int word_start,
                                int word_end, const char *suffix,
                                const char *type_suffix,
                                const char *cont_suffix);

       The cpl_add_completion() function is called zero or more times  by  the
       completion  callback function to record each possible completion in the
       specified WordCompletion object.  These  completions  are  subsequently
       returned by cpl_complete_word(), as described later. The cpl, line, and
       word_end arguments should be those that were  passed  to  the  callback
       function.  The word_start argument should be the index within the input
       line string of the start of the word  that  is  being  completed.  This
       should  equal  word_end if a zero-length string is being completed. The
       suffix argument is the string that would have to  be  appended  to  the
       incomplete  word  to  complete  it.  If this needs any quoting (eg. the
       addition of backslashes before special charaters) to  be  valid  within
       the displayed input line, this should be included. A copy of the suffix
       string is allocated internally, so there is no need  to  maintain  your
       copy of the string after cpl_add_completion() returns.

       Note  that  in  the  array  of  possible completions which the cpl_com-
       plete_word() function returns, the suffix recorded  by  cpl_add_comple-
       tion()  is listed along with the concatentation of this suffix with the
       word that lies between word_start and word_end in the input line.

       The type_suffix argument specifies an optional string to be appended to
       the  completion  if it is displayed as part of a list of completions by
       cpl_list_completions(). The intention is that this indicate to the user
       the  type of each completion. For example, the file completion function
       places a directory separator after completions that are directories, to
       indicate  their  nature to the user. Similary, if the completion were a
       function, you could indicate this to the user by setting type_suffix to
       "()". Note that the type_suffix string isn't copied, so if the argument
       isn't a literal string between speech marks, be sure  that  the  string
       remains  valid  for  at  least  as  long  as  the  results  of cpl_com-
       plete_word() are needed.

       The cont_suffix is a continuation suffix to  append  to  the  completed
       word  in  the  input line if this is the only completion. This is some-
       thing that isn't part of the completion itself, but that gives the user
       an  indication  about how they might continue to extend the token.  For
       example, the file-completion callback function adds a directory separa-
       tor  if the completed word is a directory. If the completed word were a
       function name, you could similarly aid the user  by  arranging  for  an
       open parenthesis to be appended.

         CplMatches *cpl_complete_word(WordCompletion *cpl,
                                       const char *line,
                                       int word_end, void *data,
                                       CplMatchFn *match_fn);

       The  cpl_complete_word()  is  normally  called  behind  the  scenes  by
       gl_get_line, but can also be called  separately  if  you
       separately  allocate  a WordCompletion object. It performs word comple-
       tion, as described at the beginning of this section. Its first argument
       is  a resource object previously returned by new_WordCompletion().  The
       line argument is the input line string, containing the word to be  com-
       pleted.  The  word_end  argument contains the index of the character in
       the input line, that just follows the last character of the word to  be
       completed.  When  called  by  gl_get_line(), this is the character over
       which the user pressed TAB.  The  match_fn  argument  is  the  function
       pointer of the callback function which will lookup possible completions
       of the word, as described above, and the data argument provides  a  way
       for the application to pass arbitrary data to the callback function.

       If  no errors occur, the cpl_complete_word() function returns a pointer
       to a CplMatches container, as defined below. This  container  is  allo-
       cated as part of the cpl object that was passed to cpl_complete_word(),
       and will thus change on each call which uses the same cpl argument.

         typedef struct {
           char *completion;        /* A matching completion */
                                    /*  string */
           char *suffix;            /* The part of the */
                                    /*  completion string which */
                                    /*  would have to be */
                                    /*  appended to complete the */
                                    /*  original word. */
           const char *type_suffix; /* A suffix to be added when */
                                    /*  listing completions, to */
                                    /*  indicate the type of the */
                                    /*  completion. */
         } CplMatch;

         typedef struct {
           char *suffix;            /* The common initial part */
                                    /*  of all of the completion */
                                    /*  suffixes. */
           const char *cont_suffix; /* Optional continuation */
                                    /*  string to be appended to */
                                    /*  the sole completion when */
                                    /*  nmatch==1. */
           CplMatch *matches;       /* The array of possible */
                                    /*  completion strings, */
                                    /*  sorted into lexical */
                                    /*  order. */
           int nmatch;              /* The number of elements in */
                                    /*  the above matches[] */
                                    /*  array. */
         } CplMatches;

       If an error occurs during completion, cpl_complete_word() returns NULL.
       A   description   of   the   error  can  be  acquired  by  calling  the
       cpl_last_error() function.

         const char *cpl_last_error(WordCompletion *cpl);

       The cpl_last_error() function returns a terse description of the  error
       which  occurred on the last call to cpl_complete_word() or cpl_add_com-
       pletion().

         CplMatches *cpl_recall_matches(WordCompletion *cpl);

       As a convenience, the  return  value  of  the  last  call  to  cpl_com-
       plete_word()   can   be   recalled   at   a   later   time  by  calling
       cpl_recall_matches(). If cpl_complete_word()  returned  NULL,  so  will
       cpl_recall_matches().

         int cpl_list_completions(CplMatches *result, FILE *fp,
                                  int terminal_width);

       When the cpl_complete_word() function returns multiple possible comple-
       tions, the cpl_list_completions() function can be called upon  to  list
       them,  suitably arranged across the available width of the terminal. It
       arranges for the displayed columns of completions to all have the  same
       width,  set  by the longest completion. It also appends the type_suffix
       strings that were recorded with each completion, thus indicating  their
       types to the user.


THE BUILT-IN FILENAME-COMPLETION CALLBACK

       By  default the gl_get_line function, passes the follow-
       ing completion callback function to cpl_complete_word(). This  function
       can  also  be  used  separately,  either  by  sending  it  to  cpl_com-
       plete_word(), or by calling it directly from your own completion  call-
       back function.

         CPL_MATCH_FN(cpl_file_completions);

       Certain aspects of the behavior of this callback can be changed via its
       data argument. If you are happy with its default behavior you can  pass
       NULL  in  this argument. Otherwise it should be a pointer to a CplFile-
       Conf object, previously allocated by calling new_CplFileConf().

         CplFileConf *new_CplFileConf(void);

       CplFileConf  objects  encapsulate  the  configuration   parameters   of
       cpl_file_completions().  These parameters, which start out with default
       values, can be changed by  calling  the  accessor  functions  described
       below.

       By default, the cpl_file_completions() callback function searches back-
       wards for the start of the filename being completed,  looking  for  the
       first  un-escaped  space or the start of the input line. If you wish to
       specify a different location, call cfc_file_start() with the  index  at
       which the filename starts in the input line. Passing start_index=-1 re-
       enables the default behavior.

         void cfc_file_start(CplFileConf *cfc, int start_index);

       By default, when cpl_file_completions() looks  at  a  filename  in  the
       input  line,  each  lone  backslash in the input line is interpreted as
       being a special character which removes any special significance of the
       character  which  follows  it, such as a space which should be taken as
       part of the filename rather than delimiting the start of the  filename.
       These  backslashes  are thus ignored while looking for completions, and
       subsequently added before spaces, tabs and literal backslashes  in  the
       list  of  completions.  To have unescaped backslashes treated as normal
       characters, call cfc_literal_escapes() with a  non-zero  value  in  its
       literal argument.

         void cfc_literal_escapes(CplFileConf *cfc, int literal);

       By  default, cpl_file_completions() reports all files who's names start
       with the prefix that is being completed. If you only  want  a  selected
       subset  of  these  files to be reported in the list of completions, you
       can arrange this by providing a callback function which takes the  full
       pathname  of  a file, and returns 0 if the file should be ignored, or 1
       if the file should be included in the list of completions. To  register
       such    a    function   for   use   by   cpl_file_completions(),   call
       cfc_set_check_fn(), and pass it a pointer  to  the  function,  together
       with  a pointer to any data that you would like passed to this callback
       whenever it is called. Your callback can make its  decisions  based  on
       any property of the file, such as the filename itself, whether the file
       is readable, writable or executable, or even based  on  what  the  file
       contains.

         #define CPL_CHECK_FN(fn) int (fn)(void *data, \
                                           const char *pathname)
         typedef CPL_CHECK_FN(CplCheckFn);

         void cfc_set_check_fn(CplFileConf *cfc,
                               CplCheckFn *chk_fn, void *chk_data);

       The  cpl_check_exe() function is a provided callback of the above type,
       for use with cpl_file_completions(). It returns non-zero if  the  file-
       name  that  it is given represents a normal file that the user has exe-
       cute permission to. You could use this to  have  cpl_file_completions()
       only list completions of executable files.

       When  you have finished with a CplFileConf variable, you can pass it to
       the del_CplFileConf() destructor function to reclaim its memory.

         CplFileConf *del_CplFileConf(CplFileConf *cfc);



THREAD SAFETY

       In multi-threaded programs, you should use the libtecla_r.a version  of
       the library. This uses POSIX reentrant functions where available (hence
       the _r suffix), and disables features that rely on non-reentrant system
       functions.  In  the  case  of this module, the only disabled feature is
       username completion  in  ~username/  expressions,  in  cpl_file_comple-
       tions().

       Using  the  libtecla_r.a  version of the library, it is safe to use the
       facilities of this module  in  multiple  threads,  provided  that  each
       thread  uses  a  separately  allocated  WordCompletion object. In other
       words, if two threads want to do word completion, they should each call
       new_WordCompletion() to allocate their own completion objects.


FILES

       libtecla.a    -    The tecla library
       libtecla.h    -    The tecla header file.


SEE ALSO

       libtecla, gl_get_line, ef_expand_file,
       pca_lookup_file


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                              cpl_complete_word
./libtecla/html/changes.html0100644000076400007640000042462110141252550014377 0ustar mcsmcsThe tecla library change log
In the following log, modification dates are listed using the European
convention in which the day comes before the month (ie. DD/MM/YYYY).
The most recent modifications are listed first.

31/10/2004 mcs@astro.caltech.edu (problem reported by Godfrey van der Linden) 
           getline.c
             The gl_event_handler() function had the endif of a
             conditional compilation clause in the wrong place. This
             only upset the compiler on unusual systems that don't
             have select(). The problem was seen under Mac OS X, due
             to the configuration problem in 1.6.0 that caused the
             configure script to mistakenly report that select wasn't
             available.

31/10/2004 mcs@astro.caltech.edu (info provided by Ivan Rayner)
           configure.in configure Makefile.in
             Ivan reported that under IRIX 6.5 it is necessary to add
             -D_XOPEN_SOURCE=500 to the compiler flags, when compiling
             the reentrant version of the library. Thus, whereas
             previously I hardwired the value of DEFINES_R in
             Makefile.in, I have now made this a variable in the
             configure script, which is augmented with the above
             addition, within an IRIX-specific switch clause.

             Also apparently configure leaves the RANLIB variable
             blank, instead of setting it to ":", so I have now
             explicitly set this to ":", within the new IRIX clause of
             the configure script.

31/10/2004 mcs@astro.caltech.edu (info provided by Ivan Rayner)
           getline.c
             Under IRIX, the compiler warned that gl_read_unmasked()
             was returning an int, which was then being assigned to an
             enumeration type. This is techically fine, but it
             highlighted the fact that I had meant to declare
             gl_read_unmasked() to directly return the enumerated
             type. I have now done so.

26/09/2004 mcs@astro.caltech.edu
           getline.c
             Users can now turn off interactive command-line editing
             by setting the TERM environment variable to the word "dumb".

18/07/2004 mcs@astro.caltech.edu (problem noted by Michael MacFaden)
           getline.c
             Calling gl_terminal_size() on a system without support
             for SIGWINCH caused a divide-by-zero error in an unintended
             call to gl_erase_line(), because gl_update_size() was
             incorrectly being called to query the terminal size,
             instead of gl_query_size().

18/07/2004 Padraig Brady  (documented here by mcs@astro.caltech.edu)
           getline.c
             The suspend and termination signal-handlers installed by
             gl_tty_signals(), were being installed swapped.

03/06/2004 Mike Meaney  (documented here by mcs@astro.caltech.edu)
           getline.c
             Mike pointed out the fact that the curses setupterm()
             function is actually documented to exit the application
             if an error occurs while its optional errret argument is
             NULL. I hadn't noticed this, and because I didn't need
             the extra information returned in the errret argument, I
             was passing it a NULL. As suggested by Mike, I now pass
             this argument a pointer to a dummy errret variable.

23/05/2004 mcs@astro.caltech.edu (problem noted by John Beck)
           man/func/cpl_complete_word.in
             Some of the prototypes of functions and types documented
             by the cpl_complete_word man page, weren't listed in the
             Synopsis section of this man page. They are now listed
             there.
            
23/05/2004 mcs@astro.caltech.edu
           getline.c man/func/gl_get_line.in
             I have now added support for calling gl_normal_io() from
             any callback functions that the application installs by
             calling either gl_inactivity_timeout(), or gl_watch_fd().
             Previously, if one of these callback functions called
             gl_normal_io(), then after returning to gl_get_line(),
             gl_get_line() would incorrectly assume that the terminal
             was still in raw I/O mode. Now, gl_get_line() checks to
             see if gl_normal_io() was called by the callback, and
             if so, calls _gl_raw_io() to reinstate raw I/O mode.

21/05/2004 mcs@astro.caltech.edu
           configure.in configure
             On Mac OS X the code that the configure script used to
             check for select() failed due to missing symbols in
             sys/select.h. Moving the inclusion of sys/select.h to
             after the inclusion of sys/time.h, sys/types.h and
             sys/unistd.h fixed this.

11/05/2004 mcs@astro.caltech.edu
           getline.c man/func/gl_get_line.in
             If the line buffer returned by one call to gl_get_line()
             was passed as the start_line argument of the next call to
             gl_get_line(), then instead of the just-entered line
             being presented back to the user for further editing, the
             start_line argument was effectively ignored, because the
             line buffer whose pointer was being passed back, was
             being cleared before the start_line pointer was examined.
             This appears to have been a case of me incorrectly
             thinking that I had forgotten to initialize gl->line[]
             and gl->ntotal in the gl_reset_input_line() function, and
             then "fixing" this supposed omission. Removing this
             erroneous fix, restored things to how they were meant to
             be. To make it unlikely that I will make the same mistake
             again, I have renamed the function from
             gl_reset_input_line() to gl_reset_editor(), to stop it
             looking as though it is meant to reset the contents of
             the input line (that is what gl_truncate_buffer() is
             for), explicitly stated that it doesn't clear the input
             line, in the header comments of the function, and added a
             prominent warning comment in the body of the function.

             Also, since support for passing back the returned line
             pointer via the start_line argument of the next call to
             gl_get_line(), wasn't documented in the man page, but was
             meant to be supported, and definitely used to work, I
             have now amended the man page documentation of
             gl_get_line() to explicitly state that this feature is
             officially supported.

2?/04/2004 Released 1.6.0

22/04/2004 mcs@astro.caltech.edu  (Fixed a bug reported by John Beck)
           getline.c
             When an error, signal, or other abnormal event aborted
             gl_get_line(), the cleanup code that restored the
             terminal to a sane state, also overwrote the value of
             errno that was associated with the aborting event. An
             I/O error occurring in the cleanup code would have also
             overwritten the value to be returned by
             gl_return_status(), and thus remove any possibility of
             the caller finding out what really caused gl_get_line()
             to abort. I have now written a new internal function
             called, gl_record_status(), which records the completion
             status to be returned by gl_return_status(), and the
             value to assign to errno just before gl_get_line()
             returns. This is called wherever code detects conditions
             that require gl_get_line() to return early. The function
             ensures that once an abnormal completion status has been
             recorded for return, subsequent completions statuses
             aren't recorded. This ensures that the caller sees the
             original cause of the abnormal return, rather than any
             error that occurs during cleaning up from this before
             return.

17/04/2004 mcs@astro.caltech.edu
           getline.c
             If an application's callback called gl_read_char() after
             calling gl_normal_io(), it would inappropriately
             redisplay the input line, when it called _gl_raw_io() to
             temporarily switch the terminal back into raw mode.

             To fix this, _gl_raw_io() now takes a new 'redisplay'
             argument, which specifies whether or not to queue a
             redisplay of the input line. I also created a new
             gl->postpone flag, which is set by gl_normal_io(), and
             cleared by _gl_raw_io() (when its redisplay argument is
             true). When this flag is set, gl_flush_output() ignores
             queued redisplays, as it generally should between calls
             to gl_normal_io() and gl_raw_io(). Thus its effect is to
             postpone redisplays while line editing is suspended.

11/04/2004 mcs@astro.caltech.edu
           history.c man/misc/tecla.in
             History searches can now include the globbing operators
             *, ?, []. When a search prefix is found to have at least
             one of these characters, then only history lines that
             completely match that pattern are returned.

11/04/2004 mcs@astro.caltech.edu  (issue raised by Mark Coiley)
           getline.c ioutil.c
             There appears to be a bug in Solaris's terminal I/O.
             When the terminal file descriptor is placed in
             non-blocking I/O mode, and the terminal is switched from
             canonical to raw mode, characters that were previously
             entered in canonical I/O mode don't become available to
             be read until the user types one character more. Select()
             incorrectly says that there are no characters available,
             and read() returns EAGAIN. This is only a problem for
             gl_get_line() when gl_get_line() is in non-blocking
             server I/O mode, so most users won't have experienced any
             problems with this.

             The only way that I have found to get read() to return
             the characters, without the user first having to type
             another character, is to turn off non-blocking I/O before
             calling read(). Select() still claims that there are no
             characters available to be read, but read happily returns
             them anyway. Fortunately, one can perform non-blocking
             terminal reads without setting the non-blocking I/O flag
             of the file descriptor, simply by setting the VTIME
             terminal attribute to zero (which I already was
             doing). Thus, when in non-blocking server I/O, I now turn
             off the non-blocking I/O flag, attempt to read one
             character and only if this fails, do I then call the
             select() based event handler to implement any configured
             non-zero timeout, before attempting the read again. Of
             course the non-blocking I/O flag is still needed for
             writing, so I only turn it off temporarily while reading.

25/03/2004 mcs@astro.caltech.edu  (bug reported by Gregory Harris)
           Makefile.in
             It appears that when in February, I patched Makefile.in
             to add abolute paths to the install-sh shell-script,
             I accidentally replaced install-sh with install.sh. I
             corrected the name in the Makefile.

25/03/2004 Gregory Harris  (documented here by mcs)
           configure.in configure
             Greg added the configuration parameters needed to build
             the shared version of the libtecla library under FreeBSD.

25/03/2004 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man/func/gl_get_line.in
           man/func/gl_read_char.in
             I wrote a public function called gl_read_char(). Unlike
             gl_query_char(), this function neither prompts the user
             for input, nor displays the character that was entered.
             In fact it doesn't write anything to the terminal, and
             takes pains not to disturb any incompletely entered
             input line, and can safely be called from application
             callback functions.

21/03/2004 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man/func/gl_get_line.in
           man/func/gl_query_char.in
             I wrote a public function called gl_query_char(), which
             prompts the user and awaits a single-character reply,
             without the user having to hit return.

23/02/2004 mcs@astro.caltech.edu (bug reported by Gregory Harris)
           configure.in configure getline.c enhance.c demo3.c
             The configure script now checks for the sys/select.h
             header file, and arranges for a C macro called
             HAVE_SYS_SELECT_H to be set if it exists. Thus the files
             that use select() now use this macro to conditionally
             include sys/select.h where available. Apparently this
             header is required under FreeBSD 5.1.

23/02/2004 mcs@astro.caltech.edu
           getline.c libtecla.h man/func/gl_get_line.in
             I wrote two new public functions, gl_append_history() and
             gl_automatic_history(). Together these allow the
             application to take over the responsibility of adding
             lines to the history list from gl_get_line(). I then
             documented their functionality in the gl_get_line man
             page.
           Version 1.6.0
             I incremented the minor version number of the library, to
             comply with the requirement to do so when additions are
             made to the public interface. See libtecla.map for
             details.
           libtecla.map
             I added a new 1.6.0 group for the new minor version, and
             added the above pair of functions to it.

15/02/2004 mcs@astro.caltech.edu (fixes a bug reported by Satya Sahoo)
           history.c
             Calling gl_load_history() multiple times, eventually led
             to a segmentation fault. This was due to the head of the
             list of unused history string segments not getting
             reset when the history buffer was cleared. While
             debugging this problem I also noticed that the history
             resizing function was way too complicated to verify, so
             after fixing the above bug, I heavily simplified the
             history resizing function, trading off a small reduction
             in memory efficiency, for greatly improved clarity, and
             thus made it much more verifiable and maintainable.

14/02/2004 mcs@astro.caltech.edu (fixes a bug reported by Tim Burress).
           getline.c
             If gl_change_terminal() was first used to tell
             gl_get_line to read input from a file, then called later
             to tell it to read subsequent input from a terminal, no
             prompt would be displayed for the first line of
             interactive input. The problem was that on reaching the
             end of the input file, gl_get_line() should have called
             gl_abandon_line(), to tell the next call to gl_get_line()
             to start inputting a new line from scratch. I have added
             this now.

14/02/2004 Krister Walfridsson (documented here by mcs@astro.caltech.edu)
           Makefile.in
             Krister noticed that I had failed to put $(srcdir)/ in front
             of some invokations of install.sh. I have remedied this.
           config.guess config.sub
             I hadn't updated these for a long time, so apparently they
             didn't recognise the BSD system that Krister was using.
             I have now updated them to the versions that come with
             autoconf-2.59. 

22/01/2004 mcs@astro.caltech.edu
           keytab.c
             When parsing key-binding specifications, backslash escaped
             characters following ^ characters were not being expanded.
             Thus ^\\ got interpretted as a control-\ character followed
             by a \ character, rather than simply as a control-\
             character.

12/01/2004 mcs@astro.caltech.edu
           cplfile.c cplmatch.c demo2.c demo3.c demo.c direader.c
           expand.c getline.c history.c homedir.c pathutil.c pcache.c
           configure.in configure INSTALL
             The configuration script now takes a
             "--without-file-system" argument. This is primarily for
             intended for embedded systems that either don't have
             filesystems, or where the file-system code in libtecla is
             unwanted bloat. It sets the WITHOUT_FILE_SYSTEM
             macro. This removes all code related to filesystem
             access, including the entire public file-expansion,
             file-completion and path-lookup facilities. Note that the
             general word completion facility is still included, but
             without the normally bundled file completion
             callback. Actually the callback is still there, but it
             reports no completions, regardless of what string you ask
             it to complete.

             This option is described in the INSTALL document.

12/01/2004 mcs@astro.caltech.edu
           getline.c configure.in configure INSTALL
             The configuration script now takes a
             "--without-file-actions" argument. This allows an
             application author/installer to prevent users of
             gl_get_line() from accessing the filesystem from the
             builtin actions of gl_get_line(). It defines a macro
             called HIDE_FILE_SYSTEM. This causes the
             "expand-filename", "read-from-file", "read-init-files",
             and "list-glob" action functions to be completely
             removed. It also changes the default behavior of actions
             such as "complete-word" and "list-or-eof" to show no
             completions, instead of the normal default of showing
             filename completions.

             This option is described in the INSTALL document.

11/01/2004 mcs@astro.caltech.edu
           getline.c man/func/gl_get_line.in
             In case an application's customized completion handler
             needs to write to the terminal for some unforseen reason,
             there needs to be a way for the it to cleanly suspend raw
             line editing, before writing to the terminal, and the
             caller then needs to be aware that it may need to
             resurrect the input line when the callback returns. I
             have now arranged that the completion callback functions
             can call the gl_normal_io() function for this purpose,
             and documented this in the gl_get_line() man page.

11/01/2004 mcs@astro.caltech.edu  (In response to a bug report by Satya Sahoo)
           getline.c
             The gl_configure_getline() function makes a malloc'd copy
             of the names of the configuration files that it is asked
             to read. Before the bug fix, if the application made one
             or more calls to this function, the memory allocated by
             the final call that it made before calling del_GetLine(),
             wasn't being freed. Note that memory allocated in all but
             the final call was being correctly freed, so the maximum
             extent of the memory leak was the length of the file
             name(s) passed in the final call to
             gl_configure_getline(), and an application that didn't
             call gl_configure_getline() didn't suffer any leak.

20/12/2003 mcs@astro.caltech.edu
           history.c
             Ellen tested the history fix that I reported below, and
             pointed out that it still had a problem. This turned out
             to be because getline.c was making some incorrect
             assumptions about the new behavior of history.c. This
             problem and the previous one both revolved around how
             search prefixes were stored and discarded, so I have now
             re-written this part of the code. Previously the search
             prefix was retained by looking for a line with that
             prefix, and keeping a pointer to that line. This saved
             memory, compared to storing a separate copy of the
             prefix, but it led to all kinds of hairy
             interdependencies, so I have now changed the code to keep
             a separate copy of search prefixes. To keep the memory
             requirements constant, the search prefix is stored in the
             history buffer, like normal history lines, but not
             referenced by the time-ordered history list. The prefix
             can now be kept around indefinitely, until a new search
             prefix is specified, regardless of changes to the
             archived lines in the history buffer. This is actually
             necessary to make the vi-mode re-search actions work
             correctly. In particular, I no longer discard the search
             prefix whenever a history search session ends. Also,
             rather than have getline.c keep its own record of when a
             history session is in progress, it now consults
             history.c, so that failed assumptions can't cause the
             kind of discrepancy that occurred before. For this to
             work, getline.c now explicitly tells history.c to cancel
             search sessions whenever it executes any non-history
             action.

14/12/2003 mcs@astro.caltech.edu (bug reported by Ellen Oschmann)
           history.c
             If one searched backwards for a prefix, then returned to
             the original line, changed that line, then started
             another backwards prefix search, getline incorrectly
             discarded the new search prefix in the process of
             throwing away its cached copy of the previous pre-search
             input line. In other words getline was belatedly
             cancelling a previous search, after a new search had
             already partially begun, and thus messed up the new
             search. The obvious fix was to arrange for the current
             search to be cancelled whenever the history pointer
             returns to its starting point, rather than waiting for
             the next search to begin from there.

14/12/2003 mcs@astro.caltech.edu
           history.c
             _glh_recall_line() was returning the last line in the
             history buffer instead of the line requested by the
             caller. This only affected the obscure "repeat-history"
             action-function, which probably isn't used by anybody.

09/12/2003 Version 1.5.0 released.

28/09/2003 mcs@astro.caltech.edu
           homedir.c
             When the home directory of the login user is requested,
             see if the HOME environment variable exists, and if so
             return its value, rather than looking up the user's home
             directory in the password file. This seems to be the
             convention adopted by other unix programs that perform
             tilde expansion, and it works around a strange problem,
             where a third-party libtecla program, statically compiled
             under an old version of RedHat, unexpectedly complained
             that getpwd() returned an error when the program was run
             under RedHat 9.

01/09/2003 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man/func/gl_get_line.in
           man/func/gl_register_action.in.
             It is now possible for an application to register
             external functions as action functions. These actions are
             initially bound to specified key-sequences, but if they
             are registered before the user's configuration file is
             loaded, they can also be re-bound by the user to
             different key-sequences. The function used to register a
             new action, is called gl_register_action().  Action
             functions are passed a readonly copy of the input line
             and the cursor position. They can display text to the
             terminal, or perform other operations on the application
             environment. Currently, they can't edit the input line or
             move the cursor. This will require the future addition of
             functions to queue the invokation of the built-in action
             functions.

26/08/2003 mcs@astro.caltech.edu
           getline.c
             I modified gl_update_buffer() to ensure that the cursor
             stays within the input line after external line
             modifications, and to queue a redisplay of the
             potentially modified input line.

21/07/2003 mcs@astro.caltech.edu
           configure.in configure Makefile.in Makefile.stub INSTALL
             By specifying --without-man-pages or --with-man-pages=no
             as command-line arguments to the configure script, it is
             now possible to have the configure script skip the
             man-page preprocessing step, and arrange for the man-page
             installation targets in the Makefile to do nothing. This
             option is designed for people who embed libtecla within
             other packages. It is also used by Makefile.stub when
             the distclean target is specified.

21/07/2003 mcs@astro.caltech.edu
           configure.in configure
             The previous workaround for recent versions of gcc
             placing /usr/local/include at the start of the system
             inlcude-file search path, broke something else.  The fix
             placed /usr/include before gcc's include area, which
             meant that gcc's modified version of stdarg.h was being
             ignored in deference to the version in /usr/include. I
             have changed the fix to have gcc report the search path,
             then have awk add options to CFLAGS to reorder this path,
             plaing /usr/local/include at the end.

             Also, under Solaris 9, including term.h without first
             including curses.h results in complaints about undefined
             symbols, such as bool. As a result the configure script's
             test for term.h was failing. I have now modified it to
             include curses.h in the test code that it uses to check
             for term.h. In the process I also improved the tests for
             curses.h and term.h to prevent an ncurses version of
             term.h from being used with the system-default version of
             curses.h.

29/06/2003 mcs@astro.caltech.edu
           Makefile.in direader.c homedir.c
             On some systems (eg. linux) the _POSIX_C_SOURCE
             feature-test macro is set by system headers, rather than
             being an option set by a project's Makefile at
             compilation time.  In software, such as tecla, where the
             definition of this macro is used as an indication of
             whether to use the non-reentrant or reentrant versions of
             system functions, this means that the reentrant functions
             are always used, regardless of whether this macro is set
             or not by the project Makefile. Thus, on such systems the
             reentrant and non-reentrant versions of the tecla library
             are essentially identical. This has a couple of
             drawbacks.  First, since thread-safe functions for
             traversing the password file don't exist, the supposedly
             non-reentrant version of the tecla library can't support
             ambiguous tab-completion of usernames in ~username/
             constructions. Secondly, on some systems the use of
             reentrant system functions dictates the use of a shared
             library that isn't needed for the non-reentrant
             functions, thus making it more difficult to distribute
             binary versions of the library.

             To remedy this situation I have modified the DEFINES_R
             variable in Makefile.in to arrange for the compiler to
             define a C macro called PREFER_REENTRANT when it is
             compiling the reentrant version of the tecla library.
             This macro is now used in the source code to determine
             when to require reentrant code. Whithin the source code,
             wherever a potentially non-reentrant interface is used,
             the existance of both this macro and a suitably valued
             _POSIX_C_SOURCE macro, are tested for to see if a
             reentrant alternative to the problem code should be used.

22/06/2003 mcs@astro.caltech.edu
           getline.c
             I changed the way that redisplays are requested and
             performed.  Redisplays are now queued by calling
             gl_queue_redisplay(), and subsequently performed by
             gl_flush_output(), when the queue of already pending
             output has been completely dispatched. This was necessary
             to prevent event handlers from filling up the output
             queue with redisplays, and it also simplifies a number of
             things. In the process I removed the gl_queue_display()
             function. I also wrote a gl_line_erased() function, which
             is now called by all functions that erase the input
             line. I also split the gl_abandon_line() function into
             public and private callable parts, and used the private
             version internally to arrange to discard the input line
             after errors.

             The raw_mode flag was not being initialized by new_GetLine().
             It is now initialized to zero.

             I removed the zapline flag, since using the endline flag to
             communicate the desire to terminate the line, did the same
             thing.

             gl_terminal_move_cursor() now does nothing when the input
             line isn't displayed.

18/03/2003 mcs@astro.caltech.edu
           getline.c
             Fixed bug which was causing newlines not to be output
             at the end of each newly entered line. I was
             interpreting the gl->endline flag in conflicting ways in
             two places. To fix this I have created a gl->displayed
             flag. This flags whether an input line is currently
             displayed.

17/03/2003 mcs@astro.caltech.edu
           getline.c libtecla.h man/func/gl_get_line.in
           man/func/gl_erase_terminal.in libtecla.map
             I added a new function that programs can call to clear
             the terminal between calls to gl_get_line().

11/03/2003 mcs@astro.caltech.edu
           configure.in configure
             Under linux when _POSIX_C_SOURCE is defined, getpwent()
             and associated functions become undefined, because
             _SVID_SOURCE and _BSD_SOURCE become undefined. Adding
             these feature macros back to CFLAGS resolves this.

06/03/2003 mcs@astro.caltech.edu
           getline.c libtecla.map man/func/gl_get_line.in
             Following the lead of Edward Chien, I wrote a function
             called gl_bind_keyseq(), which binds a specified
             key-sequence to a given action, or unbinds the
             key-sequence.

24/02/2003 mcs@astro.caltech.edu
           getline.c libtecla.map man/func/cpl_complete_word.in
             I implemented a simple function called
             cpl_recall_matches().  This recalls the return value of
             the last call to cpl_complete_word().

19/01/2003 mcs@astro.caltech.edu
           getline.c
             The documented signal handling, fd event-handling,
             inactivity timeout handling, and server-mode non-blocking
             I/O features are now implemented for non-interactive
             input streams, such as pipes and files.

19/01/2003 mcs@astro.caltech.edu
           getline.c libtecla.h man/func/gl_get_line.in demo3.c
             I added a new return status enumerator to report
             when an end-of-file condition causes gl_get_line()
             to return NULL.

13/01/2003 mcs@astro.caltech.edu
           history.c
             I rewrote the history facility. The previous
             circular buffer implementation was a nightmare to change,
             and it couldn't efficiently support certain newly
             requested features. The new implementation stores history
             lines in linked lists of fixed sized string segments,
             taken from the buffer, with each line being reference
             counted and recorded in a hash table. If the user enters
             a line multiple times, only one copy of the line is now
             stored. Not only does this make better use of the
             available buffer space, but it also makes it easy to
             ensure that a line whose prefix matches the current
             search prefix, isn't returned more than once in sequence,
             since we can simply see if the latest search result has
             the same hash-table pointer as the previous one, rather
             than having to compare strings. Another plus is that due
             to the use of linked lists of nodes of fixed size line
             segments, there is no longer any need to continually
             shuffle the contents of the buffer in order to defragment
             it. As far as the user is concerned, the visible
             differences are as follows:

             1. If the user enters a given line multiple times in a
                row, each one will be recorded in the history list,
                and will thus be listed by gl_show_history(), and
                saved in the history file. Previously only one line
                was recorded when consecutive duplicates were entered.
                This was a kludge to prevent history recall from
                recalling the same line multiple times in a row. This
                only achieved the desired result when not recalling by
                prefix.

             2. Not only simple recall, but prefix-based history line
                recalls now don't return the same line multiple times
                in a row. As mentioned in (1) above, previously this
                only worked when performing a simple recall, without a
                search prefix.

28/12/2002 mcs@astro.caltech.edu
           getline.c
             The one-line function, gl_buff_curpos_to_term_curpos()
             was only being used by gl_place_cursor(), so I inlined it
             in that function, and removed it.

28/12/2002 mcs@astro.caltech.edu
           getline.c
             gl_suspend_process() was calling the application-level
             gl_normal_io() and gl_raw_io() functions, where it should
             have been calling the internal versions _gl_normal_io()
             and _gl_raw_io().
             Also gl_handle_signal() was masking and unmasking just
             the signals of the first element of the gl[] array
             argument. It now masks and unmasks all trappable signals.

28/12/2002 mcs@astro.caltech.edu
           getline.c
             Now that the number of terminal characters used to
             display the current input line, is recorded, the relative
             line on which the last character of the input line
             resides can be determined without having to call
             gl_buff_curpos_to_term_curpos(). This is now used by
             gl_normal_io() via gl_start_newline(), so there is now no
             need for gl_buff_curpos_to_term_curpos() to be
             async-signal safe. I have thus removed the annoying
             gl->cwidth[] array, and gl_buff_curpos_to_term_curpos()
             now calls gl_width_of_char() directly again. There is
             also now no need for the gl_line_of_char_start() and
             gl_line_of_char_end() functions, so I have removed them.

28/12/2002 mcs@astro.caltech.edu
           getline.c
             Unfortunately it turns out that the terminfo/termcap
             control sequence which is defined to delete everything
             from the current position to the end of the terminal, is
             only defined to work when at the start of a terminal
             line. In gnome terminals in RedHat 8.0, if it is used
             within a terminal line, it erases the whole terminal
             line, rather than just what follows the cursor. Thus to
             portably truncate the displayed input line it is
             necessary to first use the control sequence which deletes
             from the cursor position to the end of the line, then if
             there are more terminal lines, move to the start of the
             next line, and use the delete to end-of-terminal control
             sequence, then restore the cursor position. This requires
             that one know how many physical terminal lines are used
             by the current input line, so I now keep a record of the
             number of characters so far displayed to the terminal
             following the start of the prompt, and the new
             gl_truncate_display() function uses this information to
             truncate the displayed input line from the current cursor
             position.

28/12/2002 mcs@astro.caltech.edu
           getline.c
             gl_start_newline() now moves to an empty line following
             the input line, rather than just to the next line. It
             also arranges for the input line to be redisplayed before
             editing resumes. A major user of this is gl_print_info(),
             which now need not be followed by an explicit call to
             gl_redisplay(), since the terminal input loop in
             gl_get_input_line() ensures that gl_redisplay() is called
             after any action function that asserts gl->redisplay.
             Also, all functions that erase the displayed input line
             can now call the gl_erase_line() function, which is
             designed to work correctly even when a terminal resize
             invalidates the horizontal cursor position.  Finally, the
             new gl_queue_display() function is now used by functions
             that need to arrange for the input line to be displayed
             from scratch after the displayed line has been erased or
             invalidated by other text being written to the terminal.
             All of these changes are aimed at reducing the number of
             places that directly modify gl->term_curpos and
             gl->redisplay.

22/12/2002 Markus Gyger   (logged here by mcs)
           Makefile.in update_html
	     In places where echo and sed were being used to extract
	     the base names of files, Markus substituted the basename
	     command. He also replaced explicit cp and chmod commands
	     with invokations of the install-sh script.
           configure.in
             Use $target_os and $target_cpu, where appropriate,
	     instead of $target.
           configure.in
             The Solaris man function and library man pages should
	     be in sections 3lib and 3tecla respectively, only in
	     Solaris version 2.8 and above.
           configure.in
             Markus provided values for the man page configuration
             variables for HPUX.
           man/*/*.in
             I had missed parameterizing man page section numbers in
	     the man page titles, Markus corrected this.
           man/func/libtecla_version.in
             Fixed incorrect section number in the link to the
             libtecla man page.
           homedir.c
             When compiled to be reentrant, although one can't use the
	     non-reentrant getpwent() function to scan the password
	     file for username completions, one can at least see if
	     the prefix being completed is a valid username, and if
	     the username of the current user minimally matches the
	     prefix, and if so list them. I simplified Markus'
	     modification by adding a prefix argument to the
             _hd_scan_user_home_dirs() function, and redefining the
	     function description accordingly, such that now it
	     reports only those password file entries who's usernames
	     minimally match the specified prefix. Without this, it
	     would have been necessary to peak inside the private data
	     argument passed in by cf_complete_username().
             Markus also provided code which under Solaris uses the
             non-reentrant interfaces if the reentrant version of the
             library isn't linked with the threads library.

19/12/2002 mcs@astro.caltech.edu
           Makefile.in
             Markus pointed out that LDFLAGS was being picked up by
             the configure script, but not then being interpolated
             into te Makefile. I have thus added the necessary
             assignment to Makefile.in and arranged for the value of
             LDFLAGS to be passed on to recursive make's. I also did
             the same for CPPFLAGS, which had also been omitted.

18/12/2002 mcs@astro.caltech.edu
           man/* man/*/* configure.in configure Makefile.in
           update_html
             It turns out that the assignment of man page sections to
             topics differs somewhat from system to system, so this is
             another thing that needs to be configured by the main
             configuration script, rather than being hardwired. All
             man pages have now been moved into suitably named
             topic-specific sub-directories of the top-level man
             directory, and instead of having a numeric suffix, now
             have the .in suffix, since they are now preprocessed by
             the configure script, in the same fashion as Makefile.in.
             Whithin these *.in versions of the man pages, and within
             Makefile.in, the installation subdirectory (eg. man1) and
             the file-name suffix (eg. 1), are written using
             configuration macros, so that they get expanded to the
             appropriate tokens when the configure script is run. In
             principle, the man pages could also take advantage of
             other configuration macros, such as the one which expands
             to the library installation directory, to include full
             path names to installed files in the documentation, so in
             the future this feature could have more uses than just
             that of parameterizing man page sections.

18/12/2002 mcs@astro.caltech.edu
           man3 man3/* Makefile.in html/index.html update_html
             Markus suggested splitting the gl_get_line(3) man page
             into user and developer sections, and also pointed out
             that the enhance man page should be in section 1, not
             section 3. I have thus created a top-level man
             directory in which to place the various sections, and
             moved the man3 directory into it. The enhance.3 man page
             is now in man/man1/enhance.1. I have extracted all
             user-oriented sections from the gl_get_line(3) man page
             and placed them in a new man7/tecla.7 man page.

18/12/2002 mcs@astro.caltech.edu
           getline.c
             Terminal resizing was broken in normal mode, due to
             me forcing the terminal cursor position to zero in the
             wrong place in gl_check_caught_signal().

14/12/2002 Markus Gyger  (logged here by mcs)
           configure.in configure
             Under Solaris, recent versions of gcc search
             /usr/local/include for header files before the system
             directories. This caused a problem if ncurses was
             installed under Solaris, since the termcap.h include file
             in /usr/local/include ended up being used at compile
             time, whereas the system default version of the curses
             library was used at link time. Since the two libraries
             declare tputs() differently, this evoked a complaint from
             gcc. Markus came up with a way to force Gnu cpp to move
             /usr/local/include to the end of the system-include-file
             search path, where it belongs.

13/12/2002 mcs@astro.caltech.edu
           man3/gl_io_mode.3
             I rewrote the man page which documents the new non-blocking
             server I/O mode.

12/12/2002 mcs@astro.caltech.edu
           demo3.c
             I wrote a new version of demo3.c, using signal handlers
             that call gl_handle_signal() and gl_abandon_line(), where
             previously in this demo, these functions were called from
             the application code.

05/12/2002 mcs@astro.caltech.edu
           getline.c
             gl_normal_io(), gl_raw_io() and gl_handle_signal() and
             gl_abandon_line() are now signal safe, provided that
             signal handlers that call them are installed with sa_mask's
             that block all other signals who's handlers call them.
             This is the case if gl_tty_signals() is used to install
             signal handlers that call any of these functions.

             A major stumbling block that had to be overcome was that
             gl_displayed_char_width() calls isprint(), which can't
             safely be called from a signal handler (eg. under linux,
             the is*() functions all use thread-specific data
             facilities to support per-thread locales, and the
             thread-specific data facilities aren't signal safe). To
             work around this, all functions that modify the
             input-line buffer, now do so via accessor functions which
             also maintain a parallel array of character widths, for
             use by gl_buff_curpos_to_term_curpos() in place of
             gl_displayed_char_width(). Other minor problems were the
             need to avoid tputs(), who's signal safety isn't defined.

05/12/2002 Eric Norum        (logged here by mcs@astro.caltech.edu)
           configure.in
             Eric provided the configuration information needed
             to build shared libraries under Darwin (Max OS X).

05/12/2002 Richard Mlynarik  (logged here by mcs@astro.caltech.edu)
           configure.in
             AC_PROG_RANLIB gets the wrong version of ranlib when
             cross compiling, so has now been replaced by an
             invokation of AC_CHECK_TOOL. In addition, AC_CHECK_TOOL
             is also now used to find an appropriate version of LD.

05/12/2002 mcs@astro.caltech.edu (based on patch by Pankaj Rathore)
           getline.c libtecla.h libtecla.map man3/gl_get_line.3
             The new gl_set_term_size() function provides a way
             to tell gl_get_line() about changes in the size of
             the terminal in cases where the values returned by
             ioctl(TIOCGWINSZ) isn't correct.

05/12/2002 mcs@astro.caltech.edu
           getline.c
             Rather than calling sprintf() to see how much space would
             be needed to print a given number in octal, I wrote a
             gl_octal_width() function, for use by
             gl_displayed_char_width().  This makes the latter
             function async signal safe.

05/12/2002 mcs@astro.caltech.edu
           chrqueue.c
             Whenever the buffer is exhausted, and getting a new
             buffer node would require a call to malloc(), attempt
             to flush the buffer to the terminal. In blocking I/O
             mode this means that the buffer never grows. In
             non-blocking I/O mode, it just helps keep the buffer
             size down.

05/12/2002 mcs@astro.caltech.edu
           freelist.h freelist.c
             The new _idle_FreeListNodes() function queries the
             number of nodes in the freelist which aren't currently
             in use.

05/12/2002 mcs@astro.caltech.edu
           Makefile.stub
             This now accepts all of the targets that the configured
             makefile does, and after configuring the latter makefile,
             it invokes it with the same options.

03/12/2002 mcs@astro.caltech.edu
           mans3/gl_io_mode.3
             I completed the man page for all of the new functions
             related to non-blocking I/O.

01/12/2002 mcs@astro.caltech.edu
           man3/gl_get_line.3
             I wrote a long section on reliable signal handling,
             explaining how gl_get_line() does this, how to make
             use of this in a program, and how to handle signals
             reliably when faced with other blocking functions.
             This basically documents what I have learnt about
             signal handling while working on this library.

01/12/2002 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             In non-blocking server mode, the gl_replace_prompt()
             function can now be used between calls to gl_get_line()
             if the application wants to change the prompt of the
             line that is being edited.

01/12/2002 mcs@astro.caltech.edu
           man3/gl_get_line.3
             I documented the new gl_return_status() and
             gl_error_message() functions.

01/12/2002 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             Added SIGPOLL and SIGXFSZ to the list of signals that
             are trapped by default. These are process termination
             signals, so the terminal needs to be restored to a
             usable state before they terminate the process.

27/11/2002 mcs@astro.caltech.edu
           getline.c libtecla.h
             Completed the essential changes needed to support
             non-blocking server-I/O mode.

             The new gl_io_mode() function allows one to switch to
             and from non-blocking server-I/O mode.

             The new gl_raw_io() function is used in non-blocking
             server-I/O mode to switch the terminal into non-blocking
             raw I/O mode.

             The new gl_normal_io() function is used in non-blocking
             server-I/O mode to switch the restore the terminal to
             a normal, blocking state. This is used to suspend line
             input before suspending the process or writing messages
             to the terminal.

             The new gl_tty_signals() function installs specified
             signals handlers for all signals that suspend, terminate
             or resume processes, and also for signals that indicate
             that the terminal has been resized. This not only saves
             the application from having to keep its own ifdef'd list
             of such signals, of which there are many, but it also
             makes sure that these signal handlers are registered
             correctly. This includes using the sa_mask member of each
             sigaction structure to ensure that only one of these
             handlers runs at a time. This is essential to avoid the
             signal handlers all trying to simultaneously modify
             shared global data.

             The new gl_handle_signal() function is provided for
             responding (from application level) to signals caught by
             the application. It handles process suspension, process
             termination and terminal resize signals.

             The new gl_pending_io() function tells the application
             what direction of I/O gl_get_line() is currently waiting
             for.

             In non-blocking server I/O mode, the new
             gl_abandon_line() function can be called between calls to
             gl_get_line() to discard an input line and force the next
             call to gl_get_line() to start the input of a new line.

             Also, in non-blocking server-I/O gl_get_line() doesn't
             attempt to do anything but return when one of the signals
             that it is configured to catch is caught. This is
             necessary because when in this mode, the application is
             required to handle these signals when gl_get_line() is
             running, and the default configuration of most of these
             signals in gl_get_line() is to restore the terminal then
             call the application signal handlers. This would be a
             case of too many cooks spoiling the broth, so in this
             mode, gl_get_line() always defers to the application's
             signal handlers.
             
26/11/2002 mcs@astro.caltech.edu
           getline.c libtecla.h
             I implemented a couple of new functions to support
             reliable signal handling, as now documented
             (see above) in the gl_get_line(3) man page.

             The new gl_catch_blocked() function tells gl_get_line()
             to unblock all configured signals around calls to
             long-running functions, not only those that aren't
             blocked when gl_get_line() is called. This allows
             the caller to implement reliable signal handling,
             since the unblocking is only done from within code
             protected by sigsetjmp(), which avoids race conditions.

             The new gl_list_signals() function fills a provided
             sigset_t with the set of signals that gl_get_line() is
             currently configured to catch. This allows callers to
             block said signals, such that they are only unblocked by
             gl_get_line() when it is waiting for I/O. When used in
             conjunction with the gl_catch_blocked() function, this
             removes the potential for race conditions.

             Also, when gl_get_line() installs its signal handler,
             it uses the sa_mask member of the sigaction structure
             to ensure that only one instance of this signal handler
             will ever be executing at a time.

25/11/2002 mcs@astro.caltech.edu (bug reported by Pankaj Rathore)
           getline.c
             When any history recall action was invoked when the
             input line buffer was full, an error message would be
             displayed complaining about the length of the string
             in the line input buffer being inconsistent with the
             specified allocated size. This was because instead of
             sending the allocated size of the input line, I was
             sending the length excluding the element that is
             reserved for the '\0' terminator. Sending it the
             correct size corrected the problem.

24/11/2002 mcs@astro.caltech.edu
           getline.c
             All public functions which take GetLine objects as
             arguments now block signals on entry and restore the
             signal mask on return. This was an attempt to make it
             safe to call getline functions from signal handlers, but
             the fact is that the functions that I really wanted this
             to apply to, potentially call malloc(), so this currently
             isn't the case.

23/11/2002 mcs@astro.caltech.edu
           getline.c libtecla.h
             The new gl_return_status() function returns an enumerated
             return status which can be used to query what caused
             gl_get_line() to return.

22/11/2002 mcs@astro.caltech.edu
           Most existing .c and .h files, plus errmsg.c errmsg.h
           Makefile.rules
             Until now, many library functions would report error
             messages to stderr. This isn't appropriate for library
             functions, so in place of this behavior, error messages
             are now recorded in internal ErrMsg objects, and passed
             between modules via new module-specific error querying
             functions. In addition, errno is now set appropriately.
             Thus when gl_get_line() and related functions return an
             error, strerror() can be used to look up system errors,
             and gl_error_message() can be used to recover a higher level
             error message. Note that error messages that are
             responses to user actions continue to be reported to the
             terminal, as before.

21/11/2002 mcs@astro.caltech.edu
           getline.c keytab.h keytab.c Makefile.rules
             I wrote a new version of _kt_lookup_binding() that didn't
             require the caller to have access to the innards of a
             KeyTab object. This then enabled me to move the definition
             of KeyTab objects into keytab.c and make the typedef in
             keytab.h opaque. Many nested includes were also moved from
             keytab.h into keytab.c.

05/11/2002 mcs@astro.caltech.edu
           getline.c libtecla.map libtecla.h demo3.c
             I split the old gl_resize_terminal() function into
             two parts, gl_query_size() and gl_update_size(), with
             the latter calling the former to get the new terminal
             size.

05/11/2002 mcs@astro.caltech.edu
           getline.c
             I fixed a long time bug in the terminal resizing code.
             When the cursor wasn't on the last terminal line of the
             input line, the resizing code would redisplay the
             the line one or more lines above where it should be
             restored. This was due to an error in the calculation of
             the number of lines above the cursor position.

04/11/2002 mcs@astro.caltech.edu
           demo.c demo2.c demo3.c
             I used the new gl_display_text() function to display
             introductory text at the startup of each of the demo
             programs. The text is enclosed within a box of asterixes,
             drawn dynamically to fit within the confines of the
             available terminal width.

04/11/2002 mcs@astro.caltech.edu
           libtecla.h getline.c ioutil.c ioutil.h Makefile.rules
           libtecla.map man3/gl_get_line.3 man3/gl_display_text.3
             Needing a way to display introductory text intelligently
             in the demo programs, I wrote and documented the
             gl_display_text() function. This justifies arbitrary
             length text within the bounds of the terminal width,
             with or without optional indentation, prefixes and
             suffixes.

03/11/2002 mcs@astro.caltech.edu
           demo3.c Makefile.rules
             I wrote a new demonstration program. This program acts
             exactly like the main demonstration program, except that
             it uses an external event loop instead of using the
             gl_get_line() internal event loop. This is thus an example
             of the new non-blocking server I/O facility.

02/11/2002 mcs@astro.caltech.edu
           getline.c keytab.c keytab.h libtecla.h man3/gl_get_line.3
           man3/gl_completion_action.3
             I added the ability to register additional word
             completion actions via the new function
             gl_completion_action().  All action functions now take a
             new (void *data) argument, which is stored with the
             function in the symbol table of actions. The new
             gl_completion_action() function uses this feature to
             record dynamically allocated objects containing the
             specified completion function and callback data along
             with either the gl_complete_word() action function, or
             the gl_list_completions() action function.  These two
             actions continue to use the builtin completion functions
             when their data pointer is NULL.

20/10/2002 mcs@astro.caltech.edu
           The following are changes merged from the non-blocking
           gl_get_line() development branch.

           getline.c
             I wrote a gl_start_newline() function, to replace all of
             the explicit calls to output \r\n to stdout.

             Informational messages are now written to the terminal
             using a new variadic function called gl_print_info().
             This starts a newline, writes string arguments until a
             special argument, GL_END_INFO, is seen, then starts
             another newline.

             Changed _output_ to _print_ in the following function
             names gl_output_control_sequence(), gl_output_char(),
             gl_output_string() and gl_output_raw_string().

             gl_print_raw_string() now has a length argument, so that
             strings that aren't terminated with '\0' can be printed.

             The display of the initial contents of a new line to be
             edited has been moved into a new function called
             gl_present_line().

             The gl_get_input_line() function now takes the prompt
             string as an argument so that gl_replace_prompt() can be
             called from within this function instead of from
             gl_get_line().

             Keyboard input is now buffered in a persistent buffer in
             the parent GetLine object. gl_read_character() checks
             this for unprocessed characters in preference to calling
             gl_read_terminal() to append characters to it.  A new
             function, gl_discard_chars(), removes processed
             characters from this buffer. This change is in
             preparation for a non-blocking version of gl_get_line(),
             where partially input key-sequences must be stored
             between calls to gl_get_line().

           getline.c getline.h history.c history.h cplmatch.c \
           cplmatch.h expand.c expand.h
             All terminal output from gl_get_line() is now routed
             through a GL_WRITE_FN() callback function called
             gl_write_fn. Internal functions in cplmatch.c,
             expand.c and history.c have been created which take
             such callbacks to write output. These are used both
             by functions in getline.c, to display file completions,
             expansions, history etc, and as the internals of existing
             public functions in these files that print to stdio
             streams. In the latter case an internal stdio
             GL_WRITE_FN() callback is substituted, so that the
             functions behave as before.

           getline.c chrqueue.c chrqueue.h
             The gl_write_fn() callback used by gl_get_line() now
             writes to a queue, implemented in chrqueue.c. This queue
             is implemented as a list of blocks of buffer segments,
             the number of which shrink and grow as
             needed. The contents of the queue are flushed to the
             terminal via another GL_WRITE_FN() callback passed to the
             queue object. Currently gl_get_line() passes an internal
             function assigned to gl->flush_fn, called
             gl_flush_terminal(), which writes the contents of the
             queue to the terminal, and knows how to handle both
             blocking and non-blocking I/O. The output queue is
             designed to be flushed to the terminal incrementally, and
             thereby also facilitates non-blocking I/O.

           getline.c getline.h
             gl_get_line() now reads all input via the GL_READ_FN()
             callback, assigned to gl->read_fn. Currently this is
             set to an internal function called gl_read_terminal(),
             which knows how to handle both blocking and
             non-blocking I/O.

           getline.c libtecla.h
             The new gl_set_nonblocking() function can be used to
             enable or disable non-blocking I/O. The default is still
             blocking I/O. In non-blocking mode, the terminal is told
             not to wait when either reading or writing would block.
             gl_get_line() then returns, with a return value of NULL,
             but with the terminal left in raw mode, so that the
             caller's event loop can detect key presses. The caller
             should call gl_return_status() to check whether the NULL
             return value was due to an error, lack of input, or
             inability to write to the terminal without waiting. If
             either reading or writing was said to have blocked, the
             user then should check for I/O readiness in the specified
             direction before calling gl_get_line() again to
             incrementally build up the input line.

05/08/2002 mcs@astro.caltech.edu
           man3/gl_get_line.3 man3/gl_inactivity_timeout.3
             I documented the new gl_inactivity_timeout() function.

08/07/2002 mcs@astro.caltech.edu
           libtecla.h getline.c libtecla.map
             I added a new gl_inactivity_timeout() function. On
             systems that have the select system call, this provides
             the option of registering a function that is then called
             whenever no I/O activity has been seen for more than a
             specified period of time. Like the gl_watch_fd()
             facility, timeout callbacks return a code which tells
             gl_get_line() how to proceed after the timeout has been
             handled.
             
04/07/2002 mcs@astro.caltech.edu  (based on a bug report from Michael MacFaden)
           getline.c
             The internal event handler wasn't responding to write
             events on client file descriptors, due to a typo which
             resulted in read events being checked for twice, and
             writes not checked for at all.
           pathutil.c
             The amount of space to allocate for pathnames is supposed
             to come from PATH_MAX in limits.h, but I had neglected to
             include limits.h. This went unnoticed because on most
             systems the equivalent number is deduced by calling
             pathconf(). Apparently under NetBSD this function doesn't
             work correctly over NFS mounts.

30/05/2002 Version 1.4.1 released.

25/05/2002 mcs@astro.caltech.edu  (based on suggestions by Paul Smith)
           pathutil.c
             Apparently, under QNX pathconf("/",_PC_PATH_MAX) returns
             EINVAL. At Paul's suggestion I have modified the code to
             silently substitute the existing MAX_PATHLEN_FALLBACK
             value if pathconf() returns an error of any kind.
           homedir.c
             Under QNX, sysconf(_SC_GETPW_R_SIZE_MAX) also apparently
             returns EINVAL, so as with pathconf() I modified the code
             to substitute a fallback default, rather than
             complaining and failing.
           enhance.c
             Paul told me that the inclusion of sys/termios.h was
             causing compilation of enhance.c to fail under QNX. This
             line is a bug.  The correct thing to do is include
             termios.h without a sub-directory prefix, as I was
             already doing futher up in the file, so I have just
             removed the errant include line.

07/05/2002 mcs@astro.caltech.edu  (async development branch only)
           getline.c
             gl_read_character() now caches and reads unprocessed
             characters from a key-press lookahead buffer. Whenever
             gl_intepret_char() receives a new character which makes
             an initially promising key-sequence no longer match the
             prefix of any binding, it now simply discards the first
             character from the key-press buffer and resets the buffer
             pointer so that the next call to gl_read_character()
             returns the character that followed it, from the buffer.
           getline.c
             The part of gl_get_input_line() which preloads, displays
             and prepares to edit a new input line, has now been moved
             into a function called gl_present_line().

12/02/2002 mcs@astro.caltech.edu
           getline.c configure.in configure
             Mac OS X doesn't have a term.h or termcap.h, but it does
             define prototypes for tputs() and setupterm(), so the
             default prototypes that I was including if no headers
             where available, upset it. I've removed these prototypes.
             I also now conditionally include whichever is found of
             curses.h and ncurses/curses.h for both termcap and
             terminfo (before I wasn't including curses.h when
             termcap was selected).

12/02/2002 mcs@astro.caltech.edu
           Updated version number to 1.4.1, ready for a micro
           release.

12/02/2002 mcs@astro.caltech.edu
           html/index.html
             Added Mac OS X and Cygwin to the list of systems that
             can compile libtecla.

12/02/2002 mcs@astro.caltech.edu
           getline.c
             Under Mac OS X, the tputs() callback function returns
             void, instead of the int return value used by other
             systems. This declaration is now used if both __MACH__
             and __APPLE__ are defined. Hopefully these are the
             correct system macros to check. Thanks for Stephan
             Fiedler for providing information on Mac OS X.

11/02/2002 mcs@astro.caltech.edu
           configure.in configure getline.c
             Some systems don't have term.h, and others have it hidden
             in an ncurses sub-directory of the standard system include
             directory. If term.h can't be found, simply don't include
             it. If it is in an ncurses sub-directory, include
             ncurses/term.h instead of term.h.

04/02/2002 mcs@astro.caltech.edu
           configure.in configure Makefile.in Makefile.rules
             Use ranlib on systems that need it (Mac OS X).  Also,
             make all components of the installation directories where
             needed, instead of assuming that they exist.

04/02/2002 mcs@astro.caltech.edu
           getline.c
             When the tab completion binding was unbound from the tab
             key, hitting the tab key caused gl_get_line() to ring the
             bell instead of inserting a tab character. This is
             problematic when using the 'enhance' program with
             Jython, since tabs are important in Python. I have
             corrected this.

10/12/2001 Version 1.4.0 released.

10/12/2001 mcs@astro.caltech.edu
           getline.c
             If the TIOCGWINSZ ioctl doesn't work, as is the case when
             running in an emacs shell, leave the size unchanged, rather
             than returning a fatal error.

07/12/2001 mcs@astro.caltech.edu
           configure.in configure
             Now that the configure version of CFLAGS is included in
             the makefile, I noticed that the optimization flags -g
             and -O2 had been added. It turns out that if CFLAGS isn't
             already set, the autoconf AC_PROG_CC macro initializes it
             with these two optimization flags. Since this would break
             backwards compatibility in embedded distributions that
             already use the OPT= makefile argument, and because
             turning debugging on needlessly bloats the library, I now
             make sure that CFLAGS is set before calling this macro.

07/12/2001 mcs@astro.caltech.edu
           enhance.c
             Use argv[0] in error reports instead of using a
             hardcoded macro.

07/12/2001 mcs@astro.caltech.edu
           getline.c
             The cut buffer wasn't being cleared after being
             used as a work buffer by gl_load_history().

06/12/2001 mcs@astro.caltech.edu
           configure.in configure
             I removed my now redundant definition of SUN_TPUTS from
             CFLAGS. I also added "-I/usr/include" to CFLAGS under
             Solaris to prevent gcc from seeing conflicting versions
             of system header files in /usr/local/include.

06/12/2001 Markus Gyger (logged here by mcs)
           Lots of files.
             Lots of corrections to misspellings and typos in the
             comments.
           getline.c
             Markus reverted a supposed fix that I added a day or two
             ago. I had incorrectly thought that in Solaris 8, Sun had
             finally brought their declaration of the callback
             function of tputs() into line with other systems, but it
             turned out that gcc was pulling in a GNU version of
             term.h from /usr/local/include, and this was what
             confused me.

05/12/2001 mcs@astro.caltech.edu
           Makefile.in
             I added @CFLAGS@ to the CFLAGS assignment, so that
             if CFLAGS is set as an environment variable when
             configure is run, the corresponding make variable
             includes its values in the output makefile.

05/12/2001 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man3/gl_get_line.3
           man3/gl_last_signal.3
             I added a function that programs can use to find out
             which signal caused gl_get_line() to return EINTR.

05/12/2001 mcs@astro.caltech.edu
           getline.c
             When the newline action was triggered by a printable
             character, it failed to display that character. It now
             does. Also, extra control codes that I had added, to
             clear to the end of the display after the carriage return,
             but before displaying the prompt, were confusing expect
             scripts, so I have removed them. This step is now done
             instead in gl_redisplay() after displaying the full input
             line.

05/12/2001 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             A user convinced me that continuing to invoke meta
             keybindings for meta characters that are printable is a
             bad idea, as is allowing users to ask to have setlocale()
             called behind the application's back. I have thus changed
             this. The setlocale configuration option has gone, and
             gl_get_line() is now completely 8-bit clean, by default.
             This means that if a meta character is printable, it is
             treated as a literal character, rather than a potential
             M-c binding.  Meta bindings can still be invoked via
             their Esc-c equivalents, and indeed most terminal
             emulators either output such escape pairs by default when
             the meta character is pressed, or can be configured to do
             so. I have documented how to configure xterm to do this,
             in the man page.

03/12/2001 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             gl_get_line() by default now prints any 8-bit printable
             characters that don't match keybindings. Previously
             characters > 127 were only printed if preceded by the
             literal-next action.  Alternatively, by placing the
             command literal_if_printable in the tecla configuration
             file, all printable characters are treated as literal
             characters, even if they are bound to action functions.

             For international users of programs written by
             programmers that weren't aware of the need to call
             setlocale() to support alternate character sets, the
             configuration file can now also contain the single-word
             command "setlocale", which tells gl_get_line() to remedy
             this.

27/11/2001 mcs@astro.caltech.edu
           demo.c demo2.c enhance man3/gl_get_line.3
             All demos and programs now call setlocale(LC_CTYPE,"").
             This makes them support character sets of different
             locales, where specified with the LC_CTYPE, LC_ALL, or
             LANG environment variables. I also added this to the demo
             in the man page, and documented its effect.

27/11/2001 mcs@astro.caltech.edu
           getline.c
             When displaying unsigned characters with values over
             127 literally, previously it was assumed that they would
             all be displayable. Now isprint() is consulted, and if it
             says that a character isn't printable, the character code
             is displayed in octal like \307. In non-C locales, some
             characters with values > 127 are displayable, and
             isprint() tells gl_get_line() which are and which aren't.

27/11/2001 mcs@astro.caltech.edu
           getline.c pathutil.c history.c enhance.c demo2.c
             All arguments of the ctype.h character class functions
             are now cast to (int)(unsigned char). Previously they
             were cast to (int), which doesn't correctly conform to
             the requirements of the C standard, and could cause
             problems for characters with values > 127 on systems
             with signed char's.

26/11/2001 mcs@astro.caltech.edu
           man3/enhance.3 man3/libtecla.3
             I started writing a man page for the enhance program.

26/11/2001 mcs@astro.caltech.edu
           Makefile.in Makefile.rules INSTALL
             It is now possible to specify whether the demos and other
             programs are to be built, by overriding the default
             values of the DEMOS, PROGRAMS and PROGRAMS_R variables.
             I have also documented the BINDIR variable and the
             install_bin makefile target.

22/11/2001 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man3/gl_get_line.3
           man3/gl_ignore_signal.3 man3/gl_trap_signal.3
             Signal handling has now been modified to be customizable.
             Signals that are trapped by default can be removed from
             the list of trapped signals, and signals that aren't
             currently trapped, can be added to the list. Applications
             can also specify the signal and terminal environments in
             which an application's signal handler is invoked, and
             what gl_get_line() does after the signal handler returns.

13/11/2001 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             Added half-bright, reverse-video and blinking text to the
             available prompt formatting options.
           getline.c
             Removed ^O from the default VT100 sgr0 capability
             string.  Apparently it can cause problems with some
             terminal emulators, and we don't need it, since it turns
             off the alternative character set mode, which we don't
             use.
           getline.c
             gl_tigetstr() and gl_tgetstr() didn't guard against the
             error returns of tigetstr() and tgetstr() respectively.
             They now do.

11/11/2001 mcs@astro.caltech.edu
           getline.c libtecla.h libtecla.map man3/gl_get_line.3
           man3/gl_prompt_style.3
             Although the default remains to display the prompt string
             literally, the new gl_prompt_style() function can be used
             to enable text attribute formatting directives in prompt
             strings, such as underlining, bold font, and highlighting
             directives.

09/11/2001 mcs@astro.caltech.edu
           enhance.c Makefile.rules configure.in configure
             I added a new program to the distribution that allows one
             to run most third party programs with the tecla library
             providing command-line editing.

08/11/2001 mcs@astro.caltech.edu
           libtecla.h getline.c man3/gl_get_line.3 history.c history.h
             I added a max_lines argument to gl_show_history() and
             _glh_show_history(). This can optionally be used to
             set a limit on the number of history lines displayed.
           libtecla.h getline.c man3/gl_get_line.3
             I added a new function called gl_replace_prompt(). This
             can be used by gl_get_line() callback functions to
             request that a new prompt be use when they return.

06/11/2001 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             I implemented, bound and documented the list-history
             action, used for listing historical lines of the current
             history group.
           getline.c man3/gl_get_line.3 man3/gl_echo_mode.3
             I wrote functions to specify and query whether subsequent
             lines will be visible as they are being typed.

28/10/2001 mcs@astro.caltech.edu
           getline.c man3/gl_get_line.3
             For those cases where a terminal provides its own
             high-level terminal editing facilities, you can now
             specify an edit-mode argument of 'none'. This disables
             all tecla key bindings, and by using canonical terminal
             input mode instead of raw input mode, editing is left up
             to the terminal driver.

21/10/2001 mcs@astro.caltech.edu
           libtecla.h getline.c history.c history.h
           man3/gl_get_line.3 man3/gl_history_info.3
             I added the new gl_state_of_history(),
             gl_range_of_history() and gl_size_of_history()
             functions for querying information about the
             history list.
           history.c
             While testing the new gl_size_of_history()
             function, I noticed that when the history buffer
             wrapped, any location nodes of old lines between
             the most recent line and the end of the buffer
             weren't being removed. This could result in bogus
             entries appearing at the start of the history list.
             Now fixed.

20/10/2001 mcs@astro.caltech.edu

           libtecla.h getline.c history.c history.h
           man3/gl_get_line.3 man3/gl_lookup_history.3
             I added a function called gl_lookup_history(), that
             the application can use to lookup lines in the history
             list.
           libtecla.h getline.c history.c history.h man3/gl_get_line.3
             gl_show_history() now takes a format string argument
             to control how the line is displayed, and with what
             information. It also now provides the option of either
             displaying all history lines or just those of the
             current history group.
           getline.c man3/gl_get_line.3
             gl_get_line() only archives lines in the history buffer
             if the newline action was invoked by a newline or
             carriage return character.

16/10/2001 mcs@astro.caltech.edu

           history.c history.h getline.c libtecla.h libtecla.map
           man3/gl_get_line.3 man3/gl_resize_history.3
           man3/gl_limit_history.3 man3/gl_clear_history.3
           man3/gl_toggle_history.3
	     I added a number of miscellaneous history configuration
	     functions. You can now resize or delete the history
	     buffer, limit the number of lines that are allowed in the
	     buffer, clear either all history or just the history of
	     the current history group, and temporarily enable and
	     disable the history mechanism.

13/10/2001 mcs@astro.caltech.edu

           getline.c
             tputs_fp is now only declared if using termcap or
             terminfo.
           getline.c libtecla.map man3/gl_get_line.3
           man3/gl_terminal_size.3
             I added a public gl_terminal_size() function for
             updating and querying the current size of the terminal.
           update_version configure.in libtecla.h
             A user noted that on systems where the configure script
             couldn't be used, it was inconvenient to have the version
             number macros set by the configure script, so they are
             now specified in libtecla.h. To reduce the likelihood
             that the various files where the version number now
             appears might get out of sync, I have written the
             update_version script, which changes the version number
             in all of these files to a given value.

01/10/2001 mcs@astro.caltech.edu

           getline.c history.c history.h man3/gl_get_line.3
             I added a max_lines argument to gl_save_history(), to
             allow people to optionally place a ceiling on the number
             of history lines saved. Specifying this as -1 sets the
             ceiling to infinity.

01/10/2001 mcs@astro.caltech.edu

           configure.in configure
             Under digital unix, getline wouldn't compile with
             _POSIX_C_SOURCE set, due to type definitions needed by
             select being excluded by this flag. Defining the
             _OSF_SOURCE macro as well on this system, resolved this.

30/09/2001 mcs@astro.caltech.edu

           getline.c libtecla.h history.c history.h man3/gl_get_line.3
           man3/gl_group_history.3
             I implemented history streams. History streams
             effectively allow multiple history lists to be stored in
             a single history buffer. Lines in the buffer are tagged
             with the current stream identification number, and
             lookups only consider lines that are marked with the
             current stream identifier.
           getline.c libtecla.h history.c history.h man3/gl_get_line.3
           man3/gl_show_history.3
             The new gl_show_history function displays the current
             history to a given stdio output stream.

29/09/2001 mcs@astro.caltech.edu

           getline.c
             Previously new_GetLine() installed a persistent signal
             handler to be sure to catch the SIGWINCH (terminal size
             change) signal between calls to gl_get_line(). This had
             the drawback that if multiple GetLine objects were
             created, only the first GetLine object used after the
             signal was received, would see the signal and adapt to
             the new terminal size. Instead of this, a signal handler
             for sigwinch is only installed while gl_get_line() is
             running, and just after installing this handler,
             gl_get_line() checks for terminal size changes that
             might have occurred while the signal handler wasn't
             installed.
           getline.c
             Dynamically allocated copies of capability strings looked
             up in the terminfo or termcap databases are now made, so
             that calls to setupterm() etc for one GetLine object
             don't get trashed when another GetLine object calls
             setupterm() etc. It is now safe to allocate and use
             multiple GetLine objects, albeit only within a single
             thread.
           
28/09/2001 mcs@astro.caltech.edu

           version.c Makefile.rules
             I added a function for querying the version number of
             the library.

26/09/2001 mcs@astro.caltech.edu

           getline.c man3/gl_get_line.3
             I added the new gl_watch_fd() function, which allows
             applications to register callback functions to be invoked
             when activity is seen on arbitrary file descriptors while
             gl_get_line() is awaiting keyboard input from the user.

           keytab.c
             If a request is received to delete a non-existent
             binding, which happens to be an ambiguous prefix of other
             bindings no complaint is now generated about it being
             ambiguous.

23/09/2001 mcs@astro.caltech.edu

           getline.c history.c history.h man3/gl_get_line.3
           libtecla.map demo.c
             I added new public functions for saving and restoring the
             contents of the history list. The demo program now uses
             these functions to load and save history in ~/.demo_history.

23/09/2001 mcs@astro.caltech.edu

           getline.c
             On trying the demo for the first time on a KDE konsole
             terminal, I discovered that the default M-O binding
             to repeat history was hiding the arrow keys, which are
             M-OA etc. I have removed this binding. The M-o (ie the
             lower case version of this), is still bound.

18/09/2001 mcs@astro.caltech.edu

           getline.c man3/gl_get_line.3 libtecla.map
             Automatic reading of ~/.teclarc is now postponed until
             the first call to gl_get_line(), to give the application
             the chance to specify alternative configuration sources
             with the new function gl_configure_getline(). The latter
             function allows configuration to be done with a string, a
             specified application-specific file, and/or a specified
             user-specific file. I also added a read-init-files action
             function, for re-reading the configuration files, if any.
             This is by default bound to ^X^R. This is all documented
             in gl_get_line.3.

08/09/2001 mcs@astro.caltech.edu

           getline.c man3/gl_get_line.3
             It is now possible to bind actions to key-sequences
             that start with printable characters. Previously
             keysequences were required to start with meta or control
             characters. This is documented in gl_get_line.3.

           getline.c man3/gl_get_line.3
             A customized completion function can now arrange for
             gl_get_line() to return the current input line whenever a
             successful completion has been made. This is signalled by
             setting the last character of the optional continuation
             suffix to a newline character. This is documented in
             gl_get_line.3.

05/07/2001 Bug reported by Mike MacFaden, fixed by mcs

           configure.in
             There was a bug in the configure script that only
             revealed itself on systems without termcap but not
             terminfo (eg. NetBSD). I traced the bug back to a lack of
             sufficient quoting of multi-line m4 macro arguments in
             configure.in, and have now fixed this and recreated the
             configure script.

05/07/2001 Bug reported and patched by Mike MacFaden (patch modified
           by mcs to match original intentions).

           getline.c
             getline.c wouldn't compile when termcap was selected as
             the terminal information database. setupterm() was being
             passed a non-existent variable, in place of the term[]
             argument of gl_control_strings(). Also if
             gl_change_terminal() is called with term==NULL, "ansi"
             is now substituted.

02/07/2001 Version 1.3.3 released.

27/06/2001 mcs@astro.caltech.edu

           getline.c expand.c cplmatch.c
             Added checks to fprintf() statements that write to the
             terminal.
           getline.c
             Move the cursor to the end of the line before suspending,
             so that the cursor doesn't get left in the middle of the
             input line.
           Makefile.in
             On systems that don't support shared libraries, the
             distclean target of make deleted libtecla.h. This has
             now been fixed.
           getline.c
             gl_change_terminal() was being called by gl_change_editor(),
             with the unwanted side effect that raw terminal modes were
             stored as those to be restored later, if called by an
             action function. gl_change_terminal() was being called in
             this case to re-establish terminal-specific key bindings,
             so I have just split this part of the function out into
             a separate function for both gl_change_editor() and
             gl_change_terminal() to call.

12/06/2001 mcs@astro.caltech.edu

           getline.c
             Signal handling has been improved. Many more signals are
             now trapped, and instead of using a simple flag set by a
             signal handler, race conditions are avoided by blocking
             signals during most of the gl_get_line() code, and
             unblocking them via calls to sigsetjmp(), just before
             attempting to read each new character from the user.
             The matching use of siglongjmp() in the signal
             handlers ensures that signals are reblocked correctly
             before they are handled. In most cases, signals cause
             gl_get_line() to restore the terminal modes and signal
             handlers of the calling application, then resend the
             signal to the application. In the case of SIGINT, SIGHUP,
             SIGPIPE, and SIGQUIT, if the process still exists after
             the signals are resent, gl_get_line() immediately returns
             with appropriate values assigned to errno. If SIGTSTP,
             SIGTTIN or SIGTTOU signals are received, the process is
             suspended. If any other signal is received, and the
             process continues to exist after the signal is resent to
             the calling application, line input is resumed after the
             terminal is put back into raw mode, the gl_get_line()
             signal handling is restored, and the input line redrawn.
           man/gl_get_line(3)
             I added a SIGNAL HANDLING section to the gl_get_line()
             man page, describing the new signal handling features.

21/05/2001 Version 1.3.2 released.

21/05/2001 mcs@astro.caltech.edu

           getline.c
             When vi-replace-char was used to replace the character at
             the end of the line, it left the cursor one character to
             its right instead of on top of it. Now rememdied.
           getline.c
             When undoing, to properly emulate vi, the cursor is now
             left at the leftmost of the saved and current cursor
             positions.
           getline.c man3/gl_get_line.3
             Implemented find-parenthesis (%), delete-to-paren (M-d%),
             vi-change-to-paren (M-c%), copy-to-paren (M-y%).
           cplfile.c pcache.c
             In three places I was comparing the last argument of
             strncmp() to zero instead of the return value of
             strncmp().

20/05/2001 mcs@astro.caltech.edu

           getline.c man3/gl_get_line.3
             Implemented and documented the vi-repeat-change action,
             bound to the period key. This repeats the last action
             that modified the input line.

19/05/2001 mcs@astro.caltech.edu

           man3/gl_get_line.3
             I documented the new action functions and bindings
             provided by Tim Eliseo, plus the ring-bell action and
             the new "nobeep" configuration option.
           getline.c
             I modified gl_change_editor() to remove and reinstate the
             terminal settings as well as the default bindings, since
             these have editor-specific differences. I also modified
             it to not abort if a key-sequence can't be bound for some
             reason. This allows the new vi-mode and emacs-mode
             bindings to be used safely.
           getline.c
             When the line was re-displayed on receipt of a SIGWINCH
             signal, the result wasn't visible until the next
             character was typed, since a call to fflush() was needed.
             gl_redisplay_line() now calls gl_flush_output() to remedy
             this.

17/05/2001 mcs@astro.catlech.edu

           getline.c
             Under Linux, calling fflush(gl->output_fd) hangs if
             terminal output has been suspended with ^S. With the
             tecla library taking responsability for reading the stop
             and start characters this was a problem, because once
             hung in fflush(), the keyboard input loop wasn't entered,
             so the user couldn't type the start character to resume
             output.  To remedy this, I now have the terminal process
             these characters, rather than the library.

12/05/2001 mcs@astro.caltech.edu

           getline.c
             The literal-next action is now implemented as a single
             function which reads the next character itself.
             Previously it just set a flag which effected the
             interpretation of the next character read by the input
             loop.
           getline.c
             Added a ring-bell action function. This is currently
             unbound to any key by default, but it is used internally,
             and can be used by users that want to disable any of the
             default key-bindings.

12/05/2001 Tim Eliseo    (logged here by mcs)

           getline.c
             Don't reset gl->number until after calling an action
             function. By looking at whether gl->number is <0 or
             not, action functions can then tell whether the count
             that they were passed was explicitly specified by the
             user, as opposed to being defaulted to 1.
           getline.c
             In vi, the position at which input mode is entered
             acts as a barrier to backward motion for the few
             backward moving actions that are enabled in input mode.
             Tim added this barrier to getline.
           getline.c
             In gl_get_line() after reading an input line, or
             having the read aborted by a signal, the sig_atomic_t
             gl_pending_signal was being compared to zero instead
             of -1 to see if no signals had been received.
             gl_get_line() will thus have been calling raise(-1),
             which luckily didn't seem to do anything. Tim also
             arranged for errno to be set to EINTR when a signal
             aborts gl_get_line().
           getline.c
             The test in gl_add_char_to_line() for detecting
             when overwriting a character with a wider character,
             had a < where it needed a >. Overwriting with a wider
             character thus overwrote trailing characters. Tim also
             removed a redundant copy of the character into the
             line buffer.
           getline.c
             gl_cursor_left() and gl->cursor_right() were executing
             a lot of redundant code, when the existing call to the
             recently added gl_place_cursor() function, does all that
             is necessary.
           getline.c
             Remove redundant code from backward_kill_line() by
             re-implimenting in terms of gl_place_cursor() and
             gl_delete_chars().
           getline.c
             gl_forward_delete_char() now records characters in cut
             buffer when in vi command mode.
           getline.c
             In vi mode gl_backward_delete_char() now only deletes
             up to the point at which input mode was entered. Also
             gl_delete_chars() restores from the undo buffer when
             deleting in vi insert mode.
           getline.c
             Added action functions, vi-delete-goto-column,
             vi-change-to-bol, vi-change-line, emacs-mode, vi-mode,
             vi-forward-change-find, vi-backward-change-find,
             vi-forward-change-to, vi-backward-change-to,
             vi-change-goto-col, forward-delete-find, backward-delete-find,
             forward-delete-to, backward-delete-to,
             delete-refind, delete-invert-refind, forward-copy-find,
             backward-copy-find, forward-copy-to, backward-copy-to
             copy-goto-column, copy-rest-of-line, copy-to-bol, copy-line,
             history-re-search-forward, history-re-search-backward.

06/05/2001 Version 1.3.1 released.

03/05/2001 mcs@astro.caltech.edu

           configure.in
             Old versions of GNU ld don't accept version scripts.
             Under Linux I thus added a test to try out ld with
             the --version-script argument to see if it works.
             If not, version scripts aren't used.
           configure.in
             My test for versions of Solaris earlier than 7
             failed when confronted by a three figure version
             number (2.5.1). Fixed.

30/04/2001 mcs@astro.caltech.edu

           getline.c
             In vi mode, history-search-backward and
             history-search-forward weren't doing anything when
             invoked at the start of an empty line, whereas
             they should have acted like up-history and down-history.
           Makefile.in Makefile.rules
             When shared libraries are being created, the build
             procedure now arranges for any alternate library
             links to be created as well, before linking the
             demos. Without this the demos always linked to the
             static libraries (which was perfectly ok, but wasn't a
             good example).
           Makefile.in Makefile.rules
             On systems on which shared libraries were being created,
             if there were no alternate list of names, make would
             abort due to a Bourne shell 'for' statement that didn't
             have any arguments. Currently there are no systems who's
             shared library configurations would trigger this
             problem.
           Makefile.rules
             The demos now relink to take account of changes to the
             library.
           configure.in configure
             When determining whether the reentrant version of the
             library should be compiled by default, the configure
             script now attempts to compile a dummy program that
             includes all of the appropriate system headers and
             defines _POSIX_C_SOURCE. This should now be a robust test
             on systems which use C macros to alias these function
             names to other internal functions.
           configure.in
             Under Solaris 2.6 and earlier, the curses library is in
             /usr/ccs/lib. Gcc wasn't finding this. In addition to
             remedying this, I had to remove "-z text" from
             LINK_SHARED under Solaris to get it to successfully
             compile the shared library against the static curses
             library.
           configure.in
             Under Linux the -soname directive was being used
             incorrectly, citing the fully qualified name of the
             library instead of its major version alias. This will
             unfortunately mean that binaries linked with the 1.2.3
             and 1.2.4 versions of the shared library won't use
             later versions of the library unless relinked.

30/04/2001 mcs@astro.caltech.edu

           getline.c
             In gl_get_input_line(), don't redundantly copy the
             start_line if start_line == gl->line.

30/04/2001 Version 1.3.0 released.

28/04/2001 mcs@astro.caltech.edu

           configure.in
             I removed the --no-undefined directive from the Linux
             LINK_SHARED command. After recent patches to our RedHat
             7.0 systems ld started reporting some internal symbols of
             libc as being undefined.  Using nm on libc indicated that
             the offending symbols are indeed defined, albeit as
             "common" symbols, so there appears to be a bug in
             RedHat's ld. Removing this flag allows the tecla shared
             library to compile, and programs appear to function fine.
           man3/gl_get_line.3
             The default key-sequence used to invoke the
             read-from-file action was incorrectly cited as ^Xi
             instead of ^X^F.

26/04/2001 mcs@astro.caltech.edu

           getline.c man3/gl_get_line.3
             A new vi-style editing mode was added. This involved
             adding many new action functions, adding support for
             specifying editing modes in users' ~/.teclarc files,
             writing a higher level cursor motion function to support
             the different line-end bounds required in vi command
             mode, and a few small changes to support the fact that vi
             has two modes, input mode and command mode with different
             bindings.

             When vi editing mode is enabled, any binding that starts
             with an escape or a meta character, is interpreted as a
             command-mode binding, and switches the library to vi
             command mode if not already in that mode. Once in command
             mode the first character of all keysequences entered
             until input mode is re-enabled, are quietly coerced to
             meta characters before being looked up in the key-binding
             table. So, for example, in the key-binding table, the
             standard vi command-mode 'w' key, which moves the cursor
             one word to the right, is represented by M-w. This
             emulates vi's dual sets of bindings in a natural way
             without needing large changes to the library, or new
             binding syntaxes. Since cursor keys normally emit
             keysequences which start with escape, it also does
             something sensible when a cursor key is pressed during
             input mode (unlike true vi, which gets upset).

             I also added a ^Xg binding for the new list-glob action
             to both the emacs and vi key-binding tables. This lists
             the files that match the wild-card expression that
             precedes it on the command line.

             The function that reads in ~/.teclarc used to tell
             new_GetLine() to abort if it encountered anything that it
             didn't understand in this file. It now just reports an
             error and continues onto the next line.
           Makefile.in:
             When passing LIBS=$(LIBS) to recursive invokations of
             make, quotes weren't included around the $(LIBS) part.
             This would cause problems if LIBS ever contained more
             than one word (with the supplied configure script this
             doesn't happen currently). I added these quotes.
           expand.c man3/ef_expand_file.3:
             I wrote a new public function called ef_list_expansions(),
             to list the matching filenames returned by
             ef_expand_file().

             I also fixed the example in the man page, which cited
             exp->file instead of exp->files, and changed the
             dangerous name 'exp' with 'expn'.
           keytab.c:
             Key-binding tables start with 100 elements, and are
             supposedly incremented in size by 100 elements whenever
             the a table runs out of space. The realloc arguments to
             do this were wrong. This would have caused problems if
             anybody added a lot of personal bindings in their
             ~/.teclarc file. I only noticed it because the number of
             key bindings needed by the new vi mode exceeded this
             number.
           libtecla.map
             ef_expand_file() is now reported as having been added in
             the upcoming 1.3.0 release.

25/03/2001 Markus Gyger  (logged here by mcs)

           Makefile.in:
             Make symbolic links to alternative shared library names
             relative instead of absolute.
           Makefile.rules:
             The HP-UX libtecla.map.opt file should be made in the
             compilation directory, to allow the source code directory
             to be on a readonly filesystem.
           cplmatch.c demo2.c history.c pcache.c
             To allow the library to be compiled with a C++ compiler,
             without generating warnings, a few casts were added where
             void* return values were being assigned directly to
             none void* pointer variables.

25/03/2001 mcs@astro.caltech.edu

           libtecla.map:
             Added comment header to explain the purpose of the file.
             Also added cpl_init_FileArgs to the list of exported
             symbols. This symbol is deprecated, and no longer
             documented, but for backwards compatibility, it should
             still be exported.
           configure:
             I had forgotten to run autoconf before releasing version
             1.2.4, so I have just belatedly done so.  This enables
             Markus' changes to "configure.in" documented previously,
             (see 17/03/2001).

20/03/2001 John Levon   (logged here by mcs)

           libtecla.h
             A couple of the function prototypes in libtecla.h have
             (FILE *) argument declarations, which means that stdio.h
             needs to be included. The header file should be self
             contained, so libtecla.h now includes stdio.h.

18/03/2001 Version 1.2.4 released.

           README html/index.html configure.in
             Incremented minor version from 3 to 4.

18/03/2001 mcs@astro.caltech.edu

           getline.c
             The fix for the end-of-line problem that I released a
             couple of weeks ago, only worked for the first line,
             because I was handling this case when the cursor position
             was equal to the last column, rather than when the cursor
             position modulo ncolumn was zero.
           Makefile.in Makefile.rules
             The demos are now made by default, their rules now being
             int Makefile.rules instead of Makefile.in.
           INSTALL
             I documented how to compile the library in a different
             directory than the distribution directory.
             I also documented features designed to facilitate
             configuring and building the library as part of another
             package.

17/03/2001 Markus Gyger (logged here by mcs)

           getline.c
             Until now cursor motions were done one at a time. Markus
             has added code to make use the of the terminfo capability
             that moves the cursor by more than one position at a
             time. This greatly improves performance when editing near
             the start of long lines.
           getline.c
             To further improve performance, Markus switched from
             writing one character at a time to the terminal, using
             the write() system call, to using C buffered output
             streams. The output buffer is only flushed when
             necessary.
           Makefile.rules Makefile.in configure.in
             Added support for compiling for different architectures
             in different directories. Simply create another directory
             and run the configure script located in the original
             directory.
           Makefile.in configure.in libtecla.map
             Under Solaris, Linux and HP-UX, symbols that are to be
             exported by tecla shared libraries are explicitly specified
             via symbol map files. Only publicly documented functions
             are thus visible to applications.
           configure.in
             When linking shared libraries under Solaris SPARC,
             registers that are reserved for applications are marked
             as off limits to the library, using -xregs=no%appl when
             compiling with Sun cc, or -mno-app-regs when compiling
             with gcc. Also removed -z redlocsym for Solaris, which
             caused problems under some releases of ld.
           homedir.c  (after minor changes by mcs)
             Under ksh, ~+ expands to the current value of the ksh
             PWD environment variable, which contains the path of
             the current working directory, including any symbolic
             links that were traversed to get there. The special
             username "+" is now treated equally by tecla, except
             that it substitutes the return value of getcwd() if PWD
             either isn't set, or if it points at a different
             directory than that reported by getcwd().

08/03/2001 Version 1.2.3 released.

08/03/2001 mcs@astro.caltech.edu

           getline.c
             On compiling the library under HP-UX for the first time
             I encountered and fixed a couple of bugs:

             1. On all systems except Solaris, the callback function
                required by tputs() takes an int argument for the
                character that is to be printed. Under Solaris it
                takes a char argument. The callback function was
                passing this argument, regardless of type, to write(),
                which wrote the first byte of the argument.  This was
                fine under Solaris and under little-endian systems,
                because the first byte contained the character to be
                written, but on big-endian systems, it always wrote
                the zero byte at the other end of the word. As a
                result, no control characters were being written to
                the terminal.
             2. While attempting to start a newline after the user hit
                enter, the library was outputting the control sequence
                for moving the cursor down, instead of the newline
                character. On many systems the control sequence for
                moving the cursor down happends to be a newline
                character, but under HP-UX it isn't. The result was
                that no new line was being started under HP-UX.

04/03/2001 mcs@astro.caltech.edu

           configure.in Makefile.in Makefile.stub configure config.guess
           config.sub Makefile.rules install-sh PORTING README INSTALL
             Configuration and compilation of the library is now
             performed with the help of an autoconf configure
             script. In addition to relieving the user of the need to
             edit the Makefile, this also allows automatic compilation
             of the reentrant version of the library on platforms that
             can handle it, along with the creation of shared
             libraries where configured. On systems that aren't known
             to the configure script, just the static tecla library is
             compiled. This is currently the case on all systems
             except Linux, Solaris and HP-UX. In the hope that
             installers will provide specific conigurations for other
             systems, the configure.in script is heavily commented,
             and instructions on how to use are included in a new
             PORTING file.

24/02/2001 Version 1.2b released.

22/02/2001 mcs@astro.caltech.edu

           getline.c
             It turns out that most terminals, but not all, on writing
             a character in the rightmost column, don't wrap the
             cursor onto the next line until the next character is
             output. This library wasn't aware of this and thus if one
             tried to reposition the cursor from the last column,
             gl_get_line() thought that it was moving relative to a
             point on the next line, and thus moved the cursor up a
             line. The fix was to write one extra character when in
             the last column to force the cursor onto the next line,
             then backup the cursor to the start of the new line.
           getline.c
             On terminal initialization, the dynamic LINES and COLUMNS
             environment variables were ignored unless
             terminfo/termcap didn't return sensible dimensions. In
             practice, when present they should override the static
             versions in the terminfo/termcap databases. This is the
             new behavior. In reality this probably won't have caused
             many problems, because a SIGWINCH signal which informs of
             terminal size changes is sent when the terminal is
             opened, so the dimensions established during
             initialization quickly get updated on most systems.

18/02/2001 Version 1.2a released.

18/02/2001 mcs@astro.caltech.edu

           getline.c
             Three months ago I moved the point at which termios.h
             was included in getline.c. Unfortunately, I didn't notice
             that this moved it to after the test for TIOCGWINSZ being
             defined. This resulted in SIGWINCH signals not being
             trapped for, and thus terminal size changes went
             unnoticed. I have now moved the test to after the 
             inclusion of termios.h.

12/02/2001 Markus Gyger     (described here by mcs)

           man3/pca_lookup_file.3 man3/gl_get_line.3
           man3/ef_expand_file.3 man3/cpl_complete_word.3
             In the 1.2 release of the library, all functions in the
             library were given man pages. Most of these simply
             include one of the above 4 man pages, which describe the
             functions while describing the modules that they are in.
             Markus added all of these function names to the lists in
             the "NAME" headers of the respective man pages.
             Previously only the primary function of each module was
             named there.

11/02/2001 mcs@astro.caltech.edu

           getline.c
             On entering a line that wrapped over two or more
             terminal, if the user pressed enter when the cursor
             wasn't on the last of the wrapped lines, the text of the
             wrapped lines that followed it got mixed up with the next
             line written by the application, or the next input
             line. Somehow this slipped through the cracks and wasn't
             noticed until now. Anyway, it is fixed now.

09/02/2001 Version 1.2 released.

04/02/2001 mcs@astro.caltech.edu

           pcache.c libtecla.h
             With all filesystems local, demo2 was very fast to start
             up, but on a Sun system with one of the target
             directories being on a remote nfs mounted filesystem, the
             startup time was many seconds. This was due to the
             executable selection callback being applied to all files
             in the path at startup. To avoid this, all files are now
             included in the cache, and the application specified
             file-selection callback is only called on files as they
             are matched. Whether the callback rejected or accepted
             them is then cached so that the next time an already
             checked file is looked at, the callback doesn't have to
             be called. As a result, startup is now fast on all
             systems, and since usually there are only a few matching
             file completions at a time, the delay during completion
             is also usually small. The only exception is if the user
             tries to complete an empty string, at which point all
             files have to be checked. Having done this once, however,
             doing it again is fast.
           man3/pca_lookup_file.3
             I added a man page documenting the new PathCache module.
           man3/<many-new-files>.3
             I have added man pages for all of the functions in each
             of the modules. These 1-line pages use the .so directive
             to redirect nroff to the man page of the parent module.
           man Makefile update_html
             I renamed man to man3 to make it easier to test man page
             rediction, and updated Makefile and update_html
             accordingly. I also instructed update_html to ignore
             1-line man pages when making html equivalents of the man
             pages.
           cplmatch.c
             In cpl_list_completions() the size_t return value of
             strlen() was being used as the length argument of a "%*s"
             printf directive. This ought to be an int, so the return
             value of strlen() is now cast to int. This would have
             caused problems on architectures where the size of a
             size_t is not equal to the size of an int.

02/02/2001 mcs@astro.caltech.edu

           getline.c
             Under UNIX, certain terminal bindings are set using the
             stty command. This, for example, specifies which control
             key generates a user-interrupt (usually ^C or ^Y). What I
             hadn't realized was that ASCII NUL is used as the way to
             specify that one of these bindings is unset. I have now
             modified the code to skip unset bindings, leaving the
             corresponding action bound to the built-in default, or a
             user provided binding.

28/01/2001 mcs@astro.caltech.edu

           pcache.c libtecla.h
             A new module was added which supports searching for files
             in any colon separated list of directories, such as the
             unix execution PATH environment variable. Files in these
             directories, after being individually okayed for
             inclusion via an application provided callback, are
             cached in a PathCache object. You can then look up the
             full pathname of a given filename, or you can use the
             provided completion callback to list possible completions
             in the path-list. The contents of relative directories,
             such as ".", obviously can't be cached, so these
             directories are read on the fly during lookups and
             completions. The obvious application of this facility is
             to provide Tab-completion of commands, and thus a
             callback to place executable files in the cache, is
             provided.
           demo2.c
             This new program demonstrates the new PathCache
             module. It reads and processes lines of input until the
             word 'exit' is entered, or C-d is pressed. The default
             tab-completion callback is replaced with one which at the
             start of a line, looks up completions of commands in the
             user's execution path, and when invoked in other parts of
             the line, reverts to normal filename completion. Whenever
             a new line is entered, it extracts the first word on the
             line, looks it up in the user's execution path to see if
             it corresponds to a known command file, and if so,
             displays the full pathname of the file, along with the
             remaining arguments.
           cplfile.c
             I added an optional pair of callback function/data
             members to the new cpl_file_completions() configuration
             structure. Where provided, this callback is asked
             on a file-by-file basis, which files should be included
             in the list of file completions. For example, a callback
             is provided for listing only completions of executable
             files.
           cplmatch.c
             When listing completions, the length of the type suffix
             of each completion wasn't being taken into account
             correctly when computing the column widths. Thus the
             listing appeared ragged sometimes. This is now fixed.
           pathutil.c
             I added a function for prepending a string to a path,
             and another for testing whether a pathname referred to
             an executable file.

28/01/2001 mcs@astro.caltech.edu

           libtecla.h cplmatch.c man/cpl_complete_word.3
             The use of a publically defined structure to configure
             the cpl_file_completions() callback was flawed, so a new
             approach has been designed, and the old method, albeit
             still supported, is no longer documented in the man
             pages. The definition of the CplFileArgs structure in
             libtecla.h is now accompanied by comments warning people
             not to modify it, since modifications could break
             applications linked to shared versions of the tecla
             library. The new method involves an opaque CplFileConf
             object, instances of which are returned by a provided
             constructor function, configured with provided accessor
             functions, and when no longer needed, deleted with a
             provided destructor function. This is documented in the
             cpl_complete_word man page. The cpl_file_completions()
             callback distinguishes what type of configuration
             structure it has been sent by virtue of a code placed at
             the beginning of the CplFileConf argument by its
             constructor.

04/01/2001 mcs@astro.caltech.edu (Release of version 1.1j)

           getline.c
             I added upper-case bindings for the default meta-letter
             keysequences such as M-b. They thus continue to work
             when the user has caps-lock on.
           Makefile
             I re-implemented the "install" target in terms of new
             install_lib, install_inc and install_man targets. When
             distributing the library with other packages, these new
             targets allows for finer grained control of the
             installation process.

30/12/2000 mcs@astro.caltech.edu

           getline.c man/gl_get_line.3
             I realized that the recall-history action that I
             implemented wasn't what Markus had asked me for. What he
             actually wanted was for down-history to continue going
             forwards through a previous history recall session if no
             history recall session had been started while entering
             the current line. I have thus removed the recall-history
             action and modified the down-history action function
             accordingly.

24/12/2000 mcs@astro.caltech.edu

           getline.c
             I modified gl_get_line() to allow the previously returned
             line to be passed in the start_line argument.
           getline.c man/gl_get_line.3
             I added a recall-history action function, bound to M^P.
             This recalls the last recalled history line, regardless
             of whether it was from the current or previous line.

13/12/2000 mcs@astro.caltech.edu (Release of version 1.1i)

           getline.c history.h history.c man/gl_get_line.3
             I implemented the equivalent of the ksh Operate action. I
             have named the tecla equivalent "repeat-history". This
             causes the line that is to be edited to returned, and
             arranges for the next most recent history line to be
             preloaded on the next call to gl_get_line(). Repeated
             invocations of this action thus result in successive
             history lines being repeated - hence the
             name. Implementing the ksh Operate action was suggested
             by Markus Gyger. In ksh it is bound to ^O, but since ^O
             is traditionally bound by the default terminal settings,
             to stop-output, I have bound the tecla equivalent to M-o.

01/12/2000 mcs@astro.caltech.edu (Release of version 1.1h)

           getline.c keytab.c keytab.h man/gl_get_line.3
             I added a digit-argument action, to allow repeat
             counts for actions to be entered. As in both tcsh
             and readline, this is bound by default to each of
             M-0, M-1 through to M-9, the number being appended
             to the current repeat count. Once one of these has been
             pressed, the subsequent digits of the repeat count can be
             typed with or without the meta key pressed. It is also
             possible to bind digit-argument to other keys, with or
             without a numeric final keystroke. See man page for
             details.

           getline.c man/gl_get_line.3
             Markus noted that my choice of M-< for the default
             binding of read-from-file, could be confusing, since
             readline binds this to beginning-of-history. I have
             thus rebound it to ^X^F (ie. like find-file in emacs).

           getline.c history.c history.h man/gl_get_line.3
             I have now implemented equivalents of the readline
             beginning-of-history and end-of-history actions.
             These are bound to M-< and M-> respectively.

           history.c history.h
             I Moved the definition of the GlHistory type, and
             its subordinate types from history.h to history.c.
             There is no good reason for any other module to
             have access to the innards of this structure.

27/11/2000 mcs@astro.caltech.edu (Release of version 1.1g)

           getline.c man/gl_get_line.3
             I added a "read-from-file" action function and bound it
             by default to M-<. This causes gl_get_line() to
             temporarily return input from the file who's name
             precedes the cursor.
             
26/11/2000 mcs@astro.caltech.edu

           getline.c keytab.c keytab.h man/gl_get_line.3
             I have reworked some of the keybinding code again.

             Now, within key binding strings, in addition to the
             previously existing notation, you can now use M-a to
             denote meta-a, and C-a to denote control-a. For example,
             a key binding which triggers when the user presses the
             meta key, the control key and the letter [
             simultaneously, can now be denoted by M-C-[, or M-^[ or
             \EC-[ or \E^[.

             I also updated the man page to use M- instead of \E in
             the list of default bindings, since this looks cleaner.

           getline.c man/gl_get_line.3
             I added a copy-region-as-kill action function and
             gave it a default binding to M-w.

22/11/2000 mcs@astro.caltech.edu

           *.c
             Markus Gyger sent me a copy of a previous version of
             the library, with const qualifiers added in appropriate
             places. I have done the same for the latest version.
             Among other things, this gets rid of the warnings
             that are generated if one tells the compiler to
             const qualify literal strings.

           getline.c getline.h glconf.c
             I have moved the contents of glconf.c and the declaration
             of the GetLine structure into getline.c. This is cleaner,
             since now only functions in getline.c can mess with the
             innards of GetLine objects. It also clears up some problems
             with system header inclusion order under Solaris, and also
             the possibility that this might result in inconsistent
             system macro definitions, which in turn could cause different
             declarations of the structure to be seen in different files.

           hash.c
             I wrote a wrapper function to go around strcmp(), such that
             when hash.c is compiled with a C++ compiler, the pointer
             to the wrapper function is a C++ function pointer.
             This makes it compatible with comparison function pointer
             recorded in the hash table.

           cplmatch.c getline.c libtecla.h
             Markus noted that the Sun C++ compiler wasn't able to
             match up the declaration of cpl_complete_word() in
             libtecla.h, where it is surrounded by a extern "C" {}
             wrapper, with the definition of this function in
             cplmatch.c. My suspicion is that the compiler looks not
             only at the function name, but also at the function
             arguments to see if two functions match, and that the
             match_fn() argument, being a fully blown function pointer
             declaration, got interpetted as that of a C function in
             one case, and a C++ function in the other, thus
             preventing a match.

             To fix this I now define a CplMatchFn typedef in libtecla.h,
             and use this to declare the match_fn callback.

20/11/2000 (Changes suggested by Markus Gyger to support C++ compilers):
           expand.c
             Renamed a variable called "explicit" to "xplicit", to
             avoid conflicts when compiling with C++ compilers.
           *.c
             Added explicit casts when converting from (void *) to
             other pointer types. This isn't needed in C but it is
             in C++.
           getline.c
             tputs() has a strange declaration under Solaris. I was
             enabling this declaration when the SPARC feature-test
             macro was set. Markus changed the test to hinge on the
             __sun and __SVR4 macros.
           direader.c glconf.c stringrp.c
             I had omitted to include string.h in these two files.

           Markus also suggested some other changes, which are still
           under discussion. With the just above changes however, the
           library compiles without complaint using g++.

19/11/2000 mcs@astro.caltech.edu
           getline.h getline.c keytab.c keytab.h glconf.c
           man/gl_get_line.3
             I added support for backslash escapes (include \e
             for the keyboard escape key) and literal binary
             characters to the characters allowed within key sequences
             of key bindings.

           getline.h getline.c keytab.c keytab.h glconf.c
           man/gl_get_line.3
             I introduced symbolic names for the arrow keys, and
             modified the library to use the cursor key sequences
             reported by terminfo/termcap in addition to the default
             ANSI ones. Anything bound to the symbolically named arrow
             keys also gets bound to the default and terminfo/termcap
             cursor key sequences. Note that under Solaris
             terminfo/termcap report the properties of hardware X
             terminals when TERM is xterm instead of the terminal
             emulator properties, and the cursor keys on these two
             systems generate different key sequences. This is an
             example of why extra default sequences are needed.

           getline.h getline.c keytab.c
             For some reason I was using \e to represent the escape
             character. This is supported by gcc, which thus doesn't
             emit a warning except with the -pedantic flag, but isn't
             part of standard C. I now use a macro to define escape
             as \033 in getline.h, and this is now used wherever the
             escape character is needed.

17/11/2000 mcs@astro.caltech.edu (Release of version 1.1d)

           getline.c, man/gl_get_line(3), html/gl_get_line.html
             In tcsh ^D is bound to a function which does different
             things depending on where the cursor is within the input
             line. I have implemented its equivalent in the tecla
             library. When invoked at the end of the line this action
             function displays possible completions. When invoked on
             an empty line it causes gl_get_line() to return NULL,
             thus signalling end of input. When invoked within a line
             it invokes forward-delete-char, as before. The new action
             function is called del-char-or-list-or-eof.

           getline.c, man/gl_get_line(3), html/gl_get_line.html
             I found that the complete-word and expand-file actions
             had underscores in their names instead of hyphens. This
             made them different from all other action functions, so I
             have changed the underscores to hyphens.

           homedir.c
             On SCO UnixWare while getpwuid_r() is available, the
             associated _SC_GETPW_R_SIZE_MAX macro used by sysconf()
             to find out how big to make the buffer to pass to this
             function to cater for any password entry, doesn't
             exist. I also hadn't catered for the case where sysconf()
             reports that this limit is indeterminate. I have thus
             change the code to substitute a default limit of 1024 if
             either the above macro isn't defined or if sysconf() says
             that the associated limit is indeterminate.
           
17/11/2000 mcs@astro.caltech.edu (Release of version 1.1c)

           getline.c, getline.h, history.c, history.h
             I have modified the way that the history recall functions
             operate, to make them better emulate the behavior of
             tcsh. Previously the history search bindings always
             searched for the prefix that preceded the cursor, then
             left the cursor at the same point in the line, so that a
             following search would search using the same prefix. This
             isn't how tcsh operates. On finding a matching line, tcsh
             puts the cursor at the end of the line, but arranges for
             the followup search to continue with the same prefix,
             unless the user does any cursor motion or character
             insertion operations in between, in which case it changes
             the search prefix to the new set of characters that are
             before the cursor. There are other complications as well,
             which I have attempted to emulate. As far as I can
             tell, the tecla history recall facilities now fully
             emulate those of tcsh.

16/11/2000 mcs@astro.caltech.edu (Release of version 1.1b)

           demo.c:
             One can now quit from the demo by typing exit.

           keytab.c:
             The first entry of the table was getting deleted
             by _kt_clear_bindings() regardless of the source
             of the binding. This deleted the up-arrow binding.
             Symptoms noted by gazelle@yin.interaccess.com.

           getline.h:
             Depending on which system include files were include
             before the inclusion of getline.h, SIGWINCH and
             TIOCGWINSZ might or might not be defined. This resulted
             in different definitions of the GetLine object in
             different files, and thus some very strange bugs! I have
             now added #includes for the necessary system header files
             in getline.h itself. The symptom was that on creating a
             ~/.teclarc file, the demo program complained of a NULL
             argument to kt_set_keybinding() for the first line of the
             file.

15/11/2000 mcs@astro.caltech.edu (Release of version 1.1a)

           demo.c:
             I had neglected to check the return value of
             new_GetLine() in the demo program. Oops.

           getline.c libtecla.h:
             I wrote gl_change_terminal(). This allows one to change to
             a different terminal or I/O stream, by specifying the
             stdio streams to use for input and output, along with the
             type of terminal that they are connected to.

           getline.c libtecla.h:
             Renamed GetLine::isterm to GetLine::is_term. Standard
             C reserves names that start with "is" followed by
             alphanumeric characters, so this avoids potential
             clashes in the future.

           keytab.c keytab.h
             Each key-sequence can now have different binding
             functions from different sources, with the user provided
             binding having the highest precedence, followed by the
             default binding, followed by any terminal specific
             binding. This allows gl_change_terminal() to redefine the
             terminal-specific bindings each time that
             gl_change_terminal() is called, without overwriting the
             user specified or default bindings. In the future, it will
             also allow for reconfiguration of user specified
             bindings after the call to new_GetLine(). Ie. deleting a
             user specified binding should reinstate any default or
             terminal specific binding.

           man/cpl_complete_word.3 html/cpl_complete_word.html
           man/ef_expand_file.3    html/ef_expand_file.html
           man/gl_get_line.3       html/gl_get_line.html
             I added sections on thread safety to the man pages of the
             individual modules.

           man/gl_get_line.3       html/gl_get_line.html
             I documented the new gl_change_terminal() function.

           man/gl_get_line.3       html/gl_get_line.html
             In the description of the ~/.teclarc configuration file,
             I had omitted the 'bind' command word in the example
             entry. I have now remedied this.
./libtecla/html/pca_lookup_file.html0100644000076400007640000003413410141252547016124 0ustar mcsmcs Manual Page
pca_lookup_file                  pca_lookup_file



NAME

       pca_lookup_file,    del_PathCache,    del_PcaPathConf,   new_PathCache,
       new_PcaPathConf, pca_last_error,  pca_path_completions,  pca_scan_path,
       pca_set_check_fn,  ppc_file_start,  ppc_literal_escapes - lookup a file
       in a list of directories

SYNOPSIS

       #include <libtecla.h>

       PathCache *new_PathCache(void);

       PathCache *del_PathCache(PathCache *pc);

       int pca_scan_path(PathCache *pc, const char *path);

       void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn,
                             void *data);

       char *pca_lookup_file(PathCache *pc, const char *name,
                             int name_len, int literal);

       const char *pca_last_error(PathCache *pc);

       CPL_MATCH_FN(pca_path_completions);



DESCRIPTION

       The PathCache object is part of  the  tecla  library  (see  the  libte-
       cla(@LIBR_MANEXT@) man page).

       PathCache objects allow an application to search for files in any colon
       separated list of directories, such as the unix execution PATH environ-
       ment  variable. Files in absolute directories are cached in a PathCache
       object, whereas relative directories are scanned  as  needed.  Using  a
       PathCache  object,  you can look up the full pathname of a simple file-
       name, or you can obtain a list of the possible completions of  a  given
       filename  prefix.  By  default all files in the list of directories are
       targets for lookup and completion, but a versatile  mechanism  is  pro-
       vided  for only selecting specific types of files. The obvious applica-
       tion of this facility is to provide Tab-completion and lookup  of  exe-
       cutable  commands  in  the  unix  PATH,  so  an optional callback which
       rejects all but executable files, is provided.


AN EXAMPLE

       Under UNIX, the following example program looks  up  and  displays  the
       full pathnames of each of the command names on the command line.

         #include <stdio.h>
         #include <stdlib.h>
         #include <libtecla.h>

         int main(int argc, char *argv[])
         {
           int i;
         /*
          * Create a cache for executable files.
          */
           PathCache *pc = new_PathCache();
           if(!pc)
             exit(1);
         /*
          * Scan the user's PATH for executables.
          */
           if(pca_scan_path(pc, getenv("PATH"))) {
             fprintf(stderr, "%s\n", pca_last_error(pc));
             exit(1);
           }
         /*
          * Arrange to only report executable files.
          */
          pca_set_check_fn(pc, cpl_check_exe, NULL);
         /*
          * Lookup and display the full pathname of each of the
          * commands listed on the command line.
          */
           for(i=1; i<argc; i++) {
             char *cmd = pca_lookup_file(pc, argv[i], -1, 0);
             printf("The full pathname of '%s' is %s\n", argv[i],
                    cmd ? cmd : "unknown");
           }
           pc = del_PathCache(pc);  /* Clean up */
           return 0;
         }

       The following is an example of what this does on my laptop under linux:

         $ ./example less more blob
         The full pathname of 'less' is /usr/bin/less
         The full pathname of 'more' is /bin/more
         The full pathname of 'blob' is unknown
         $


FUNCTION DESCRIPTIONS

       In order to use the facilities of this module, you must first  allocate
       a PathCache object by calling the new_PathCache() constructor function.

         PathCache *new_PathCache(void)

       This function creates the resources needed to cache and lookup files in
       a list of directories. It returns NULL on error.


POPULATING THE CACHE

       Once you have created a cache, it needs to be populated with files.  To
       do this, call the pca_scan_path() function.

         int pca_scan_path(PathCache *pc, const char *path);

       Whenever this function is called, it discards the current  contents  of
       the  cache,  then  scans  the list of directories specified in its path
       argument for files. The path argument must be  a  string  containing  a
       colon-separated       list       of      directories,      such      as
       "/usr/bin:/home/mcs/bin:.". This can include directories  specified  by
       absolute pathnames such as "/usr/bin", as well as sub-directories spec-
       ified by relative pathnames such as "." or "bin". Files in the absolute
       directories  are  immediately cached in the specified PathCache object,
       whereas sub-directories, whose identities obviously change whenever the
       current  working  directory is changed, are marked to be scanned on the
       fly whenever a file is looked up.

       On success this function return  0.  On  error  it  returns  1,  and  a
       description of the error can be obtained by calling pca_last_error(pc).


LOOKING UP FILES

       Once the cache has been populated with files, you can look up the  full
       pathname   of   a   file,   simply   by   specifying  its  filename  to
       pca_lookup_file().

         char *pca_lookup_file(PathCache *pc, const char *name,
                               int name_len, int literal);

       To make it possible to pass this function a filename which is  actually
       part  of  a longer string, the name_len argument can be used to specify
       the length of the filename at the start of the name[] argument. If  you
       pass  -1  for  this length, the length of the string will be determined
       with strlen(). If the name[]  string  might  contain  backslashes  that
       escape  the  special  meanings  of spaces and tabs within the filename,
       give the literal argument,  the  value  0.  Otherwise,  if  backslashes
       should  be  treated  as  normal characters, pass 1 for the value of the
       literal argument.


FILENAME COMPLETION

       Looking up the potential completions of a filename-prefix in the  file-
       name  cache, is achieved by passing the provided pca_path_completions()
       callback function to the cpl_complete_word() function (see the cpl_com-
       plete_word(@FUNC_MANEXT@) man page).

         CPL_MATCH_FN(pca_path_completions);

       This  callback  requires that its data argument be a pointer to a PcaP-
       athConf object. Configuration objects of this  type  are  allocated  by
       calling new_PcaPathConf().

         PcaPathConf *new_PcaPathConf(PathCache *pc);

       This  function returns an object initialized with default configuration
       parameters, which determine  how  the  cpl_path_completions()  callback
       function  behaves. The functions which allow you to individually change
       these parameters are discussed below.

       By default, the pca_path_completions() callback function searches back-
       wards  for  the  start of the filename being completed, looking for the
       first un-escaped space or the start of the input line. If you  wish  to
       specify  a  different location, call ppc_file_start() with the index at
       which the filename starts in the input line. Passing start_index=-1 re-
       enables the default behavior.

         void ppc_file_start(PcaPathConf *ppc, int start_index);

       By  default,  when  pca_path_completions()  looks  at a filename in the
       input line, each lone backslash in the input  line  is  interpreted  as
       being a special character which removes any special significance of the
       character which follows it, such as a space which should  be  taken  as
       part  of the filename rather than delimiting the start of the filename.
       These backslashes are thus ignored while looking for  completions,  and
       subsequently  added  before spaces, tabs and literal backslashes in the
       list of completions. To have unescaped backslashes  treated  as  normal
       characters,  call  ppc_literal_escapes()  with  a non-zero value in its
       literal argument.

         void ppc_literal_escapes(PcaPathConf *ppc, int literal);

       When you have finished with a PcaPathConf variable, you can pass it  to
       the del_PcaPathConf() destructor function to reclaim its memory.

         PcaPathConf *del_PcaPathConf(PcaPathConf *ppc);



BEING SELECTIVE

       If  you  are  only  interested  in certain types or files, such as, for
       example, executable files, or files whose names  end  in  a  particular
       suffix, you can arrange for the file completion and lookup functions to
       be selective in the filenames that they return.  This is done by regis-
       tering  a  callback  function  with  your PathCache object. Thereafter,
       whenever a filename is found which  either  matches  a  filename  being
       looked  up, or matches a prefix which is being completed, your callback
       function will be called with the full pathname of the  file,  plus  any
       application-specific data that you provide, and if the callback returns
       1 the filename will be reported as a match, and if  it  returns  0,  it
       will  be  ignored.   Suitable  callback  functions and their prototypes
       should be declared with the following macro. The CplCheckFn typedef  is
       also provided in case you wish to declare pointers to such functions.

         #define CPL_CHECK_FN(fn) int (fn)(void *data, \
                                           const char *pathname)
         typedef CPL_CHECK_FN(CplCheckFn);

       Registering    one    of   these   functions   involves   calling   the
       pca_set_check_fn() function. In  addition  to  the  callback  function,
       passed  via  the  check_fn argument, you can pass a pointer to anything
       via the data argument. This pointer will be passed on to your  callback
       function,  via  its  own  data argument, whenever it is called, so this
       provides a way to pass appplication specific data to your callback.

         void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn,
                               void *data);

       Note that these callbacks are passed the full pathname of each matching
       file,  so the decision about whether a file is of interest can be based
       on any property of the file, not just its filename. As an example,  the
       provided cpl_check_exe() callback function looks at the executable per-
       missions of the file and the permissions of its parent directories, and
       only  returns  1  if  the user has execute permission to the file. This
       callback function can thus be used to lookup or complete command  names
       found  in  the  directories listed in the user's PATH environment vari-
       able. The example program given earlier in this  man  page  provides  a
       demonstration of this.

       Beware  that  if somebody tries to complete an empty string, your call-
       back will get called once for every file in the cache, which could num-
       ber  in  the  thousands. If your callback does anything time consuming,
       this could result in an unacceptable delay for the user,  so  callbacks
       should be kept short.

       To  improve performance, whenever one of these callbacks is called, the
       choice that it makes is cached, and the  next  time  the  corresponding
       file  is  looked  up, instead of calling the callback again, the cached
       record of whether it was accepted or rejected is used. Thus if somebody
       tries  to  complete  an  empty  string, and hits tab a second time when
       nothing appears to happen, there will only be one long delay, since the
       second  pass  will operate entirely from the cached dispositions of the
       files. These cached dipositions are discarded whenever  pca_scan_path()
       is called, and whenever pca_set_check_fn() is called with changed call-
       back function or data arguments.


ERROR HANDLING

       If pca_scan_path() reports that an error occurred by returning  1,  you
       can   obtain   a   terse   description   of   the   error   by  calling
       pca_last_error(pc). This returns an internal string containing an error
       message.

         const char *pca_last_error(PathCache *pc);



CLEANING UP

       Once  you  have  finished using a PathCache object, you can reclaim its
       resources by passing it to  the  del_PathCache()  destructor  function.
       This  takes a pointer to one of these objects, and always returns NULL.

         PathCache *del_PathCache(PathCache *pc);


THREAD SAFETY

       In multi-threaded programs, you should use the libtecla_r.a version  of
       the library. This uses POSIX reentrant functions where available (hence
       the _r suffix), and disables features that rely on non-reentrant system
       functions.  In  the  case  of this module, the only disabled feature is
       username completion  in  ~username/  expressions,  in  cpl_path_comple-
       tions().

       Using  the  libtecla_r.a  version of the library, it is safe to use the
       facilities of this module  in  multiple  threads,  provided  that  each
       thread uses a separately allocated PathCache object. In other words, if
       two threads want to do path searching, they should each call  new_Path-
       Cache() to allocate their own caches.


FILES

       libtecla.a    -    The tecla library
       libtecla.h    -    The tecla header file.


SEE ALSO

       libtecla, gl_get_line, ef_expand_file,
       cpl_complete_word


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                pca_lookup_file
./libtecla/html/ef_expand_file.html0100644000076400007640000002333610141252545015721 0ustar mcsmcs Manual Page
ef_expand_file                    ef_expand_file



NAME

       ef_expand_file,   del_ExpandFile,   ef_last_error,  ef_list_expansions,
       new_ExpandFile - expand filenames containing ~user/$envvar and wildcard
       expressions

SYNOPSIS

       #include <libtecla.h>

       ExpandFile *new_ExpandFile(void);

       ExpandFile *del_ExpandFile(ExpandFile *ef);

       FileExpansion *ef_expand_file(ExpandFile *ef,
                                     const char *path,
                                     int pathlen);

       int ef_list_expansions(FileExpansion *result, FILE *fp,
                              int term_width);

       const char *ef_last_error(ExpandFile *ef);


DESCRIPTION

       The  ef_expand_file()  function  is  part of the tecla library (see the
       libtecla man page). It  expands  a  specified  filename,
       converting  ~user/  and  ~/ expressions at the start of the filename to
       the corresponding home directories, replacing $envvar with the value of
       the  corresponding  environment  variable,  and  then, if there are any
       wildcards, matching these against existing  filenames.  Backslashes  in
       the  input filename are interpreted as escaping any special meanings of
       the characters that follow them.  Only backslahes that  are  themselves
       preceded by backslashes are preserved in the expanded filename.

       In  the  presence  of  wildcards,  the  returned list of filenames only
       includes the names of existing files which match the wildcards.  Other-
       wise,  the  original  filename is returned after expansion of tilde and
       dollar expressions, and the result  is  not  checked  against  existing
       files. This mimics the file-globbing behavior of the unix tcsh shell.

       The supported wildcards and their meanings are:
         *        -  Match any sequence of zero or more characters.
         ?        -  Match any single character.
         [chars]  -  Match any single character that appears in
                     'chars'.  If 'chars' contains an expression of
                     the form a-b, then any character between a and
                     b, including a and b, matches. The '-'
                     character looses its special meaning as a
                     range specifier when it appears at the start
                     of the sequence of characters. The ']'
                     character also looses its significance as the
                     terminator of the range expression if it
                     appears immediately after the opening '[', at
                     which point it is treated one of the
                     characters of the range. If you want both '-'
                     and ']' to be part of the range, the '-'
                     should come first and the ']' second.

         [^chars] -  The same as [chars] except that it matches any
                     single character that doesn't appear in
                     'chars'.

       Note that wildcards never match the initial dot in filenames that start
       with '.'. The initial '.' must be explicitly specified in the filename.
       This  again  mimics  the globbing behavior of most unix shells, and its
       rational is based in the fact that in unix, files with names that start
       with  '.'  are usually hidden configuration files, which are not listed
       by default by the ls command.

       The following is a complete example of how to use  the  file  expansion
       function.

         #include <stdio.h>
         #include <libtecla.h>

         int main(int argc, char *argv[])
         {
           ExpandFile *ef;      /* The expansion resource object */
           char *filename;      /* The filename being expanded */
           FileExpansion *expn; /* The results of the expansion */
           int i;

           ef = new_ExpandFile();
           if(!ef)
             return 1;

           for(arg = *(argv++); arg; arg = *(argv++)) {
             if((expn = ef_expand_file(ef, arg, -1)) == NULL) {
               fprintf(stderr, "Error expanding %s (%s).\n", arg,
                                ef_last_error(ef));
             } else {
               printf("%s matches the following files:\n", arg);
               for(i=0; i<expn->nfile; i++)
                 printf(" %s\n", expn->files[i]);
             }
           }

           ef = del_ExpandFile(ef);
           return 0;
         }

       Descriptions of the functions used above are as follows:

         ExpandFile *new_ExpandFile(void)

       This  function creates the resources used by the ef_expand_file() func-
       tion. In particular, it maintains the memory that is used to record the
       array  of matching filenames that is returned by ef_expand_file(). This
       array is expanded as needed, so there is no built in limit to the  num-
       ber of files that can be matched.

         ExpandFile *del_ExpandFile(ExpandFile *ef)

       This  function  deletes  the resources that were returned by a previous
       call to new_ExpandFile(). It always returns NULL (ie a deleted object).
       It does nothing if the ef argument is NULL.

       A container of the following type is returned by ef_expand_file().

         typedef struct {
           int exists;   /* True if the files in files[] exist */
           int nfile;    /* The number of files in files[] */
           char **files; /* An array of 'nfile' filenames. */
         } FileExpansion;

         FileExpansion *ef_expand_file(ExpandFile *ef,
                                       const char *path,
                                       int pathlen)

       The  ef_expand_file()  function  performs  filename expansion, as docu-
       mented at the start of this section. Its first argument is  a  resource
       object  returned  by  new_ExpandFile().  A  pointer to the start of the
       filename to be matched is passed via the path argument. This must be  a
       normal  NUL  terminated  string, but unless a length of -1 is passed in
       pathlen, only the first pathlen characters will be used in the filename
       expansion.   If  the length is specified as -1, the whole of the string
       will be expanded.

       The function returns a pointer to a container who's  contents  are  the
       results  of  the expansion. If there were no wildcards in the filename,
       the nfile member will be 1, and the exists member should be queried  if
       it  is  important to know if the expanded file currently exists or not.
       If there were wildcards, then the contained files[] array will  contain
       the names of the nfile existing files that matched the wildcarded file-
       name, and the exists member will  have  the  value  1.  Note  that  the
       returned container belongs to the specified ef object, and its contents
       will change on each call, so if you need to retain the results of  more
       than  one  call  to  ef_expand_file(), you should either make a private
       copy  of  the  returned  results,  or  create  multiple  file-expansion
       resource objects via multiple calls to new_ExpandFile().

       On  error,  NULL  is  returned,  and an explanation of the error can be
       determined by calling ef_last_error(ef).

         const char *ef_last_error(ExpandFile *ef)

       This function returns  the  message  which  describes  the  error  that
       occurred  on  the last call to ef_expand_file(), for the given (Expand-
       File *ef) resource object.

         int ef_list_expansions(FileExpansion *result, FILE *fp,
                                int terminal_width);

       The ef_list_expansions() function provides a convenient way to list the
       filename expansions returned by ef_expand_file(). Like the unix ls com-
       mand, it arranges the filenames into equal width columns,  each  column
       having  the  width  of  the largest file. The number of columns used is
       thus determined by the length of the longest filename, and  the  speci-
       fied  terminal  width.  Beware  that filenames that are longer than the
       specified terminal width are printed without being truncated, so output
       longer than the specified terminal width can occur. The list is written
       to the stdio stream specified by the fp argument.


THREAD SAFETY

       In multi-threaded programs, you should use the libtecla_r.a version  of
       the library. This uses POSIX reentrant functions where available (hence
       the _r suffix), and disables features that rely on non-reentrant system
       functions. Currently there are no features disabled in this module.

       Using  the  libtecla_r.a  version of the library, it is safe to use the
       facilities of this module  in  multiple  threads,  provided  that  each
       thread  uses  a separately allocated ExpandFile object. In other words,
       if two threads want  to  do  file  expansion,  they  should  each  call
       new_ExpandFile() to allocate their own file-expansion objects.


FILES

       libtecla.a    -    The tecla library
       libtecla.h    -    The tecla header file.


SEE ALSO

       libtecla, gl_get_line, cpl_complete_word,
       pca_lookup_file


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                 ef_expand_file
./libtecla/html/gl_get_line.html0100644000076400007640000030171510141252546015242 0ustar mcsmcs Manual Page
gl_get_line                          gl_get_line



NAME

       gl_get_line,    new_GetLine,    del_GetLine,   gl_customize_completion,
       gl_change_terminal, gl_configure_getline, gl_load_history, gl_save_his-
       tory,   gl_group_history,   gl_show_history,  gl_watch_fd,  gl_inactiv-
       ity_timeout,  gl_terminal_size,  gl_set_term_size,   gl_resize_history,
       gl_limit_history,  gl_clear_history,  gl_toggle_history, gl_lookup_his-
       tory,  gl_state_of_history,  gl_range_of_history,   gl_size_of_history,
       gl_echo_mode,   gl_replace_prompt,  gl_prompt_style,  gl_ignore_signal,
       gl_trap_signal, gl_last_signal, gl_completion_action,  gl_display_text,
       gl_return_status,  gl_error_message, gl_catch_blocked, gl_list_signals,
       gl_bind_keyseq, gl_erase_terminal, gl_automatic_history, gl_append_his-
       tory,  gl_query_char, gl_read_char - allow the user to compose an input
       line

SYNOPSIS

       #include <stdio.h>
       #include <libtecla.h>

       GetLine *new_GetLine(size_t linelen, size_t histlen);

       GetLine *del_GetLine(GetLine *gl);

       char *gl_get_line(GetLine *gl, const char *prompt,
                         const char *start_line, int start_pos);

       int gl_query_char(GetLine *gl, const char *prompt,
                         char defchar);

       int gl_read_char(GetLine *gl);

       int gl_customize_completion(GetLine *gl, void *data,
                                   CplMatchFn *match_fn);

       int gl_change_terminal(GetLine *gl, FILE *input_fp,
                              FILE *output_fp, const char *term);

       int gl_configure_getline(GetLine *gl,
                                const char *app_string,
                                const char *app_file,
                                const char *user_file);

       int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin,
                          const char *keyseq, const char *action);

       int gl_save_history(GetLine *gl, const char *filename,
                           const char *comment, int max_lines);

       int gl_load_history(GetLine *gl, const char *filename,
                           const char *comment);

       int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event,
                       GlFdEventFn *callback, void *data);

       int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *callback,
                          void *data, unsigned long sec,
                          unsigned long nsec);

       int gl_group_history(GetLine *gl, unsigned stream);

       int gl_show_history(GetLine *gl, FILE *fp,
                           const char *fmt, int all_groups,
                           int max_lines);

       int gl_resize_history(GetLine *gl, size_t bufsize);

       void gl_limit_history(GetLine *gl, int max_lines);

       void gl_clear_history(GetLine *gl, int all_groups);

       void gl_toggle_history(GetLine *gl, int enable);

       GlTerminalSize gl_terminal_size(GetLine *gl,
                                       int def_ncolumn,
                                       int def_nline);

       int gl_set_term_size(GetLine *gl, int ncolumn, int nline);

       int gl_lookup_history(GetLine *gl, unsigned long id,
                             GlHistoryLine *hline);

       void gl_state_of_history(GetLine *gl,
                                GlHistoryState *state);

       void gl_range_of_history(GetLine *gl,
                                GlHistoryRange *range);

       void gl_size_of_history(GetLine *gl, GlHistorySize *size);

       void gl_echo_mode(GetLine *gl, int enable);

       void gl_replace_prompt(GetLine *gl, const char *prompt);

       void gl_prompt_style(GetLine *gl, GlPromptStyle style);

       int gl_ignore_signal(GetLine *gl, int signo);

       int gl_trap_signal(GetLine *gl, int signo, unsigned flags,
                          GlAfterSignal after, int errno_value);

       int gl_last_signal(GetLine *gl);

       int gl_completion_action(GetLine *gl,
                                void *data, CplMatchFn *match_fn,
                                int list_only, const char *name,
                                const char *keyseq);

       int gl_register_action(GetLine *gl, void *data,
                              GlActionFn *fn, const char *name,
                              const char *keyseq);

       int gl_display_text(GetLine *gl, int indentation,
                           const char *prefix,
                           const char *suffix, int fill_char,
                           int def_width, int start,
                           const char *string);

       GlReturnStatus gl_return_status(GetLine *gl);

       const char *gl_error_message(GetLine *gl, char *buff,
                                    size_t n);

       void gl_catch_blocked(GetLine *gl);

       int gl_list_signals(GetLine *gl, sigset_t *set);

       int gl_append_history(GetLine *gl, const char *line);

       int gl_automatic_history(GetLine *gl, int enable);



DESCRIPTION

       The gl_get_line() function is part of the tecla library (see the libte-
       cla(@LIBR_MANEXT@) man page). If the user is typing at a terminal, each
       call prompts them for an line of input, then provides interactive edit-
       ing facilities, similar to those of the unix tcsh shell. In addition to
       simple command-line editing, it supports recall of  previously  entered
       command  lines,  TAB  completion  of  file names, and in-line wild-card
       expansion of filenames. Documentation of both the  user-level  command-
       line  editing features and all user configuration options, can be found
       in the tecla man page. This  man  page  concerns  itself
       with  documentation for programmers interested in using this library in
       their application.


AN EXAMPLE

       The following shows a complete example of how to use the  gl_get_line()
       function to get input from the user:

         #include <stdio.h>
         #include <locale.h>
         #include <libtecla.h>

         int main(int argc, char *argv[])
         {
           char *line;    /* The line that the user typed */
           GetLine *gl;   /* The gl_get_line() resource object */

           setlocale(LC_CTYPE, ""); /* Adopt the user's choice */
                                    /* of character set. */

           gl = new_GetLine(1024, 2048);
           if(!gl)
             return 1;

           while((line=gl_get_line(gl, "$ ", NULL, -1)) != NULL &&
                  strcmp(line, "exit\n") != 0)
             printf("You typed: %s\n", line);

           gl = del_GetLine(gl);
           return 0;
         }

       In  the  example, first the resources needed by the gl_get_line() func-
       tion are created by calling new_GetLine(). This  allocates  the  memory
       used  in  subsequent calls to the gl_get_line() function, including the
       history buffer for recording previously entered lines. Then one or more
       lines are read from the user, until either an error occurs, or the user
       types exit. Then finally the resources that were allocated by  new_Get-
       Line(),  are  returned to the system by calling del_GetLine(). Note the
       use of the NULL return value of del_GetLine() to make gl NULL. This  is
       a safety precaution. If the program subsequently attempts to pass gl to
       gl_get_line(), said  function  will  complain,  and  return  an  error,
       instead of attempting to use the deleted resource object.



THE FUNCTIONS USED IN THE EXAMPLE

       The descriptions of the functions used in the example are as follows:

         GetLine *new_GetLine(size_t linelen, size_t histlen)

       This  function creates the resources used by the gl_get_line() function
       and returns an opaque pointer to the object that  contains  them.   The
       maximum  length of an input line is specified via the linelen argument,
       and the number of bytes to allocate for storing history lines is set by
       the histlen argument. History lines are stored back-to-back in a single
       buffer of this size. Note that this means that the  number  of  history
       lines  that  can be stored at any given time, depends on the lengths of
       the individual lines.  If you want to place an upper limit on the  num-
       ber  of  lines  that can be stored, see the gl_limit_history() function
       described later. If you don't want history at all, specify  histlen  as
       zero, and no history buffer will be allocated.

       On error, a message is printed to stderr and NULL is returned.

         GetLine *del_GetLine(GetLine *gl)

       This  function  deletes  the resources that were returned by a previous
       call to new_GetLine(). It always returns NULL (ie a deleted object). It
       does nothing if the gl argument is NULL.

         char *gl_get_line(GetLine *gl, const char *prompt,
                          const char *start_line, int start_pos);

       The  gl_get_line()  function  can be called any number of times to read
       input from the user. The gl argument must have been previously returned
       by  a call to new_GetLine(). The prompt argument should be a normal NUL
       terminated string, specifying the prompt to present the user  with.  By
       default  prompts  are  displayed  literally,  but  if  enabled with the
       gl_prompt_style() function (see later), prompts can contain  directives
       to  do underlining, switch to and from bold fonts, or turn highlighting
       on and off.

       If you want to specify the initial contents of the line, for  the  user
       to  edit,  pass the desired string via the start_line argument. You can
       then specify which character of this line the cursor is initially posi-
       tioned  over,  using  the  start_pos argument. This should be -1 if you
       want the cursor to follow the last character of the start line. If  you
       don't want to preload the line in this manner, send start_line as NULL,
       and set start_pos to -1. Note that the line  pointer  returned  by  one
       call  to  gl_get_line()  can  be  passed  back  to  the  next  call  to
       gl_get_line() via the start_line. This allows the application  to  take
       the  last entered line, and if it contains an error, to then present it
       back to the user for re-editing, with the cursor  initially  positioned
       where the error was encountered.

       The gl_get_line() function returns a pointer to the line entered by the
       user, or NULL on error or at the end of the input. The returned pointer
       is  part  of  the  specified gl resource object, and thus should not be
       free'd by the caller, or assumed to be unchanging from one call to  the
       next.  When  reading  from a user at a terminal, there will always be a
       newline character at the end of the returned line.  When standard input
       is being taken from a pipe or a file, there will similarly be a newline
       unless the input line was too long to store in the internal buffer.  In
       the latter case you should call gl_get_line() again to read the rest of
       the line. Note  that  this  behavior  makes  gl_get_line()  similar  to
       fgets().    In   fact   when   stdin   isn't   connected  to  a  termi-
       nal,gl_get_line() just calls fgets().


THE RETURN STATUS OF GL_GET_LINE

       As described above, the gl_get_line() function has two possible  return
       values;  a pointer to the completed input line, or NULL. Extra informa-
       tion about what caused gl_get_line() to return  is  available  both  by
       inspecting errno, and by calling the gl_return_status() function.


         GlReturnStatus gl_return_status(GetLine *gl);


       The  following  are  the  possible enumerated values that this function
       returns.


         GLR_NEWLINE     -  The last call to gl_get_line()
                            successfully returned a completed
                            input line.

         GLR_BLOCKED     -  gl_get_line() was in non-blocking
                            server mode, and returned early to
                            avoid blocking the process while
                            waiting for terminal I/O. The
                            gl_pending_io() function can be
                            used to see what type of I/O
                            gl_get_line() was waiting for.
                            (see the gl_io_mode man page
                            for details).

         GLR_SIGNAL      -  A signal was caught by
                            gl_get_line() that had an
                            after-signal disposition of
                            GLS_ABORT (See gl_trap_signal()).

         GLR_TIMEOUT     -  The inactivity timer expired while
                            gl_get_line() was waiting for
                            input, and the timeout callback
                            function returned GLTO_ABORT.
                            See gl_inactivity_timeout() for
                            information about timeouts.

         GLR_FDABORT     -  An application I/O callack returned
                            GLFD_ABORT (see gl_watch_fd()).

         GLR_EOF         -  End of file reached. This can happen
                            when input is coming from a file or a
                            pipe, instead of the terminal. It also
                            occurs if the user invokes the
                            list-or-eof or del-char-or-list-or-eof
                            actions at the start of a new line.

         GLR_ERROR       -  An unexpected error caused
                            gl_get_line() to abort (consult
                            errno and/or
                            gl_error_message() for details.


       When gl_return_status() returns GLR_ERROR, and the value of errno isn't
       sufficient to explain what happened, you can use the gl_error_message()
       function to request a description of the last error that occurred.


         const char *gl_error_message(GetLine *gl, char *buff,
                                      size_t n);


       The return value is a pointer to the message that occurred. If the buff
       argument  is  NULL, this will be a pointer to a buffer within gl, who's
       value will probably change on the next call to any function  associated
       with gl_get_line(). Otherwise, if a non-NULL buff argument is provided,
       the error message, including a '\0' terminator, will be written  within
       the  first  n  elements  of this buffer, and the return value will be a
       pointer to the first element of this buffer. If the message  won't  fit
       in the provided buffer, it will be truncated to fit.


OPTIONAL PROMPT FORMATTING

       Whereas by default the prompt string that you specify is displayed lit-
       erally, without any special interpretation of the characters within it,
       the  gl_prompt_style()  function can be used to enable optional format-
       ting directives within the prompt.

         void gl_prompt_style(GetLine *gl, GlPromptStyle style);

       The style argument, which specifies the formatting style, can take  any
       of the following values:

         GL_FORMAT_PROMPT   -  In this style, the formatting
                               directives described below, when
                               included in prompt strings, are
                               interpreted as follows:

                                 %B  -  Display subsequent
                                        characters with a bold
                                        font.
                                 %b  -  Stop displaying characters
                                        with the bold font.
                                 %F  -  Make subsequent characters
                                        flash.
                                 %f  -  Turn off flashing
                                        characters.
                                 %U  -  Underline subsequent
                                        characters.
                                 %u  -  Stop underlining
                                        characters.
                                 %P  -  Switch to a pale (half
                                        brightness) font.
                                 %p  -  Stop using the pale font.
                                 %S  -  Highlight subsequent
                                        characters (also known as
                                        standout mode).
                                 %s  -  Stop highlighting
                                        characters.
                                 %V  -  Turn on reverse video.
                                 %v  -  Turn off reverse video.
                                 %%  -  Display a single %
                                        character.

                               For example, in this mode, a prompt
                               string like "%UOK%u$ " would
                               display the prompt "OK$ ",
                               but with the OK part
                               underlined.

                               Note that although a pair of
                               characters that starts with a %
                               character, but doesn't match any of
                               the above directives is displayed
                               literally, if a new directive is
                               subsequently introduced which does
                               match, the displayed prompt will
                               change, so it is better to always
                               use %% to display a literal %.

                               Also note that not all terminals
                               support all of these text
                               attributes, and that some substitute
                               a different attribute for missing
                               ones.

         GL_LITERAL_PROMPT  -  In this style, the prompt string is
                               printed literally. This is the
                               default style.


ALTERNATE CONFIGURATION SOURCES

       As mentioned above, by default users have the option of configuring the
       behavior of gl_get_line() via a configuration file called  .teclarc  in
       their  home directories. The fact that all applications share this same
       configuration file is both an advantage and a  disadvantage.   In  most
       cases it is an advantage, since it encourages uniformity, and frees the
       user from having to configure each  application  separately.   In  some
       applications, however, this single means of configuration is a problem.
       This is particularly  true  of  embedded  software,  where  there's  no
       filesystem  to read a configuration file from, and also in applications
       where a radically different choice of keybindings is needed to  emulate
       a  legacy  keyboard  interface.  To cater for such cases, the following
       function allows the application to control where configuration informa-
       tion is read from.


         int gl_configure_getline(GetLine *gl,
                                  const char *app_string,
                                  const char *app_file,
                                  const char *user_file);


       It allows the configuration commands that would normally be read from a
       user's ~/.teclarc file, to be read from any or none of,  a  string,  an
       application specific configuration file, and/or a user-specific config-
       uration file. If this function is  called  before  the  first  call  to
       gl_get_line(),  the default behavior of reading ~/.teclarc on the first
       call to  gl_get_line()  is  disabled,  so  all  configuration  must  be
       achieved  using the configuration sources specified with this function.

       If app_string != NULL, then it is interpreted as  a  string  containing
       one  or  more  configuration commands, separated from each other in the
       string by embedded newline characters. If app_file != NULL then  it  is
       interpreted  as the full pathname of an application-specific configura-
       tion file. If user_file != NULL then it  is  interpreted  as  the  full
       pathname of a user-specific configuration file, such as ~/.teclarc. For
       example, in the following call,


         gl_configure_getline(gl, "edit-mode vi \n nobeep",
                                  "/usr/share/myapp/teclarc",
                                  "~/.teclarc");


       the app_string argument causes the calling application to start  in  vi
       edit-mode,  instead of the default emacs mode, and turns off the use of
       the terminal bell by the library. It then attempts to read  system-wide
       configuration     commands     from    an    optional    file    called
       /usr/share/myapp/teclarc, then finally reads  user-specific  configura-
       tion  commands from an optional .teclarc file in the user's home direc-
       tory. Note that the arguments are listed in ascending order  of  prior-
       ity,  with  the  contents  of app_string being potentially overriden by
       commands in app_file, and commands in app_file potentially being  over-
       riden by commands in user_file.

       You  can  call this function as many times as needed, the results being
       cumulative, but note that copies of any  filenames  specified  via  the
       app_file and user_file arguments are recorded internally for subsequent
       use by the read-init-files key-binding function, so if you plan to call
       this  function multiple times, be sure that the last call specifies the
       filenames that you want re-read when the user requests that the config-
       uration files be re-read.

       Individual  key  sequences  can  also  be  bound  and unbound using the
       gl_bind_keyseq() function.


         int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin,
                            const char *keyseq,
                            const char *action);


       The origin argument specifies the priority of the binding, according to
       who  it  is being established for, and must be one of the following two
       values.

         GL_USER_KEY   -   The user requested this key-binding.
         GL_APP_KEY    -   This is a default binding set by the
                           application.

       When both user and application bindings for a given  key-sequence  have
       been  specified,  the  user binding takes precedence. The application's
       binding is subsequently reinstated  if  the  user's  binding  is  later
       unbound  via  either  another to this function, or a call to gl_config-
       ure_getline().

       The keyseq argument specifies the key-sequence to be bound or  unbound,
       and is expressed in the same way as in a ~/.teclarc configuration file.
       The action argument must either be a string containing the name of  the
       action  to bind the key-sequence to, or it must be NULL or "" to unbind
       the key-sequence.


CUSTOMIZED WORD COMPLETION

       If in your application, you would like to have TAB completion  complete
       other  things  in  addition to or instead of filenames, you can arrange
       this by registering an alternate completion callback  function,  via  a
       call to the gl_customize_completion() function.

         int gl_customize_completion(GetLine *gl, void *data,
                                     CplMatchFn *match_fn);

       The  data  argument  provides  a way for your application to pass arbi-
       trary, application-specific information to the callback function.  This
       is  passed  to  the callback every time that it is called. It might for
       example, point to the symbol table from which possible completions  are
       to  be sought. The match_fn argument specifies the callback function to
       be called. The CplMatchFn function type is defined in libtecla.h, as is
       a  CPL_MATCH_FN() macro that you can use to declare and prototype call-
       back functions. The declaration and responsibilities of callback  func-
       tions  are  described  in depth in the cpl_complete_word
       man page.

       In brief, the callback function is responsible for looking backwards in
       the  input  line, back from the point at which the user pressed TAB, to
       find the start of the word being completed. It then must lookup  possi-
       ble  completions  of this word, and record them one by one in the Word-
       Completion object that is passed to it as an argument, by  calling  the
       cpl_add_completion()  function. If the callback function wishes to pro-
       vide filename completion in addition to its own  specific  completions,
       it  has  the  option of itself calling the builtin file-name completion
       callback.    This    also,    is    documented    in    the    cpl_com-
       plete_word(@FUNC_MANEXT@) man page.

       Note  that  if you would like gl_get_line() to return the current input
       line when a successful completion is been made, you  can  arrange  this
       when you call cpl_add_completion(), by making the last character of the
       continuation suffix a newline character. If you do this, the input line
       will   be   updated  to  display  the  completion,  together  with  any
       contiuation suffix up to the newline character, then gl_get_line() will
       return this input line.


       If, for some reason, your callback function needs to write something to
       the terminal, it must call gl_normal_io() before doing  so.  This  will
       start  a  new line after the input line that is currently being edited,
       reinstate normal terminal I/O, and tell gl_get_line()  that  the  input
       line will need to be redrawn when the callback returns.


ADDING COMPLETION ACTIONS

       In  the  previous  section the ability to customize the behavior of the
       only default completion action, complete-word, was described.  In  this
       section  the  ability  to  install additional action functions, so that
       different types of word completion  can  be  bound  to  different  key-
       sequences,  is  described.  This  is  achieved  by using the gl_comple-
       tion_action() function.


         int gl_completion_action(GetLine *gl,
                                  void *data, CplMatchFn *match_fn,
                                  int list_only, const char *name,
                                  const char *keyseq);


       The data and match_fn  arguments  are  as  described  in  the  cpl_com-
       plete_word  man  page, and specify the callback function that should be
       invoked to  identify  possible  completions.   The  list_only  argument
       determines  whether  the action that is being defined should attempt to
       complete the word as far as possible in the input line before  display-
       ing  any  possible  ambiguous  completions, or whether it should simply
       display the list of possible completions  without  touching  the  input
       line. The former option is selected by specifying a value of 0, and the
       latter by specifying a value of 1. The name argument specifies the name
       by  which  configuration  files and future invokations of this function
       should refer to the action. This must either be the name of an existing
       completion  action  to  be  changed,  or be a new unused name for a new
       action. Finally, the keyseq argument specifies the default key-sequence
       to  bind  the  action  to.  If this is NULL, no new keysequence will be
       bound to the action.

       Beware that in order for the user to be able to change the key-sequence
       that  is  bound  to actions that are installed in this manner, when you
       call gl_completion_action() to install a given  action  for  the  first
       time,  you  should  do this between calling new_GetLine() and the first
       call to gl_get_line().  Otherwise, when the user's  configuration  file
       is  read on the first call to gl_get_line(), the name of the your addi-
       tional action won't be known, and any reference to it in the configura-
       tion file will generate an error.

       As  discussed for gl_customize_completion(), if your callback function,
       for some reason, needs to write anything to the terminal, it must  call
       gl_normal_io() before doing so.


DEFINING CUSTOM ACTIONS

       Although  the built-in key-binding actions are sufficient for the needs
       of most applications, occasionally a specialized application  may  need
       to  define  one  or  more custom actions, bound to application-specific
       key-sequences. For example, a sales application would benefit from hav-
       ing  a key-sequence that displayed the part name that corresponded to a
       part number preceding the cursor. Such a feature is clearly beyond  the
       scope  of the built-in action functions. So for such special cases, the
       gl_register_action() function is provided.


         int gl_register_action(GetLine *gl, void *data,
                       GlActionFn *fn, const char *name,
                       const char *keyseq);


       This function lets the application register an external  function,  fn,
       that  will  thereafter  be  called  whenever  either the specified key-
       sequence, keyseq, is entered by the user, or the user enters any  other
       key-sequence  that  the user subsequently binds to the specified action
       name, name, in their configuration file. The data  argument  can  be  a
       pointer  to  anything that the application wishes to have passed to the
       action function, fn, whenever that function is invoked.

       The action function, fn, should be declared using the following  macro,
       which is defined in libtecla.h.


         #define GL_ACTION_FN(fn) GlAfterAction (fn)(GetLine *gl, \
                     void *data, int count, size_t curpos, \
                     const char *line)


       The  gl  and  data  arguments  are those that were previously passed to
       gl_register_action() when the action function was registered. The count
       argument  is a numeric argument which the user has the option of enter-
       ing using the digit-argument action, before invoking the action. If the
       user doesn't enter a number, then the count argument is set to 1. Nomi-
       nally this argument is interpreted as a repeat count, meaning that  the
       action  should  be  repeated  that many times. In practice however, for
       some actions a repeat count makes little sense. In such cases,  actions
       can  either  simply  ignore  the count argument, or use its value for a
       different purpose.

       A copy of the current input line is passed in the read-only line  argu-
       ment.  The  current  cursor position within this string is given by the
       index contained in the curpos argument. Note that  direct  manipulation
       of  the  input  line  and the cursor position is not permitted. This is
       because the rules dicated by various modes,  such  as  vi  mode  versus
       emacs  mode,  no-echo mode, and insert mode versus overstrike mode etc,
       make it too complex for an application writer  to  write  a  conforming
       editing action, as well as constrain future changes to the internals of
       gl_get_line(). A potential solution to this dilema would  be  to  allow
       the  action  function  to  edit  the  line  using  the existing editing
       actions. This is currently under consideration.

       If the action function wishes to write text to  the  terminal,  without
       this  getting  mixed  up  with the displayed text of the input line, or
       read from the terminal without having to handle raw terminal I/O,  then
       before  doing  either  of these operations, it must temporarily suspend
       line editing by calling  the  gl_normal_io()  function.  This  function
       flushes  any  pending  output  to the terminal, moves the cursor to the
       start of the line that follows the last  terminal  line  of  the  input
       line,  then  restores  the terminal to a state that is suitable for use
       with the C stdio facilities. The latter includes such things as restor-
       ing the normal mapping of \n to \r\n, and, when in server mode, restor-
       ing the normal blocking form of terminal I/O. Having called this  func-
       tion, the action function can read from and write to the terminal with-
       out the fear of creating a mess.  It isn't  necessary  for  the  action
       function to restore the original editing environment before it returns.
       This is done automatically by gl_get_line() after the  action  function
       returns.  The following is a simple example of an action function which
       writes the sentence "Hello world" on a new terminal line after the line
       being  edited. When this function returns, the input line is redrawn on
       the line that follows the "Hello world" line, and line editing resumes.


         static GL_ACTION_FN(say_hello_fn)
         {
           if(gl_normal_io(gl))   /* Temporarily suspend editing */
             return GLA_ABORT;
           printf("Hello world\n");
           return GLA_CONTINUE;
         }


       Action  functions  must  return  one  of  the following values, to tell
       gl_get_line() how to procede.


         GLA_ABORT     -   Cause gl_get_line() to return NULL.
         GLA_RETURN    -   Cause gl_get_line() to return the
                           completed input line.
         GLA_CONTINUE  -   Resume command-line editing.


       Note that the name argument of gl_register_action() specifies the  name
       by  which  a  user can refer to the action in their configuration file.
       This allows them to re-bind the action to an alternate key-seqeunce. In
       order  for  this  to work, it is necessary to call gl_register_action()
       between calling new_GetLine() and the first call to gl_get_line().


HISTORY FILES

       To save the contents of the history buffer before quitting your  appli-
       cation,  and subsequently restore them when you next start the applica-
       tion, the following functions are provided.


        int gl_save_history(GetLine *gl, const char *filename,
                            const char *comment, int max_lines);
        int gl_load_history(GetLine *gl, const char *filename,
                            const char *comment);


       The filename argument specifies the name to give the history file  when
       saving, or the name of an existing history file, when loading. This may
       contain home-directory and environment variable  expressions,  such  as
       "~/.myapp_history" or "$HOME/.myapp_history".

       Along  with each history line, extra information about it, such as when
       it was entered by the user, and what its nesting level is, is  recorded
       as  a comment preceding the line in the history file. Writing this as a
       comment allows the history file to double as a command  file,  just  in
       case  you  wish  to replay a whole session using it. Since comment pre-
       fixes differ in different languages, the comment argument  is  provided
       for  specifying  the  comment  prefix. For example, if your application
       were a unix shell, such as the bourne  shell,  you  would  specify  "#"
       here.  Whatever  you choose for the comment character, you must specify
       the same prefix to gl_load_history() that  you  used  when  you  called
       gl_save_history() to write the history file.

       The  max_lines  must be either -1 to specify that all lines in the his-
       tory list be saved, or a positive number specifying a  ceiling  on  how
       many of the most recent lines should be saved.

       Both  fuctions return non-zero on error, after writing an error message
       to stderr. Note that gl_load_history() does not consider the  non-exis-
       tence of a file to be an error.


MULTIPLE HISTORY LISTS

       If your application uses a single GetLine object for entering many dif-
       ferent types of input lines, you may wish gl_get_line() to  distinguish
       the different types of lines in the history list, and only recall lines
       that match the current type  of  line.  To  support  this  requirement,
       gl_get_line()  marks  lines  being recorded in the history list with an
       integer identifier chosen by the application.  Initially  this  identi-
       fier  is  set to 0 by new_GetLine(), but it can be changed subsequently
       by calling gl_group_history().


         int gl_group_history(GetLine *gl, unsigned id);


       The integer identifier id can be any number chosen by the  application,
       but  note  that  gl_save_history()  and  gl_load_history() preserve the
       association between identifiers and historical input lines between pro-
       gram  invokations,  so you should choose fixed identifiers for the dif-
       ferent types of input line used by your application.

       Whenever gl_get_line() appends a new input line to  the  history  list,
       the  current  history  identifier  is  recorded with it, and when it is
       asked to recall a historical input line, it only recalls lines that are
       marked with the current identifier.


DISPLAYING HISTORY

       The history list can be displayed by calling gl_show_history().


         int gl_show_history(GetLine *gl, FILE *fp,
                             const char *fmt,
                             int all_groups,
                             int max_lines);


       This  displays  the  current  contents of the history list to the stdio
       output stream fp. If the max_lines argument is greater than or equal to
       zero,  then  no  more than this number of the most recent lines will be
       displayed. If the all_groups argument is non-zero, lines from all  his-
       tory  groups  are  displayed.  Otherwise  just  those  of the currently
       selected history group are displayed. The format string argument,  fmt,
       determines  how the line is displayed. This can contain arbitrary char-
       acters which are written verbatim, interleaved with any of the  follow-
       ing format directives:

         %D  -  The date on which the line was originally
                entered, formatted like 2001-11-20.
         %T  -  The time of day when the line was entered,
                formatted like 23:59:59.
         %N  -  The sequential entry number of the line in
                the history buffer.
         %G  -  The number of the history group which the
                line belongs to.
         %%  -  A literal % character.
         %H  -  The history line itself.

       Thus a format string like "%D %T  %H0 would output something like:

         2001-11-20 10:23:34  Hello world

       Note  the  inclusion  of  an  explicit  newline character in the format
       string.


LOOKING UP HISTORY

       The gl_lookup_history() function allows the calling application to look
       up lines in the history list.


         typedef struct {
           const char *line;    /* The requested historical */
                                /*  line. */
           unsigned group;      /* The history group to which */
                                /*  the line belongs. */
           time_t timestamp;    /* The date and time at which */
                                /*  the line was originally */
                                /*  entered. */
         } GlHistoryLine;

         int gl_lookup_history(GetLine *gl, unsigned long id,
                               GlHistoryLine *hline);


       The  id  argument indicates which line to look up, where the first line
       that was entered in the history list after new_GetLine() was called, is
       denoted  by  0, and subsequently entered lines are denoted with succes-
       sively higher numbers. Note that the range of lines currently preserved
       in the history list can be queried by calling the gl_range_of_history()
       function, described later. If the requested  line  is  in  the  history
       list,  the  details of the line are recorded in the variable pointed to
       by the hline argument, and 1 is returned. Otherwise 0 is returned,  and
       the variable pointed to by hline is left unchanged.

       Beware  that  the string returned in hline->line is part of the history
       buffer, so it must not be modified by the caller, and will be  recycled
       on  the next call to any function that takes gl as its argument. There-
       fore you should make a private copy of this string if you need to  keep
       it around.


MANUAL HISTORY ARCHIVAL

       By default, whenever a line is entered by the user, it is automatically
       appended to the history list, just  before  gl_get_line()  returns  the
       line  to  the  caller.  This is convenient for the majority of applica-
       tions, but there are also applications that need finer grained  control
       over  what gets added to the history list. In such cases, the automatic
       addition of entered lines to the history list  can  be  turned  off  by
       calling the gl_automatic_history() function.


         int gl_automatic_history(GetLine *gl, int enable);


       If  this  function  is  called  with  its  enable  argument  set  to 0,
       gl_get_line() won't automatically archive subsequently  entered  lines.
       Automatic  archiving  can be reenabled at a later time, by calling this
       function again, with its enable argument set  to  1.   While  automatic
       history  archiving  is  disabled,  the  calling application can use the
       gl_append_history() to append lines to the history list as needed.


         int gl_append_history(GetLine *gl, const char *line);


       The line argument specifies the line to be added to the  history  list.
       This  must  be  a normal ' ' terminated string. If this string contains
       any newline characters, the line that gets archived in the history list
       will  be  terminated by the first of these. Otherwise it will be termi-
       nated by the ' ' terminator.  If the line is longer  than  the  maximum
       input  line  length,  that was specified when new_GetLine() was called,
       when the line  is  recalled,  it  will  get  truncated  to  the  actual
       gl_get_line() line length.

       If successful, gl_append_history() returns 0. Otherwise it returns non-
       zero, and sets errno to one of the following values.


          EINVAL  -  One of the arguments passed to
                     gl_append_history() was NULL.
          ENOMEM  -  The specified line was longer than the allocated
                     size of the history buffer (as specified when
                     new_GetLine() was called), so it couldn't be
                     archived.


       A textual description of the error can optionally be obtained by  call-
       ing gl_error_message(). Note that after such an error, the history list
       remains in a valid state to receive new history lines, so there is lit-
       tle harm in simply ignoring the return status of gl_append_history().


MISCELLANEOUS HISTORY CONFIGURATION

       If  you  wish  to change the size of the history buffer that was origi-
       nally specified in the call to new_GetLine(), you can do  so  with  the
       gl_resize_history() function.


         int gl_resize_history(GetLine *gl, size_t histlen);


       The  histlen argument specifies the new size in bytes, and if you spec-
       ify this as 0, the buffer will be deleted.

       As mentioned in the discussion of new_GetLine(), the  number  of  lines
       that can be stored in the history buffer, depends on the lengths of the
       individual lines. For example, a 1000 byte buffer could  equally  store
       10  lines  of average length 100 bytes, or 2 lines of average length 50
       bytes. Although the buffer is never expanded when new lines are  added,
       a  list  of  pointers  into the buffer does get expanded when needed to
       accomodate the number of lines currently stored in the buffer. To place
       an upper limit on the number of lines in the buffer, and thus a ceiling
       on  the  amount  of  memory  used  in  this  list,  you  can  call  the
       gl_limit_history() function.


         void gl_limit_history(GetLine *gl, int max_lines);


       The  max_lines  should  either be a positive number >= 0, specifying an
       upper limit on the number of lines in the buffer, or be  -1  to  cancel
       any  previously  specified  limit.  When a limit is in effect, only the
       max_lines most recently appended lines are kept in  the  buffer.  Older
       lines are discarded.

       To  discard  lines  from the history buffer, use the gl_clear_history()
       function.

         void gl_clear_history(GetLine *gl, int all_groups);

       The all_groups argument tells the function whether to delete  just  the
       lines  associated  with  the  current  history group (see gl_group_his-
       tory()), or all historical lines in the buffer.

       The gl_toggle_history() function allows you to toggle  history  on  and
       off without losing the current contents of the history list.


         void gl_toggle_history(GetLine *gl, int enable);


       Setting  the  enable argument to 0 turns off the history mechanism, and
       setting it to 1 turns it back on. When history is turned  off,  no  new
       lines  will  be added to the history list, and history lookup key-bind-
       ings will act as though there is nothing in the history buffer.


QUERYING HISTORY INFORMATION

       The configured state of the  history  list  can  be  queried  with  the
       gl_history_state() function.


         typedef struct {
           int enabled;     /* True if history is enabled */
           unsigned group;  /* The current history group */
           int max_lines;   /* The current upper limit on the */
                            /*  number of lines in the history */
                            /*  list, or -1 if unlimited. */
         } GlHistoryState;

         void gl_state_of_history(GetLine *gl,
                                  GlHistoryState *state);

       On  return,  the status information is recorded in the variable pointed
       to by the state argument.

       The gl_range_of_history() function returns  the  number  and  range  of
       lines in the history list.


       typedef struct {
         unsigned long oldest;  /* The sequential entry number */
                                /*  of the oldest line in the */
                                /*  history list. */
         unsigned long newest;  /* The sequential entry number */
                                /*  of the newest line in the */
                                /*  history list. */
         int nlines;            /* The number of lines in the */
                                /*  history list. */
       } GlHistoryRange;

       void gl_range_of_history(GetLine *gl, GlHistoryRange *range);

       The  return values are recorded in the variable pointed to by the range
       argument. If the nlines member of this structure is greater than  zero,
       then  the  oldest  and  newest members report the range of lines in the
       list, and newest=oldest+nlines-1.  Otherwise they are both zero.

       The gl_size_of_history() function returns the total size of the history
       buffer and the amount of the buffer that is currently occupied.

         typedef struct {
           size_t size;      /* The size of the history buffer */
                             /*  (bytes). */
           size_t used;      /* The number of bytes of the */
                             /*  history buffer that are */
                             /*  currently occupied. */
         } GlHistorySize;

         void gl_size_of_history(GetLine *gl, GlHistorySize *size);

       On  return, the size information is recorded in the variable pointed to
       by the size argument.


CHANGING TERMINALS

       The new_GetLine() constructor function assumes that input is to be read
       from stdin, and output written to stdout. The following function allows
       you to switch to different input and output streams.

         int gl_change_terminal(GetLine *gl, FILE *input_fp,
                                FILE *output_fp, const char *term);

       The gl argument is the object that was returned by new_GetLine().   The
       input_fp  argument  specifies  the  stream  to read from, and output_fp
       specifies the stream to be written to. Only if both of these refer to a
       terminal,  will  interactive  terminal  input  be  enabled.   Otherwise
       gl_get_line() will simply call fgets() to read command input.  If  both
       streams refer to a terminal, then they must refer to the same terminal,
       and the type of this terminal must be specified via the term  argument.
       The value of the term argument is looked up in the terminal information
       database (terminfo or termcap), in order  to  determine  which  special
       control  sequences  are needed to control various aspects of the termi-
       nal.  new_GetLine()  for  example,   passes   the   return   value   of
       getenv("TERM")  in  this argument. Note that if one or both of input_fp
       and output_fp don't refer to a terminal, then it is legal to pass  NULL
       instead of a terminal type.

       Note that if you want to pass file descriptors to gl_change_terminal(),
       you can do this by creating  stdio  stream  wrappers  using  the  POSIX
       fdopen() function.


EXTERNAL EVENT HANDLING

       By  default, gl_get_line() doesn't return until either a complete input
       line has been entered by the user, or an error occurs. In programs that
       need  to  watch for I/O from other sources than the terminal, there are
       two options.


         1. Use the functions described in the
            gl_io_mode man page to switch
            gl_get_line() into non-blocking server mode. In this mode,
            gl_get_line() becomes a non-blocking, incremental
            line-editing function that can safely be called from
            an external event loop. Although this is a very
            versatile method, it involves taking on some
            responsibilities that are normally performed behind
            the scenes by gl_get_line().

         2. While gl_get_line() is waiting for keyboard
            input from the user, you can ask it to also watch for
            activity on arbitrary file descriptors, such as
            network sockets, pipes etc, and have it call functions
            of your choosing when activity is seen. This works on
            any system that has the select() system call,
            which is most, if not all flavors of unix.


       Registering a file descriptor to be watched by  gl_get_line()  involves
       calling the gl_watch_fd() function.


         int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event,
                         GlFdEventFn *callback, void *data);


       If  this returns non-zero, then it means that either your arguments are
       invalid, or that this facility isn't supported on the host system.

       The fd argument is the file descriptor to be watched. The  event  argu-
       ment  specifies  what  type of activity is of interest, chosen from the
       following enumerated values:


         GLFD_READ   -  Watch for the arrival of data to be read.
         GLFD_WRITE  -  Watch for the ability to write to the file
                        descriptor without blocking.
         GLFD_URGENT -  Watch for the arrival of urgent
                        out-of-band data on the file descriptor.


       The callback argument  is  the  function  to  call  when  the  selected
       activity  is seen. It should be defined with the following macro, which
       is defined in libtecla.h.


         #define GL_FD_EVENT_FN(fn) GlFdStatus (fn)(GetLine *gl, \
                                             void *data, int fd, \
                                             GlFdEvent event)

       The data argument of the gl_watch_fd() function is passed to the  call-
       back  function  for  its  own  use, and can point to anything you like,
       including NULL. The file descriptor and the  event  argument  are  also
       passed  to  the callback function, and this potentially allows the same
       callback function to be registered to  more  than  one  type  of  event
       and/or  more than one file descriptor. The return value of the callback
       function should be one of the following values.


         GLFD_ABORT    -  Tell gl_get_line() to abort. When this
                          happens, gl_get_line() returns
                          NULL, and a following call to
                          gl_return_status() will return
                          GLR_FDABORT. Note that if the
                          application needs errno always to
                          have a meaningful value when
                          gl_get_line() returns NULL,
                          the callback function should set
                          errno appropriately.
         GLFD_REFRESH  -  Redraw the input line then continue
                          waiting for input. Return this if
                          your callback wrote to the terminal.
         GLFD_CONTINUE -  Continue to wait for input, without
                          redrawing the line.

       Note that before calling the callback, gl_get_line() blocks  most  sig-
       nals,  and  leaves its own signal handlers installed, so if you need to
       catch a particular signal you will need  to  both  temporarily  install
       your  own  signal  handler, and unblock the signal. Be sure to re-block
       the signal (if it was originally blocked) and  reinstate  the  original
       signal handler, if any, before returning.



       If  the  callback  function  needs to read or write to the terminal, it
       should ideally first call gl_normal_io(gl) to temporarily suspend  line
       editing.  This  will  restore  the terminal to canonical, blocking-I/O,
       mode, and move the cursor to the start of a new terminal  line.  Later,
       when  the  callback  returns,  gl_get_line()  will  notice that gl_nor-
       mal_io() was called, redisplay the input line and resume editing.  Note
       that in this case the return values, GLFD_REFRESH and GLFD_CONTINUE are
       equivalent.



       To support cases where the callback function calls a third-party  func-
       tion  which  occasionally and u0prisicre-enabledesbeforee themicallback
       automatic conversion of "0 to "
       function  is called. If the callack knows that the third-party function
       wrote to the terminal, it should then return  the  GLFD_REFRESH  return
       value, to tell gl_get_line() to redisplay the input line.



       To  remove  a  callback  function  that you previously registered for a
       given file descriptor and event, simply  call  gl_watch_fd()  with  the
       same  file descriptor and event arguments, but with a callback argument
       of 0. The data argument is ignored in this case.


SETTING AN INACTIVITY TIMEOUT

       On systems with the select() system call,  the  gl_inactivity_timeout()
       function can be used to set or cancel an inactivity timeout. Inactivity
       in this case refers both to keyboard input, and  to  I/O  on  any  file
       descriptors  registered by prior and subsequent calls to gl_watch_fd().
       On oddball systems that don't have select(), this call has no effect.


         int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *callback,
                            void *data, unsigned long sec,
                            unsigned long nsec);


       The timeout is specified in the form of an integral number  of  seconds
       and  an  integral number of nanoseconds, via the sec and nsec arguments
       respectively. Subsequently, whenever no activity is seen for this  time
       period, the function specified via the callback argument is called. The
       data argument of gl_inactivity_timeout() is  passed  verbatim  to  this
       callback  function whenever it is invoked, and can thus be used to pass
       arbitrary application-specific information to the callback. The follow-
       ing  macro is provided in libtecla.h for applications to use to declare
       and prototype timeout callback functions.


         #define GL_TIMEOUT_FN(fn) \
                      GlAfterTimeout (fn)(GetLine *gl, void *data)


       On returning, the application's callback is expected to return  one  of
       the  following  enumerators  to tell gl_get_line() how to procede after
       the timeout has been handled by the callback.


         GLTO_ABORT    -  Tell gl_get_line() to abort. When
                          this happens, gl_get_line() will
                          return NULL, and a following call
                          to gl_return_status() will return
                          GLR_TIMEOUT. Note that if the
                          application needs errno always to
                          have a meaningful value when
                          gl_get_line() returns NULL,
                          the callback function should set
                          errno appropriately.
         GLTO_REFRESH  -  Redraw the input line, then continue
                          waiting for input. You should return
                          this value if your callback wrote to the
                          terminal without having first called
                          gl_normal_io(gl).
         GLTO_CONTINUE -  In normal blocking-I/O mode, continue to
                          wait for input, without redrawing the
                          user's input line.
                          In non-blocking server I/O mode (see
                          gl_io_mode), cause gl_get_line()
                          to act as though I/O blocked. This means
                          that gl_get_line() will immediately
                          return NULL, and a following call
                          to gl_return_status() will return
                          GLR_BLOCKED.


       Note that before calling the callback, gl_get_line() blocks  most  sig-
       nals,  and  leaves its own signal handlers installed, so if you need to
       catch a particular signal you will need  to  both  temporarily  install
       your  own  signal  handler, and unblock the signal. Be sure to re-block
       the signal (if it was originally blocked) and  reinstate  the  original
       signal handler, if any, before returning.



       If  the  callback  function  needs to read or write to the terminal, it
       should ideally first call gl_normal_io(gl) to temporarily suspend  line
       editing.  This  will  restore  the terminal to canonical, blocking-I/O,
       mode, and move the cursor to the start of a new terminal  line.  Later,
       when  the  callback  returns,  gl_get_line()  will  notice that gl_nor-
       mal_io() was called, redisplay the input line and resume editing.  Note
       that in this case the return values, GLTO_REFRESH and GLTO_CONTINUE are
       equivalent.



       To support cases where the callback function calls a third-party  func-
       tion  which  occasionally and u0prisicre-enabledesbeforee themicallback
       automatic conversion of "0 to "
       function  is called. If the callack knows that the third-party function
       wrote to the terminal, it should then return  the  GLTO_REFRESH  return
       value, to tell gl_get_line() to redisplay the input line.



       Note  that  although the timeout argument includes a nano-second compo-
       nent, few computer clocks presently have  resolutions  that  are  finer
       than  a few milliseconds, so asking for less than a few milliseconds is
       equivalent to requesting zero seconds on a  lot  of  systems.  If  this
       would  be  a  problem,  you  should  base your timeout selection on the
       actual   resolution   of   the   host    clock    (eg.    by    calling
       sysconf(_SC_CLK_TCK)).



       To  turn off timeouts, simply call gl_inactivity_timeout() with a call-
       back argument of 0. The data argument is ignored in this case.


SIGNAL HANDLING DEFAULTS

       By default, the gl_get_line() function intercepts a number of  signals.
       This  is particularly important for signals which would by default ter-
       minate the process, since the terminal needs to be restored to a usable
       state  before  this  happens.  In  this  section,  the signals that are
       trapped  by  default,  and  how  gl_get_line()  responds  to  them,  is
       described.  Changing  these defaults is the topic of the following sec-
       tion.

       When the following subset of signals are  caught,  gl_get_line()  first
       restores  the  terminal  settings  and signal handling to how they were
       before gl_get_line() was called, resends the signal, to allow the call-
       ing  application's  signal  handlers  to handle it, then if the process
       still exists, gl_get_line() returns NULL and sets  errno  as  specified
       below.


        SIGINT  -  This signal is generated both by the keyboard
                   interrupt key (usually ^C), and the keyboard
                   break key.

                   errno=EINTR

        SIGHUP  -  This signal is generated when the controlling
                   terminal exits.

                   errno=ENOTTY

        SIGPIPE -  This signal is generated when a program attempts
                   to write to a pipe who's remote end isn't being
                   read by any process. This can happen for example
                   if you have called gl_change_terminal() to
                   redirect output to a pipe hidden under a pseudo
                   terminal.

                   errno=EPIPE

        SIGQUIT -  This signal is generated by the keyboard quit
                   key (usually ^\).

                   errno=EINTR

        SIGABRT -  This signal is generated by the standard C,
                   abort() function. By default it both
                   terminates the process and generates a core
                   dump.

                   errno=EINTR

        SIGTERM -  This is the default signal that the UN*X
                   kill command sends to processes.

                   errno=EINTR

       Note  that in the case of all of the above signals, POSIX mandates that
       by default the process is terminated, with the addition of a core  dump
       in  the  case  of  the  SIGQUIT  signal. In other words, if the calling
       application doesn't override the default handler by supplying  its  own
       signal  handler, receipt of the corresponding signal will terminate the
       application before gl_get_line() returns.

       If gl_get_line() aborts with errno set to EINTR, you can find out  what
       signal caused it to abort, by calling the following function.

         int gl_last_signal(const GetLine *gl);

       This  returns the numeric code (eg. SIGINT) of the last signal that was
       received during the most recent call to gl_get_line(), or -1 if no sig-
       nals were received.

       On  systems  that support it, when a SIGWINCH (window change) signal is
       received, gl_get_line() queries the terminal to find out its new  size,
       redraws the current input line to accomodate the new size, then returns
       to waiting for keyboard input from the user. Unlike other signals, this
       signal isn't resent to the application.

       Finally, the following signals cause gl_get_line() to first restore the
       terminal  and  signal  environment  to  that  which  prevailed   before
       gl_get_line() was called, then resend the signal to the application. If
       the process still exists after the  signal  has  been  delivered,  then
       gl_get_line() then re-establishes its own signal handlers, switches the
       terminal back to raw mode, redisplays the input line, and goes back  to
       awaiting terminal input from the user.

        SIGCONT    -  This signal is generated when a suspended
                      process is resumed.

        SIGPOLL    -  On SVR4 systems, this signal notifies the
                      process of an asynchronous I/O event. Note
                      that under 4.3+BSD, SIGIO and SIGPOLL are
                      the same. On other systems, SIGIO is ignored
                      by default, so gl_get_line() doesn't
                      trap it by default.

        SIGPWR     -  This signal is generated when a power failure
                      occurs (presumably when the system is on a
                      UPS).

        SIGALRM    -  This signal is generated when a timer
                      expires.

        SIGUSR1    -  An application specific signal.

        SIGUSR2    -  Another application specific signal.

        SIGVTALRM  -  This signal is generated when a virtual
                      timer expires (see man setitimer(2)).

        SIGXCPU    -  This signal is generated when a process
                      exceeds its soft CPU time limit.

        SIGXFSZ    -  This signal is generated when a process
                      exceeds its soft file-size limit.

        SIGTSTP    -  This signal is generated by the terminal
                      suspend key, which is usually ^Z, or the
                      delayed terminal suspend key, which is
                      usually ^Y.

        SIGTTIN    -  This signal is generated if the program
                      attempts to read from the terminal while the
                      program is running in the background.

        SIGTTOU    -  This signal is generated if the program
                      attempts to write to the terminal while the
                      program is running in the background.


       Obviously not all of the above signals are supported on all systems, so
       code to support them is conditionally compiled into the tecla  library.

       Note  that  if SIGKILL or SIGPOLL, which by definition can't be caught,
       or any of the hardware generated exception signals,  such  as  SIGSEGV,
       SIGBUS  and  SIGFPE, are received and unhandled while gl_get_line() has
       the terminal in raw mode, the program will be  terminated  without  the
       terminal  having been restored to a usable state. In practice, job-con-
       trol shells usually reset the terminal settings when a  process  relin-
       quishes  the controlling terminal, so this is only a problem with older
       shells.


CUSTOMIZED SIGNAL HANDLING

       The previous section listed the signals  that  gl_get_line()  traps  by
       default,  and described how it responds to them. This section describes
       how to both add and remove signals from the list  of  trapped  signals,
       and  how to specify how gl_get_line() should respond to a given signal.

       If you don't need gl_get_line() to do anything in response to a  signal
       that  it  normally  traps, you can tell to gl_get_line() to ignore that
       signal by calling gl_ignore_signal().

         int gl_ignore_signal(GetLine *gl, int signo);

       The signo argument is the number of the signal (eg.  SIGINT)  that  you
       want  to  have  ignored. If the specified signal isn't currently one of
       those being trapped, this function does nothing.

       The gl_trap_signal() function allows you to either add a new signal  to
       the  list that gl_get_line() traps, or modify how it responds to a sig-
       nal that it already traps.

         int gl_trap_signal(GetLine *gl, int signo, unsigned flags,
                            GlAfterSignal after, int errno_value);

       The signo argument is the number of the signal that you  wish  to  have
       trapped. The flags argument is a set of flags which determine the envi-
       ronment in which the application's signal handler is invoked, the after
       argument  tells gl_get_line() what to do after the application's signal
       handler returns, and errno_value tells gl_get_line() what to set  errno
       to if told to abort.

       The  flags  argument  is  a bitwise OR of zero or more of the following
       enumerators:

         GLS_RESTORE_SIG  -  Restore the caller's signal
                             environment while handling the
                             signal.

         GLS_RESTORE_TTY  -  Restore the caller's terminal settings
                             while handling the signal.

         GLS_RESTORE_LINE -  Move the cursor to the start of the
                             line following the input line before
                             invoking the application's signal
                             handler.

         GLS_REDRAW_LINE  -  Redraw the input line when the
                             application's signal handler returns.

         GLS_UNBLOCK_SIG  -  Normally, if the calling program has
                             a signal blocked (man sigprocmask),
                             gl_get_line() does not trap that
                             signal. This flag tells gl_get_line()
                             to trap the signal and unblock it for
                             the duration of the call to
                             gl_get_line().

         GLS_DONT_FORWARD -  If this flag is included, the signal
                             will not be forwarded to the signal
                             handler of the calling program.

       Two commonly useful flag combinations are also enumerated as follows:

         GLS_RESTORE_ENV   = GLS_RESTORE_SIG | GLS_RESTORE_TTY |
                             GLS_REDRAW_LINE

         GLS_SUSPEND_INPUT = GLS_RESTORE_ENV | GLS_RESTORE_LINE


       If your signal handler, or the default system signal handler  for  this
       signal, if you haven't overridden it, never either writes to the termi-
       nal, nor suspends or terminates  the  calling  program,  then  you  can
       safely set the flags argument to 0.

       If your signal handler always writes to the terminal, reads from it, or
       suspends or terminates the program, you should specify the flags  argu-
       ment as GL_SUSPEND_INPUT, so that:

       1. The cursor doesn't get left in the middle of the input
          line.
       2. So that the user can type in input and have it echoed.
       3. So that you don't need to end each output line with
          \r\n, instead of just \n.

       The  GL_RESTORE_ENV combination is the same as GL_SUSPEND_INPUT, except
       that it doesn't move the cursor, and if  your  signal  handler  doesn't
       read  or write anything to the terminal, the user won't see any visible
       indication that a signal was caught. This can be useful if you  have  a
       signal  handler  that  only  occasionally writes to the terminal, where
       using GL_SUSPEND_LINE would cause the input line  to  be  unnecessarily
       duplicated  when nothing had been written to the terminal.  Such a sig-
       nal handler, when it does write to the  terminal,  should  be  sure  to
       start a new line at the start of its first write, by writing a new line
       before returning. If the signal arrives while the user  is  entering  a
       line  that only occupies a signal terminal line, or if the cursor is on
       the last terminal line of a longer input line, this will have the  same
       effect  as  GL_SUSPEND_INPUT. Otherwise it will start writing on a line
       that already contains part of the displayed input line.   This  doesn't
       do any harm, but it looks a bit ugly, which is why the GL_SUSPEND_INPUT
       combination is better if you know that you are always going to be writ-
       ting to the terminal.

       The  after argument, which determines what gl_get_line() does after the
       application's signal handler returns (if it returns), can take any  one
       of the following values:

         GLS_RETURN   - Return the completed input line, just as
                        though the user had pressed the return
                        key.

         GLS_ABORT    - Cause gl_get_line() to abort. When
                        this happens, gl_get_line() returns
                        NULL, and a following call to
                        gl_return_status() will return
                        GLR_SIGNAL. Note that if the
                        application needs errno always to
                        have a meaningful value when
                        gl_get_line() returns NULL,
                        the callback function should set
                        errno appropriately.
         GLS_CONTINUE - Resume command line editing.

       The  errno_value argument is intended to be combined with the GLS_ABORT
       option, telling gl_get_line() what to set the standard  errno  variable
       to  before returning NULL to the calling program. It can also, however,
       be used with the GL_RETURN option, in case you wish to have  a  way  to
       distinguish  between  an  input  line that was entered using the return
       key, and one that was entered by the receipt of a signal.


RELIABLE SIGNAL HANDLING

       Signal handling is suprisingly hard to do reliably without race  condi-
       tions.  In gl_get_line() a lot of care has been taken to allow applica-
       tions to perform reliable signal handling  around  gl_get_line().  This
       section explains how to make use of this.

       As  an  example of the problems that can arise if the application isn't
       written correctly, imagine that one's application has a  SIGINT  signal
       handler that sets a global flag. Now suppose that the application tests
       this flag just before invoking gl_get_line(). If a SIGINT  signal  hap-
       pens  to  be received in the small window of time between the statement
       that tests the value  of  this  flag,  and  the  statement  that  calls
       gl_get_line(), then gl_get_line() will not see the signal, and will not
       be interrupted. As a result, the application won't be able  to  respond
       to  the  signal  until  the  user gets around to finishing entering the
       input line and gl_get_line() returns.  Depending  on  the  application,
       this  might  or might not be a disaster, but at the very least it would
       puzzle the user.

       The way to avoid such problems is to do the following.

       1. If needed, use the gl_trap_signal() function to
          configure gl_get_line() to abort when important
          signals are caught.

       2. Configure gl_get_line() such that if any of the
          signals that it catches are blocked when
          gl_get_line() is called, they will be unblocked
          automatically during times when gl_get_line() is
          waiting for I/O. This can be done either
          on a per signal basis, by calling the
          gl_trap_signal() function, and specifying the
          GLS_UNBLOCK attribute of the signal, or globally by
          calling the gl_catch_blocked() function.


            void gl_catch_blocked(GetLine *gl);


          This function simply adds the GLS_UNBLOCK attribute
          to all of the signals that it is currently configured to
          trap.

       3. Just before calling gl_get_line(), block delivery
          of all of the signals that gl_get_line() is
          configured to trap. This can be done using the POSIX
          sigprocmask() function in conjunction with the
          gl_list_signals() function.


             int gl_list_signals(GetLine *gl, sigset_t *set);


          This function returns the set of signals that it is
          currently configured to catch in the set argument,
          which is in the form required by sigprocmask().

       4. In the example, one would now test the global flag that
          the signal handler sets, knowing that there is now no
          danger of this flag being set again until
          gl_get_line() unblocks its signals while performing
          I/O.

       5. Eventually gl_get_line() returns, either because
          a signal was caught, an error occurred, or the user
          finished entering their input line.

       6. Now one would check the global signal flag again, and if
          it is set, respond to it, and zero the flag.

       7. Use sigprocmask() to unblock the signals that were
          blocked in step 3.

       The same technique can be used around certain POSIX signal-aware  func-
       tions,  such  as  sigsetjmp()  and sigsuspend(), and in particular, the
       former of these two functions can be  used  in  conjunction  with  sig-
       longjmp() to implement race-condition free signal handling around other
       long-running system calls. The way to do this, is  explained  next,  by
       showing how gl_get_line() manages to reliably trap signals around calls
       to functions like read() and select() without race conditions.

       The first thing that gl_get_line() does, whenever it is called,  is  to
       use  the  POSIX  sigprocmask() function to block the delivery of all of
       the signals that it is currently configured to catch. This is redundant
       if  the  application  has already blocked them, but it does no harm. It
       undoes this step just before returning.

       Whenever gl_get_line() needs to call read() or  select()  to  wait  for
       input  from  the  user,  it first calls the POSIX sigsetjmp() function,
       being sure to specify a non-zero value for its savesigs argument.   The
       reason for the latter argument will become clear shortly.

       If sigsetjmp() returns zero, gl_get_line() then does the following.


       a. It uses the POSIX sigaction() function to register
          a temporary signal handler to all of the signals that it
          is configured to catch. This signal handler does two
          things.

          1. It records the number of the signal that was received
             in a file-scope variable.

          2. It then calls the POSIX siglongjmp()
             function using the buffer that was passed to
             sigsetjmp() for its first argument, and
             a non-zero value for its second argument.

          When this signal handler is registered, the sa_mask
          member of the struct sigaction act argument of the
          call to sigaction() is configured to contain all of
          the signals that gl_get_line() is catching. This
          ensures that only one signal will be caught at once by
          our signal handler, which in turn ensures that multiple
          instances of our signal handler don't tread on each
          other's toes.

       b. Now that the signal handler has been set up,
          gl_get_line() unblocks all of the signals that it
          is configured to catch.

       c. It then calls the read() or select() system
          calls to wait for keyboard input.

       d. If this system call returns (ie. no signal is received),
          gl_get_line() blocks delivery of the signals of
          interest again.

       e. It then reinstates the signal handlers that were
          displaced by the one that was just installed.


       Alternatively,  if sigsetjmp() returns non-zero, this means that one of
       the signals being trapped was caught while the above steps were execut-
       ing. When this happens, gl_get_line() does the following.

       First,  note  that  when  a  call to siglongjmp() causes sigsetjmp() to
       return, provided that the savesigs argument  of  sigsetjmp()  was  non-
       zero, as specified above, the signal process mask is restored to how it
       was when sigsetjmp() was  called.  This  is  the  important  difference
       between  sigsetjmp()  and  the  older  problematic setjmp(), and is the
       essential ingredient that makes it possible to  avoid  signal  handling
       race  conditions.   Because  of  this we are guaranteed that all of the
       signals that we blocked before calling sigsetjmp() are blocked again as
       soon  as any signal is caught. The following statements, which are then
       executed, are thus guaranteed to be executed without any  further  sig-
       nals being caught.

       1. If so instructed by the gl_get_line() configuration
          attributes of the signal that was caught,
          gl_get_line() restores the terminal attributes to
          the state that they had when gl_get_line() was
          called. This is particularly important for signals that
          suspend or terminate the process, since otherwise the
          terminal would be left in an unusable state.

       2. It then reinstates the application's signal handlers.

       3. Then it uses the C standard-library raise()
          function to re-send the application the signal that
          was caught.

       3. Next it unblocks delivery of the signal that we just
          sent. This results in the signal that was just sent
          via raise(), being caught by the application's
          original signal handler, which can now handle it as it
          sees fit.

       4. If the signal handler returns (ie. it doesn't terminate
          the process), gl_get_line() blocks delivery of the
          above signal again.

       5. It then undoes any actions performed in the first of the
          above steps, and redisplays the line, if the signal
          configuration calls for this.

       6. gl_get_line() then either resumes trying to
          read a character, or aborts, depending on the
          configuration of the signal that was caught.

       What  the above steps do in essence is to take asynchronously delivered
       signals and handle them synchronously, one at a time, at a point in the
       code where gl_get_line() has complete control over its environment.


THE TERMINAL SIZE

       On  most  systems  the combination of the TIOCGWINSZ ioctl and the SIG-
       WINCH signal is used to maintain an accurate idea of the terminal size.
       The  terminal  size  is  newly queried every time that gl_get_line() is
       called and whenever a SIGWINCH signal is received.

       On the few systems where this mechanism  isn't  available,  at  startup
       new_GetLine()  first  looks for the LINES and COLUMNS environment vari-
       ables.  If these aren't found, or they contain unusable values, then if
       a  terminal information database like terminfo or termcap is available,
       the default size of the terminal is looked up in this database. If this
       too fails to provide the terminal size, a default size of 80 columns by
       24 lines is used.

       Even on systems that do support ioctl(TIOCGWINSZ), if the  terminal  is
       on the other end of a serial line, the terminal driver generally has no
       way of detecting when a resize occurs or of querying what  the  current
       size  is.  In  such  cases  no SIGWINCH is sent to the process, and the
       dimensions returned by ioctl(TIOCGWINSZ) aren't correct. The  only  way
       to  handle  such  instances is to provide a way for the user to enter a
       command that tells the remote system what the new size is. This command
       would  then  call the gl_set_term_size() function to tell gl_get_line()
       about the change in size.


         int gl_set_term_size(GetLine *gl, int ncolumn, int nline);


       The ncolumn and nline arguments are used to specify the new  dimensions
       of  the  terminal, and must not be less than 1. On systems that do sup-
       port ioctl(TIOCGWINSZ), this function first calls ioctl(TIOCSWINSZ)  to
       tell  the  terminal  driver  about  the change in size. In non-blocking
       server-I/O mode, if a line is currently being input, the input line  is
       then redrawn to accomodate the changed size. Finally the new values are
       recorded in gl for future use by gl_get_line().

       The gl_terminal_size() function allows you to query the current size of
       the  terminal,  and  install an alternate fallback size for cases where
       the size isn't available.  Beware  that  the  terminal  size  won't  be
       available  if  reading from a pipe or a file, so the default values can
       be important even on systems that do support ways of  finding  out  the
       terminal size.

         typedef struct {
           int nline;        /* The terminal has nline lines */
           int ncolumn;      /* The terminal has ncolumn columns */
         } GlTerminalSize;

         GlTerminalSize gl_terminal_size(GetLine *gl,
                                         int def_ncolumn,
                                         int def_nline);

       This  function  first  updates gl_get_line()'s fallback terminal dimen-
       sions, then records its findings in the return value.

       The def_ncolumn and def_nline specify the default  number  of  terminal
       columns  and  lines to use if the terminal size can't be determined via
       ioctl(TIOCGWINSZ) or environment variables.


HIDING WHAT YOU TYPE

       When entering sensitive information, such as passwords, it is best  not
       to  have  the  text that you are entering echoed on the terminal.  Fur-
       thermore, such text should not be recorded in the history  list,  since
       somebody  finding  your  terminal  unattended  could then recall it, or
       somebody snooping through your directories could see it in your history
       file. With this in mind, the gl_echo_mode() function allows you to tog-
       gle on and off the display and archival of  any  text  that  is  subse-
       quently entered in calls to gl_get_line().


         int gl_echo_mode(GetLine *gl, int enable);


       The enable argument specifies whether entered text should be visible or
       not. If it is 0, then subsequently entered lines will not be visible on
       the terminal, and will not be recorded in the history list. If it is 1,
       then subsequent input lines will be displayed as they are entered,  and
       provided  that  history  hasn't  been  turned off via a call to gl_tog-
       gle_history(), then they will also be archived  in  the  history  list.
       Finally,  if  the  enable argument is -1, then the echoing mode is left
       unchanged, which allows you to non-destructively query the current set-
       ting  via the return value. In all cases, the return value of the func-
       tion is 0 if echoing was disabled before the function was called, and 1
       if it was enabled.

       When  echoing  is  turned  off,  note that although tab completion will
       invisibly complete your prefix as far as  possible,  ambiguous  comple-
       tions will not be displayed.


SINGLE CHARACTER QUERIES

       Using  gl_get_line() to query the user for a single character reply, is
       inconvenient for the user, since they must hit the enter or return  key
       before  the  character that they typed is returned to the program. Thus
       the gl_query_char() function has been  provided  for  single  character
       queries like this.


         int gl_query_char(GetLine *gl, const char *prompt,
                           char defchar);


       This function displays the specified prompt at the start of a new line,
       and waits for the user to type a character. When the user types a char-
       acter, gl_query_char() displays it to the right of the prompt, starts a
       newline, then returns the character to the calling program. The  return
       value  of the function is the character that was typed. If the read had
       to be aborted for some reason, EOF is returned instead. In  the  latter
       case, the application can call the previously documented gl_return_sta-
       tus(), to find out what went wrong. This could, for example, have  been
       the  reception of a signal, or the optional inactivity timer going off.

       If the user simply hits enter, the value of  the  defchar  argument  is
       substituted.  This  means  that  when  the  user hits either newline or
       return, the character specified in  defchar,  is  displayed  after  the
       prompt,  as  though the user had typed it, as well as being returned to
       the calling application. If such a replacement is not important, simply
       pass '0 as the value of defchar.

       If  the  entered character is an unprintable character, it is displayed
       symbolically. For example, control-A is displayed as ^A, and characters
       beyond 127 are displayed in octal, preceded by a backslash.

       As with gl_get_line(), echoing of the entered character can be disabled
       using the gl_echo_mode() function.

       If the calling process is suspended while waiting for the user to  type
       their  response,  the  cursor is moved to the line following the prompt
       line, then when the process resumes, the  prompt  is  redisplayed,  and
       gl_query_char() resumes waiting for the user to type a character.

       Note that in non-blocking server mode, (see gl_io_mode),
       if an incomplete input line is  in  the  process  of  being  read  when
       gl_query_char()  is  called,  the  partial input line is discarded, and
       erased from the terminal, before the new prompt is displayed. The  next
       call to gl_get_line() will thus start editing a new line.


READING RAW CHARACTERS

       Whereas  the  gl_query_char()  function  visibly prompts the user for a
       character, and displays what they typed,  the  gl_read_char()  function
       reads a signal character from the user, without writing anything to the
       terminal, or perturbing any incompletely entered input line. This means
       that it can be called not only from between calls to gl_get_line(), but
       also from callback functions that the application has registered to  be
       called by gl_get_line().


         int gl_read_char(GetLine *gl);


       On  success,  the  return value of gl_read_char() is the character that
       was read. On failure, EOF is returned, and the gl_return_status() func-
       tion  can  be called to find out what went wrong. Possibilities include
       the optional inactivity timer going off, the receipt of a  signal  that
       is configured to abort gl_get_line(), or terminal I/O blocking, when in
       non-blocking server-I/O mode.

       Beware that certain keyboard keys, such as function  keys,  and  cursor
       keys,  usually generate at least 3 characters each, so a single call to
       gl_read_char() won't be enough to identify such keystrokes.


CLEARING THE TERMINAL

       The calling program can clear the terminal by  calling  gl_erase_termi-
       nal(). In non-blocking server-I/O mode, this function also arranges for
       the current input line to be redrawn from scratch when gl_get_line() is
       next called.


         int gl_erase_terminal(GetLine *gl);



DISPLAYING TEXT DYNAMICALLY

       Between calls to gl_get_line(), the gl_display_text() function provides
       a convenient way to display  paragraphs  of  text,  left-justified  and
       split  over  one or more terminal lines according to the constraints of
       the current width of the terminal. Examples of the use of this function
       may  be  found in the demo programs, where it is used to display intro-
       ductions. In those examples the advanced use of optional prefixes, suf-
       fixes  and  filled  lines  to draw a box around the text is also illus-
       trated.


         int gl_display_text(GetLine *gl, int indentation,
                             const char *prefix,
                             const char *suffix, int fill_char,
                             int def_width, int start,
                             const char *string);

       If gl isn't currently connected to a terminal, for example if the  out-
       put of a program that uses gl_get_line() is being piped to another pro-
       gram or redirected to a file, then the value of the def_width parameter
       is used as the terminal width.

       The  indentation  argument specifies the number of characters to use to
       indent each line of ouput. The fill_char argument specifies the charac-
       ter that will be used to perform this indentation.

       The  prefix argument can either be NULL, or be a string to place at the
       beginning of each new line (after  any  indentation).   Similarly,  the
       suffix  argument can either be NULL, or be a string to place at the end
       of each line. The suffix is placed flush against the right edge of  the
       terminal,  and  any space between its first character and the last word
       on that line is filled with the character specified via  the  fill_char
       argument.  Normally the fill-character is a space.

       The  start  argument  tells  gl_display_text() how many characters have
       already been written to the current terminal line, and  thus  tells  it
       the  starting  column  index  of the cursor.  Since the return value of
       gl_display_text() is the ending column index of the cursor, by  passing
       the  return value of one call to the start argument of the next call, a
       paragraph that is broken between more than one string can  be  composed
       by  calling  gl_display_text() for each successive portion of the para-
       graph. Note that literal newline characters are necessary at the end of
       each paragraph to force a new line to be started.

       On error, gl_display_text() returns -1.


CALLBACK FUNCTION FACILITIES

       Unless  otherwise  stated,  callback  functions, such as tab completion
       callbacks and event callbacks should not call  any  functions  in  this
       module.  The following functions, however, are designed specifically to
       be used by callback functions.

       Calling  the  gl_replace_prompt()  function  from  a   callback   tells
       gl_get_line()  to display a different prompt when the callback returns.
       Except in non-blocking server mode, it has no effect  if  used  between
       calls   to   gl_get_line().   In  non-blocking  server  mode  (see  the
       gl_io_mode man page, when  used  between  two  calls  to
       gl_get_line()  that  are  operating on the same input line, the current
       input line will be re-drawn with the new prompt on the  following  call
       to gl_get_line().


         void gl_replace_prompt(GetLine *gl, const char *prompt);



INTERNATIONAL CHARACTER SETS

       Since  libtecla version 1.4.0, gl_get_line() has been 8-bit clean. This
       means that all 8-bit characters that are printable in the  user's  cur-
       rent  locale  are  now  displayed verbatim and included in the returned
       input line.  Assuming that the calling  program  correctly  contains  a
       call like the following,

         setlocale(LC_CTYPE, "");

       then  the  current locale is determined by the first of the environment
       variables LC_CTYPE, LC_ALL, and LANG, that is found to contain a  valid
       locale  name.  If  none  of these variables are defined, or the program
       neglects to call setlocale, then the default C locale is used, which is
       US  7-bit  ASCII.  On  most  unix-like platforms, you can get a list of
       valid locales by typing the command:

         locale -a

       at the shell prompt. Further documentation on how the user can make use
       of  this  to  enter  international  characters  can  be  found  in  the
       tecla man page.


THREAD SAFETY

       In a multi-threaded program, you should use the libtecla_r.a version of
       the  library.  This  uses reentrant versions of system functions, where
       available. Unfortunately neither terminfo nor termcap were designed  to
       be reentrant, so you can't safely use the functions of the getline mod-
       ule in multiple threads (you can use the  separate  file-expansion  and
       word-completion  modules in multiple threads, see the corresponding man
       pages for details). However due to the use of POSIX reentrant functions
       for looking up home directories etc, it is safe to use this module from
       a single thread of a multi-threaded program, provided that  your  other
       threads don't use any termcap or terminfo functions.


FILES

       libtecla.a      -    The tecla library
       libtecla.h      -    The tecla header file.
       ~/.teclarc      -    The personal tecla customization file.


SEE ALSO

       libtecla, gl_io_mode, tecla, ef_expand_file,
       cpl_complete_word, pca_lookup_file


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                    gl_get_line
./libtecla/html/index.html0100644000076400007640000001130110141253613014062 0ustar mcsmcsThe tecla command-line editing library.

The Tecla command-line editing library.

The tecla library provides UNIX and LINUX programs with interactive command line editing facilities, similar to those of the UNIX tcsh shell. In addition to simple command-line editing, it supports recall of previously entered command lines, TAB completion of file names or other tokens, and in-line wild-card expansion of filenames. The internal functions which perform file-name completion and wild-card expansion are also available externally for optional use by programs.

In addition, the library includes a path-searching module. This allows an application to provide completion and lookup of files located in UNIX style paths. Although not built into the line editor by default, it can easily be called from custom tab-completion callback functions. This was originally conceived for completing the names of executables and providing a way to look up their locations in the user's PATH environment variable, but it can easily be asked to look up and complete other types of files in any list of directories.

Note that special care has been taken to allow the use of this library in threaded programs. The option to enable this is discussed in the Makefile, and specific discussions of thread safety are presented in the included man pages.

The current version is version 1.6.1. This may be obtained from:

http://www.astro.caltech.edu/~mcs/tecla/libtecla-1.6.1.tar.gz

For the sake of automated scripts, the following URL always points to the latest version. Note that the version number can be found in the README file.

http://www.astro.caltech.edu/~mcs/tecla/libtecla.tar.gz

The library is distributed under a permissive non-copyleft free software license (the X11 license with the name of the copyright holder changed). This is compatible with, but not as restrictive as the GNU GPL.

Release notes

The list of major changes that accompany each new release can be found here.

Modifications

The gory details of changes in the latest and previous versions of the library can be found here.

Library documentation

The following are html versions of the libtecla man pages:

Portability

In principle, the standard version of the library should compile without any problems on any UNIX or UNIX like system. So far it has been reported to work on the following systems:
  Sun Solaris 2.5,2.6,7,8,9 with any of gcc, Sun C, or g++.
  Mandrake Linux 7.1 etc.., gcc
  Red Hat Linux 7 etc.., gcc
  Fedora Core 1, gcc
  Suse Linux 6.4, gcc
  IBM AIX 4.3.3, gcc
  HP-UX 10.20, HP-UX 11, gcc, c89
  FreeBSD, gcc
  Alpha OSF1, cc, gcc
  Mac OS X
  Cygwin (running under Windows)
  QNX
  NetBSD 1.6, 386, gcc
  SGI IRIX 6.5
There haven't been many reports concerning the POSIX reentrant version, so the absence of any of the above from the following list of systems on which the reentrant version is known to work, shouldn't be taken as an indication that the reentrant version doesn't work.
  Sun Solaris 2.5,2.6,7,8,9 with any of gcc, Sun C, or g++.
  Mandrake Linux, gcc
  RedHat Linux, gcc
  Fedora Core, gcc
  SuSE Linux, gcc
  HP-UX 11, gcc
  IBM AIX 4.3.3, gcc
  Alpha OSF1, cc
  SGI IRIX 6.5
The only system that is known to have issues with the reentrant version of the library is SCO UnixWare 7.1.1. The problem is in the system provided signal.h, which breaks when POSIX_C_SOURCE is defined. It has been reported that this can be "fixed" by editing signal.h.

If you compile the library on a system that isn't mentioned above, please send E-mail to mcs@astro.caltech.edu.


Martin Shepherd (31-Oct-2004) ./libtecla/html/libtecla.html0100644000076400007640000001442110141252544014542 0ustar mcsmcs Manual Page
libtecla                                libtecla



NAME

       libtecla - An interactive command-line input library.

SYNOPSIS

       @CC@ ... -ltecla -lcurses


DESCRIPTION

       The tecla library provides programs with interactive command line edit-
       ing facilities, similar to those of the unix tcsh shell. In addition to
       simple  command-line  editing, it supports recall of previously entered
       command lines, TAB completion of file names or other  tokens,  and  in-
       line  wild-card  expansion  of  filenames. The internal functions which
       perform file-name completion and wild-card expansion are also available
       externally for optional use by the calling program.

       The  various  parts  of the library are documented in the following man
       pages:

         tecla              -  Use level documentation of the
                               command-line editing facilities
                               provided by gl_get_line().
         gl_get_line        -  The interactive line-input module.
         gl_io_mode         -  How to use gl_get_line() in an
                               incremental, non-blocking fashion.
         cpl_complete_word  -  The word completion module.
         ef_expand_file     -  The filename expansion module.
         pca_lookup_file    -  A directory-list based filename
                               lookup and completion module.

       In addition there is one  optional  application  distributed  with  the
       library:

         enhance            -  Add command-line editing to third
                                  party applications.


THREAD SAFETY

       If  the  library  is compiled with -D_POSIX_C_SOURCE=199506L, reentrant
       versions of as many functions as possible are used. This includes using
       getpwuid_r() and getpwnam_r() instead of getpwuid() and getpwnam() when
       looking up the home directories of specific users in the password  file
       (for  ~user/ expansion), and readdir_r() instead of readdir() for read-
       ing directory entries when doing  filename  completion.  The  reentrant
       version of the library is usually called libtecla_r.a instead of libte-
       cla.a, so if only the latter is available, it probably isn't  the  cor-
       rect version to link with threaded programs.

       Reentrant  functions  for  iterating  through  the password file aren't
       available, so when the library is compiled to be reentrant, TAB comple-
       tion  of  incomplete  usernames  in ~username/ expressions is disabled.
       This doesn't disable expansion of complete ~username expressions, which
       can  be  done  reentrantly, or expansion of the parts of filenames that
       follow them, so this doesn't remove much functionality.

       The terminfo functions setupterm(), tigetstr(), tigetnum() and  tputs()
       also aren't reentrant, but very few programs will want to interact with
       multiple terminals, so this shouldn't prevent this library  from  being
       used in threaded programs.


LIBRARY VERSION NUMBER

       The  version  number  of the library can be queried using the following
       function.

        void libtecla_version(int *major, int *minor, int *micro);


       On return, this function records the three components of  the  libtecla
       version  number  in  *major,  *minor, *micro. The formal meaning of the
       three components is as follows.


        major - Incrementing this number implies that a change has
                been made to the library's public interface, which
                makes it binary incompatible  with programs that
                were linked with previous shared versions of the
                tecla library.

        minor - This number is incremented by one whenever
                additional functionality, such as new functions or
                modules, are added to the library.

        micro - This is incremented whenever modifications to the
                library are made which make no changes to the
                public interface, but which fix bugs and/or improve
                the behind-the-scenes implementation.



TRIVIA

       In Spanish, a "tecla" is the key of a keyboard. Since this library cen-
       ters  on  keyboard  input,  and  given that I wrote much of the library
       while working in Chile, this seemed like a suitable name.


FILES

       libtecla.a    -   The tecla library.
       libtecla.h    -   The tecla header file.
       ~/.teclarc    -   The tecla personal customization file.


SEE ALSO

       gl_get_line, tecla, gl_io_mode, ef_expand_file,
       cpl_complete_word, pca_lookup_file, enhance


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)


ACKNOWLEDGMENTS

       Markus Gyger  - Lots of assistance, including help with
                       shared libraries, configuration information,
                       particularly for Solaris; modifications to
                       support C++ compilers, improvements for ksh
                       users, faster cursor motion, output
                       buffering, and changes to make gl_get_line()
                       8-bit clean.
       Mike MacFaden - Suggestions, feedback and testing that led
                       to many of the major new functions that were
                       added in version 1.4.0.
       Tim Eliseo    - Many vi-mode bindings and fixes.



                                                       libtecla
./libtecla/html/release.html0100644000076400007640000007037010141252550014405 0ustar mcsmcsThe tecla library release notes
This file lists major changes which accompany each new release.

Version 1.6.1:

  This is primarily a minor bug-fix release.

  One added feature is the ability to call gl_normal_io() from
  callbacks registered by gl_watch_fd() and
  gl_inactivity_timeout(). This allows these callbacks to cleanly
  suspend line editing before either reading from the terminal, or
  writing to the terminal; and then subsequently causes the input line
  to be automatically redisplayed, and line-editing to be resumed by
  gl_get_line(), as soon as the callback returns.

  Another minor change is that if the terminal type specified in the
  TERM environment variable is set to "dumb", gl_get_line() now treats
  the terminal as though it were a non-interactive stream, rather than
  treating it as a VT100-compatible terminal. This means that it
  doesn't either prompt for input, or perform any command-line
  editing, even when it really is interacting with a terminal. This is
  aimed at the rare situation where a third-pary program that connects
  to libtecla through an embedded pseudo-terminal, needs to be forced
  to behave as though it weren't talking to a terminal, in order that
  it be useable in non-interactive scripts.

  Note that in the previous release, the optional configuration
  function, gl_tty_signals(), was incorrectly swapping the suspend and
  terminal signal handlers before installing them.

  A configuration problem that prevented select() from being used
  under MacOS X, has been fixed.

  Although not documented in the man page, it was meant to be possible
  to take the input line that one call to gl_get_line() returned, and
  ask the next call to gl_get_line() to present it back to the user
  for re-editing, simply by passing the pointer returned by one call
  to gl_get_line() as the start_line argument of the next call to
  gl_get_line(). This feature unfortunately stopped working in 1.6.0,
  so this release restores it, and officially documents it in the man
  page documentation of gl_get_line().

  In the previous version of the library, calling gl_terminal_size()
  on a system without SIGWINCH support, would crash the
  application. This has been fixed.

  Libtecla now apparently compiles cleanly under IRIX.

Version 1.6.0:

  This release is primarily a bug-fix release. However there are also
  four new functions, so the minor version number has been
  incremented to reflect this.

  Two of the new functions are gl_automatic_history() and
  gl_append_history(). The former of these functions allows the
  application to tell gl_get_line() not to automatically archive
  entered lines in the history list. The second of these functions
  allows the application to explicitly append a line to the history
  list. Thus together, these two functions allow the calling
  application to take over control of what is placed in the history
  list.

  The third new function is gl_query_char(), which prompts the user
  for a single character reply, which the user can then type without
  having to hit return to enter it. Unless echoing is disabled, the
  character that is entered is then displayed after the prompt,
  and a newline is started.

  Finally, the 4th new function is gl_read_char(), which also reads
  a single character from the user, but doesn't prompt the user, write
  anything to the terminal, or disturb any partially entered input
  line. It is thus safe to call this function not only from between
  calls to gl_get_line(), but also from application callback
  functions, even if gl_normal_io() hasn't been called.

  When using the history-search-backwards or history-search-forwards
  actions, if the search prefix that the user typed, contains any of
  the *,? or [ globbing characters, it is now treated as a glob
  pattern to be matched against historical lines, instead of a simple
  prefix.

  I have added a --without-file-system option to the configure
  script. This is intended for use in embedded systems that either
  don't have filesystems, or where the file-system code in libtecla is
  seen as unwanted bloat. See the INSTALL document for details.

  Similarly, I also added a --without-file-actions option to the
  configure script. This allows the application author/installer to
  prevent users of gl_get_line() from accessing the filesystem with
  the builtin actions of gl_get_line(). It does this by removing a
  number of action functions, such as expand-filename, and list-glob,
  and by changing the default behavior of other actions, such as
  complete-word and list-or-eof, to show no completions.

  Now to the bugs that have been fixed. Version 1.5.0 had a lot of big
  internal changes, so there are a number of bugs that needed to be
  fixed.  There was a bug which caused a crash if gl_load_history()
  was called multiple times. There was another bug which caused a
  prompt not to be displayed on the next line after switching from
  reading input from a file to reading from the terminal. Also, in
  tecla configuration files, backslash escaped characters within
  key-binding key-sequences weren't being escaped. Thus ^\\ got
  interpretted as a control-\ followed by a \ character instead of as
  a control-\. There was a bug in the history recall mechanism which
  caused the search prefix to be forgotten in certain complicated
  usage scenarios. There was a minor memory leak in the
  gl_configure_getline() function. Finally, if gl_get_line() was
  aborted by a signal, or any other abnormal event, the value of errno
  which originally indicated what had happened, got zeroed by the
  code that restored the terminal to a usable state. Thus the
  application couldn't figure out what had caused the error, apart
  from by looking at gl_return_status(). All of these bugs have been
  fixed.

  In the Makefile, there were a number of places where install-sh was
  invoked without a path prefix. This has now been remedied.

  A fully functional workaround for a bug in Solaris' terminal I/O
  code has also been implemented. This bug, which only manifested
  itself in libtecla's uncommonly used non-blocking server I/O mode,
  caused characters entered while in normal I/O mode, between calls to
  gl_get_line() to be invisible to the next call to gl_get_line(),
  until the user typed at least one more key after raw terminal mode
  was restored.

  The Gnu autoconf config.guess and config.sub scripts have been
  updated to their latest versions. Apparently the old versions that I
  was previously using were too old to know about certain BSD ports.
  
Version 1.5.0:

  This release includes several major new features for those using
  gl_get_line(), shared library support in Darwin, better cross
  compilation support, and various minor bug fixes.

  The biggest new feature is the option of a non-blocking I/O mode, in
  which gl_get_line() can safely be called from an application's
  external event-loop to incrementally read input lines from the user.
  This feature is documented in the gl_io_mode(3) man page.

  In addition, there is now support for the definition of additional
  word-completion action functions, which can then be bound to
  different keys. See the documentation of the gl_completion_action()
  function in the gl_get_line(3) man page.

  Externally defined action functions can also be defined, although
  presently they don't have write access to the input line, so they
  are restricted to operations that display information text to the
  terminal, or modify the environment of the calling application in
  some way. See the documentation of the gl_register_action() function
  in the gl_get_line(3) man page.

  Some of the non-blocking I/O support functions can also be used for
  improved signal handling in the normal blocking mode. In particular,
  the gl_list_signals() and gl_catch_blocked() functions make it
  easier to write reliable signal handling around gl_get_line(). The
  new "RELIABLE SIGNAL HANDLING" section of the gl_get_line(3) man
  page is intended as an introduction to this subject.

  Programs can now clear the terminal between calls to gl_get_line(),
  by calling the new gl_erase_terminal() function.

  The gl_display_text() function, now used in the demos to display
  introductory banners, is provided for formatting text according to
  the width of the terminal.

  It is now possible to install inactivity timeout callbacks in
  gl_get_line(), using the new gl_inactivity_timeout() function.

  The new gl_set_term_size() function allows the application to
  explicitly set the terminal size, for cases, such as when one is
  using a terminal at the end of a serial lineq, where the terminal
  driver doesn't send the process a SIGWINCH when the terminal size
  changes.

  The new gl_bind_keyseq() function provides a convenient
  alternative to gl_configure_getline(), for binding or unbinding
  one key-sequence at a time.

  gl_get_line()s signal handling, file-descriptor event-handling,
  inactivity-timeout handling and server-mode non-blocking I/O
  features now not only work when input is coming from a terminal, but
  now also work when input is coming from non-interactive streams,
  such as files and pipes.

  The history implementation has been re-written to make it more
  efficient and easier to modify. The biggest user-level change is
  that when recalling history lines using a search prefix, the same
  line is no longer returned more than once in a row.  Previously this
  duplicate elimination only worked when one was recalling a line
  without specifying a search prefix, and this was naively performed
  by preventing neighboring duplicates from existing in the history
  list, rather than by skipping duplicates at search time.

  In previous versions of the library, when gl_get_line() and its
  associated public functions detected invalid arguments, or couldn't
  allocate memory, etc, error messages were written to stderr. This
  isn't appropriate for library functions, so instead of writing such
  messages to stderr, these messages are now recorded in buffers
  within the affected GetLine object. The latest error message can
  then subsequently be queried by calling gl_error_message(). The use
  of errno has also been expanded, and a new function called
  gl_return_status() has been provided to expand on the cause of the
  last return from gl_get_line().

  User level usage and configuration information has now been split
  out of the gl_get_line(3) man page into a separate tecla(7) man
  page. The enhance(3) man page has also been renamed to enhance(1).

  When expanding "~/", gl_get_line() now checks for, and returns the
  value of the HOME environment variable, if it exists, in preference
  to looking up the directory of the current user in the password
  file.

  When the terminal was resized to a narrower width, previous versions
  of gl_get_line() would redraw the line higher up the terminal. This
  bug has been fixed. A bug in history recall has also been fixed, in
  which an error message was being generated if one attempted to
  recall a line while the cursor was at the end of the longest
  possible input line. A more serious bug, in which callbacks
  registered by gl_watch_fd() weren't being called for write-events,
  has also been fixed. Finally, a few minor fixes have been made to
  improve support under QNX and Mac OS X.

  Beware that in this release, much of the underlying code has
  undergone some radical re-work, so although backwards compatibility
  of all documented features has been preserved, there may be some
  lingering bugs that could break existing programs.  So, if you plan
  to use this version in production code, please test it as far as
  possible within your application before releasing it to your
  clients, and as always, please report any unexpected behavior.

Version 1.4.1:

  This is a maintenance release. It includes minor changes to support
  Mac OS X (Darwin), the QNX real-time operating system, and Cygwin
  under Windows. It also fixes an oversight that was preventing the
  tab key from inserting tab characters when users unbound the
  complete-word action from it.

Version 1.4.0:

  The contents of the history list can now be saved and restored with
  the new gl_save_history() and gl_load_history() functions.

  Event handlers can now be registered to watch for and respond to I/O
  on arbitrary file descriptors while gl_get_line() is waiting for
  terminal input from the user. See the gl_get_line(3) man page
  for details on gl_watch_fd().

  As an optional alternative to getting configuration information only
  from ~/.teclarc, the new gl_configure_getline() function allows
  configuration commands to be taken from any of, a string, a
  specified application-specific file, and/or a specified
  user-specific file. See the gl_get_line(3) man page for details.

  The version number of the library can now be queried using the
  libtecla_version() function. See the libtecla(3) man page.

  The new gl_group_history() function allows applications to group
  different types of input line in the history buffer, and arrange for
  only members of the appropriate group to be recalled on a given call
  to gl_get_line(). See the gl_get_line(3) man page.

  The new gl_show_history() function displays the current history list
  to a given stdio output stream. See the gl_get_line(3) man page.

  new_GetLine() now allows you to specify a history buffer size of
  zero, thus requesting that no history buffer be allocated. You can
  subsequently resize or delete the history buffer at any time, by
  calling gl_resize_history(), limit the number of lines that are
  allowed in the buffer by calling gl_limit_history(), clear either
  all history lines from the history list, or just the history lines
  that are associated with the current history group, by calling
  gl_clear_history, and toggle the history mechanism on and off by
  calling gl_toggle_history().

  The new gl_terminal_size() function can be used to query the
  current terminal size. It can also be used to supply a default
  terminal size on systems where no mechanism is available for
  looking up the size.

  The contents and configuration of the history list can now be
  obtained by the calling application, by calling the new
  gl_lookup_history(), gl_state_of_history(), gl_range_of_history()
  and gl_size_of_history() functions. See the gl_get_line(3) man page.

  Echoing of the input line as it is typed, can now be turned on and
  off via the new gl_echo_mode() function. While echoing is disabled,
  newly entered input lines are omitted from the history list.  See
  the gl_get_line(3) man page.

  While the default remains to display the prompt string literally,
  the new gl_prompt_style() function can be used to enable text
  attribute formatting directives in prompt strings, such as
  underlining, bold font, and highlighting directives.

  Signal handling in gl_get_line() is now customizable. The default
  signal handling behavior remains essentially the same, except that
  the SIGTSTP, SIGTTIN and SIGTTOU are now forwarded to the
  corresponding signal handler of the calling program, instead of
  causing a SIGSTOP to be sent to the application.  It is now possible
  to remove signals from the list that are trapped by gl_get_line(),
  as well as add new signals to this list. The signal and terminal
  environments in which the signal handler of the calling program is
  invoked, and what gl_get_line() does after the signal handler
  returns, is now customizable on a per signal basis. You can now also
  query the last signal that was caught by gl_get_line(). This is
  useful when gl_get_line() aborts with errno=EINTR, and you need to
  know which signal caused it to abort.

  Key-sequences bound to action functions can now start with printable
  characters. Previously only keysequences starting with control or
  meta characters were permitted.

  gl_get_line() is now 8-bit clean. If the calling program has
  correctly called setlocale(LC_CTYPE,""), then the user can select an
  alternate locale by setting the standard LC_CTYPE, LC_ALL, or LANG
  environment variables, and international characters can then be
  entered directly, either by using a non-US keyboard, or by using a
  compose key on a standard US keyboard. Note that in locales in which
  meta characters become printable, meta characters no longer match
  M-c bindings, which then have to be entered using their escape-c
  equivalents.  Fortunately most modern terminal emulators either
  output the escape-c version by default when the meta key is used, or
  can be configured to do so (see the gl_get_line(3) man page), so in
  most cases you can continue to use the meta key.

  Completion callback functions can now tell gl_get_line() to return
  the input line immediately after a successful tab completion, simply
  by setting the last character of the optional continuation suffix to
  a newline character (ie. in the call to cpl_add_completion()).

  It is now safe to create and use multiple GetLine objects, albeit
  still only from a single thread. In conjunction with the new
  gl_configure_getline() function, this optionally allows multiple
  GetLine objects with different bindings to be used to implement
  different input modes.

  The edit-mode configuration command now accepts the argument,
  none. This tells gl_get_line() to revert to using just the native
  line editing facilities provided by the terminal driver. This could
  be used if the termcap or terminfo entry of the host terminal were
  badly corrupted.

  Application callback functions invoked by gl_get_line() can now
  change the displayed prompt using the gl_replace_prompt() function.

  Their is now an optional program distributed with the library. This
  is a beta release of a program which adds tecla command-line editing
  to virtually any third party application without the application
  needing to be linked to the library. See the enhance(3) man page for
  further details. Although built and installed by default, the
  INSTALL document explains how to prevent this.

  The INSTALL document now explains how you can stop the demo programs
  from being built and installed.

  NetBSD/termcap fixes. Mike MacFaden reported two problems that he
  saw when compiling libtecla under NetBSD. Both cases were related to
  the use of termcap.  Most systems use terminfo, so this problem has
  gone unnoticed until now, and won't have affected the grand majority
  of users.  The configure script had a bug which prevented the check
  for CPP working properly, and getline.c wouldn't compile due to an
  undeclared variable when USE_TERMCAP was defined. Both problems have
  now been fixed. Note that if you successfully compiled version
  1.3.3, this problem didn't affect you.

  An unfortunate and undocumented binding of the key-sequence M-O was
  shadowing the arrow-key bindings on systems that use ^[OA etc. I
  have removed this binding (the documented lower case M-o binding
  remains bound). Under the KDE konsole terminal this was causing the
  arrow keys to do something other than expected.

  There was a bug in the history list code which could result in
  strange entries appearing at the start of the history list once
  enough history lines had been added to the list to cause the
  circular history buffer to wrap. This is now fixed.
 
Version 1.3.3:

  Signal handling has been re-written, and documentation of its
  behaviour has been added to the gl_get_line(3) man page. In addition
  to eliminating race conditions, and appropriately setting errno for
  those signals that abort gl_get_line(), many more signals are now
  intercepted, making it less likely that the terminal will be left in
  raw mode by a signal that isn't trapped by gl_get_line().

  A bug was also fixed that was leaving the terminal in raw mode if
  the editing mode was changed interactively between vi and emacs.
  This was only noticeable when running programs from old shells that
  don't reset terminal modes.

Version 1.3.2:

  Tim Eliseo contributed a number of improvements to vi mode,
  including a fuller set of vi key-bindings, implementation of the vi
  constraint that the cursor can't backup past the point at which
  input mode was entered, and restoration of overwritten characters
  when backspacing in overwrite mode. There are also now new bindings
  to allow users to toggle between vi and emacs modes interactively.
  The terminal bell is now used in some circumstances, such as when an
  unrecognized key sequence is entered. This can be turned off by the
  new nobeep option in the tecla configuration file.

  Unrelated to the above, a problem under Linux which prevented ^Q
  from being used to resume terminal output after the user had pressed
  ^S, has been fixed.

Version 1.3.1:

  In vi mode a bug was preventing the history-search-backward and
  history-search-forward actions from doing anything when invoked on
  empty lines. On empty lines they now act like up-history and
  down-history respectively, as in emacs mode.

  When creating shared libraries under Linux, the -soname directive
  was being used incorrectly. The result is that Linux binaries linked
  with the 1.2.3, 1.2.4 and 1.3.0 versions of the tecla shared
  libraries, will refuse to see other versions of the shared library
  until relinked with version 1.3.1 or higher.

  The configure script can now handle the fact that under Solaris-2.6
  and earlier, the only curses library is a static one that hides in
  /usr/ccs/lib. Under Linux it now also caters for old versions of GNU
  ld which don't accept version scripts.

  The demos are now linked against the shared version of the library
  if possible. Previously they were always linked with the static
  version.

Version 1.3.0:

  The major change in this release is the addition of an optional vi
  command-line editing mode in gl_get_line(), along with lots of new
  action functions to support its bindings. To enable this, first
  create a ~/.teclarc file if you don't already have one, then add the
  following line to it.

   edit-mode vi

  The default vi bindings, which are designed to mimic those of the vi
  editor as closely as possible, are described in the gl_get_line(3)
  man page.

  A new convenience function called ef_list_expansions() has been
  added for listing filename expansions. See the ef_list_expansions(3)
  man page for details. This is used in a new list-glob binding, bound
  to ^Xg in emacs mode, and ^G in vi input mode.

  A bug has been fixed in the key-binding table expansion code. This
  bug would have caused problems to anybody who defined more than
  about 18 personalized key-bindings in their ~/.teclarc file.

Version 1.2.4:

  Buffered I/O is now used for writing to terminals, and where
  supported, cursor motion is done with move-n-positions terminfo
  capabilities instead of doing lots of move-1-position requests. This
  greatly improves how the library feels over slow links.

  You can now optionally compile different architectures in different
  directories, without having to make multiple copies of the
  distribution. This is documented in the INSTALL file.

  The ksh ~+ directive is now supported.

  Thanks to Markus Gyger for the above improvements.

  Documentation has been added to the INSTALL file describing features
  designed to facilitate configuration and installation of the library
  as part of larger packages. These features are intended to remove
  the need to modify the tecla distribution's configuration and build
  procedures when embedding the libtecla distribution in other package
  distributions.

  A previous fix to stop the cursor from warping when the last
  character of the input line was in the last column of the terminal,
  was only being used for the first terminal line of the input line.
  It is now used for all subsequent lines as well, as originally
  intended.
  
Version 1.2.3:

  The installation procedure has been better automated with the
  addition of an autoconf configure script. This means that installers
  can now compile and install the library by typing:

    ./configure
    make
    make install

  On all systems this makes at least the normal static version of the
  tecla library. It also makes the reentrant version if reentrant
  POSIX functions are detected.  Under Solaris, Linux and HP-UX the
  configuration script arranges for shared libraries to be compiled in
  addition to the static libraries.  It is hoped that installers will
  return information about how to compile shared libraries on other
  systems, for inclusion in future releases, and to this end, a new
  PORTING guide has been provided.

  The versioning number scheme has been changed. This release would
  have been 1.2c, but instead will be refered to as 1.2.3. The
  versioning scheme, based on conventions used by Sun Microsystems, is
  described in configure.in.

  The library was also tested under HP-UX, and this revealed two
  serious bugs, both of which have now been fixed.
  
  The first bug prevented the library from writing control codes to
  terminals on big-endian machines, with the exception of those
  running under Solaris. This was due to an int variable being used
  where a char was needed.

  The second bug had the symptom that on systems that don't use the
  newline character as the control code for moving the cursor down a
  line, a newline wasn't started when the user hit enter.

Version 1.2b:

  Two more minor bug fixes:

  Many terminals don't wrap the cursor to the next line when a
  character is written to the rightmost terminal column. Instead, they
  delay starting a new line until one more character is written, at
  which point they move the cursor two positions.  gl_get_line()
  wasn't aware of this, so cursor repositionings just after writing
  the last character of a column, caused it to erroneously go up a
  line. This has now been remedied, using a method that should work
  regardless of whether a terminal exhibits this behavior or not.

  Some systems dynamically record the current terminal dimensions in
  environment variables called LINES and COLUMNS. On such systems,
  during the initial terminal setup, these values should override the
  static values read from the terminal information databases, and now
  do.  Previously they were only used if the dimensions returned by
  terminfo/termcap looked bogus.

Version 1.2a:

  This minor release fixes the following two bugs:

  The initial terminal size and subsequent changes thereto, weren't
  being noticed by gl_get_line(). This was because the test for the
  existence of TIOCWINSZ was erroneously placed before the inclusion
  of termios.h. One of the results was that on input lines that
  spanned more than one terminal line, the cursor occasionally jumped
  unexpectedly to the previous terminal line.

  On entering a line that wrapped over multiple terminal lines,
  gl_get_line() simply output a carriage-return line-feed at the point
  at which the user pressed return. Thus if one typed in such a line,
  then moved back onto one of the earlier terminal lines before
  hitting return, the cursor was left on a line containing part of the
  line that had just been entered. This didn't do any harm, but it
  looked a mess.

Version 1.2:

  A new facility for looking up and completing filenames in UNIX-style
  paths has now been added (eg. you can search for, or complete
  commands using the UNIX PATH environment variable). See the
  pca_lookup_file(3) man page.

  The already existing filename completion callback can now be made
  selective in what types of files it lists. See the
  cpl_complete_word(3) man page.

  Due to its potential to break applications when changed, the use of
  the publically defined CplFileArgs structure to configure the
  cpl_file_completions() callback is now deprecated.  The definition
  of this structure has been frozen, and its documentation has been
  removed from the man pages.  It will remain supported, but if you
  have used it, you are recommended to switch to the new method, which
  involves a new opaque configuration object, allocated via a provided
  constructor function, configured via accessor functions, and
  eventually deleted with a provided destructor function. The
  cpl_file_completions() callback distinguishes which structure type
  it has been sent by virtue of a code placed at the start of the new
  structure by the constructor.  It is assumed that no existing
  applications set the boolean 'escaped' member of the CplFileArgs
  structure to 4568.  The new method is documented in the
  cpl_complete_word(3) man page.

Version 1.1j

  This was the initial public release on freshmeat.org.
./libtecla/html/enhance.html0100644000076400007640000000635710141252544014375 0ustar mcsmcs Manual Page
enhance                                  enhance



NAME

       enhance  - A program that adds command-line editing to third party pro-
       grams.

SYNOPSIS

       enhance command [ argument ... ]


DESCRIPTION

       The enhance program provides enhanced command-line  editing  facilities
       to  users  of  third  party applications, to which one doesn't have any
       source code. It does this by  placing  a  pseudo-terminal  between  the
       application and the real terminal. It uses the tecla command-line edit-
       ing library to read input from the real terminal,  then  forwards  each
       just  completed  input line to the application via the pseudo-terminal.
       All output from the application is forwarded back unchanged to the real
       terminal.

       Whenever  the application stops generating output for more than a tenth
       of a second, the enhance program treats the  latest  incomplete  output
       line  as the prompt, and redisplays any incompleted input line that the
       user has typed after it. Note that the small delay, which is  impercep-
       tible  to  the  user, isn't necessary for correct operation of the pro-
       gram. It is just an optimization, designed to stop the input line  from
       being redisplayed so often that it slows down output.

       Note  that  the  user-level command-line editing facilities provided by
       the Tecla library are documented in the tecla man page


DEFICIENCIES

       The one major problem that hasn't been solved yet, is how to deal  with
       applications  that  change  whether typed input is echo'd by their con-
       trolling terminal. For example, programs that ask for a password,  such
       as  ftp  and telnet, temporarily tell their controlling terminal not to
       echo what the user types. Since this request goes  to  the  application
       side  of the psuedo terminal, the enhance program has no way of knowing
       that this has happened, and continues to echo typed input to  its  con-
       trolling terminal, while the user types their password.

       Furthermore, before executing the host application, the enhance program
       initially sets the pseudo terminal to noecho mode, so  that  everything
       that  it sends to the program doesn't get redundantly echoed. If a pro-
       gram that switches to noecho mode explicitly  restores  echoing  after-
       wards, rather than restoring the terminal modes that were previously in
       force, then subsequently, every time that you enter a new input line, a
       duplicate copy will be displayed on the next line.


FILES

       libtecla.a    -   The tecla library.
       ~/.teclarc    -   The tecla personal customization file.


SEE ALSO

       tecla, libtecla


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                        enhance
./libtecla/html/gl_io_mode.html0100644000076400007640000006147510141252547015076 0ustar mcsmcs Manual Page
gl_io_mode                            gl_io_mode



NAME

        gl_io_mode, gl_raw_io, gl_normal_io, gl_tty_signals, gl_abandon_line,
        gl_handle_signal,  gl_pending_io  -  How  to use gl_get_line() from an
       external event loop.

SYNOPSIS

       #include <libtecla.h>

       int gl_io_mode(GetLine *gl, GlIOMode mode);

       int gl_raw_io(GetLine *gl);

       int gl_normal_io(GetLine *gl);

       int gl_tty_signals(void (*term_handler)(int),
                          void (*susp_handler)(int),
                          void (*cont_handler)(int),
                          void (*size_handler)(int));

       void gl_abandon_line(GetLine *gl);

       void gl_handle_signal(int signo, GetLine *gl, int ngl);

       GlPendingIO gl_pending_io(GetLine *gl);



DESCRIPTION

       The gl_get_line() function,  which  is  documented  separately  in  the
       gl_get_line  man page, supports two different I/O modes.
       These are selected by calling the gl_io_mode() function.


         int gl_io_mode(GetLine *gl, GlIOMode mode);


       The mode argument of this function specifies the new I/O mode, and must
       be one of the following.


         GL_NORMAL_MODE   -  Select the normal blocking-I/O mode.
                             In this mode gl_get_line()
                             doesn't return until either an error
                             occurs of the user finishes entering a
                             new line. This mode is the focus of
                             the gl_get_line man page.

         GL_SERVER_MODE   -  Select non-blocking server I/O mode.
                             In this mode, since non-blocking
                             terminal I/O is used, the entry of
                             each new input line typically requires
                             many calls to gl_get_line() from
                             an external I/O-driven event loop.
                             This mode is the focus of this man
                             page.


       Newly created GetLine objects start in normal I/O mode, so to switch to
       non-blocking server mode requires an initial call to gl_io_mode().


SERVER I/O MODE

       In non-blocking server I/O mode, the application is required to have an
       event  loop  which  calls  gl_get_line()  whenever  the  terminal  file
       descriptor can do the type I/O that gl_get_line() is  waiting  for.  To
       determine  which type of I/O gl_get_line() is waiting for, the applica-
       tion calls the gl_pending_io() function.


         GlPendingIO gl_pending_io(GetLine *gl);


       The return value of this function is one of the following  two  enumer-
       ated values.


         GLP_READ    -  gl_get_line() is waiting to write a
                        character to the terminal.

         GLP_WRITE   -  gl_get_line() is waiting to read a
                        character from the keyboad.


       If  the application is using either the select() or poll() system calls
       to watch for I/O on a group of file descriptors, then  it  should  call
       the gl_pending_io() function before each call to these functions to see
       which direction of I/O it should tell them to watch for, and  configure
       their  arguments  accordingly. In the case of the select() system call,
       this means using the FD_SET() macro to add the terminal file descriptor
       either to the set of file descriptors to be watched for readability, or
       the set to be watched for writability.

       As in normal I/O mode, the return value of gl_get_line()  is  either  a
       pointer  to a completed input line, or NULL. However, whereas in normal
       I/O mode a NULL return value always means that an  error  occurred,  in
       non-blocking  server  mode,  NULL  is  also returned when gl_get_line()
       can't read or write to the terminal  without  blocking.  Thus  in  non-
       blocking  server  mode,  in order to determine when a NULL return value
       signifies that an error occurred or not, it is necessary  to  call  the
       gl_return_status()  function.  If  this function returns the enumerated
       value, GLR_BLOCKED, as documented in the gl_get_line man
       page,  this  means  that gl_get_line() is waiting for I/O, and no error
       has occurred.

       When gl_get_line() returns NULL and gl_return_status()  indicates  that
       this  is  due  to  blocked  terminal  I/O,  the application should call
       gl_get_line() again when the type of I/O  reported  by  gl_pending_io()
       becomes  possible.  The  prompt,  start_line and start_pos arguments of
       gl_get_line() will be ignored on these calls.  If you  need  to  change
       the  prompt  of  the  line that is currently being edited, then you can
       call   the   gl_replace_prompt()   function    (documented    in    the
       gl_get_line man page) between calls to gl_get_line().


GIVING UP THE TERMINAL

       A  complication  that  is unique to non-blocking server mode is that it
       requires that the terminal  be  left  in  raw  mode  between  calls  to
       gl_get_line().  If  this  weren't  the  case,  the  external event loop
       wouldn't be able to detect individual key-presses, and the  basic  line
       editing implemented by the terminal driver would clash with the editing
       provided by gl_get_line(). What this means is that any  time  that  the
       terminal  needs  to  be used for other things than entering a new input
       line with gl_get_line(), it needs to be restored to a usable state.  In
       particular, whenever the process is suspended or terminated, the termi-
       nal must be returned to a  normal  state.  If  this  isn't  done,  then
       depending  on  the characteristics of the shell that was used to invoke
       the program, the user may end up with a hung terminal. To this end, the
       gl_normal_io()  function is provided for switching the terminal back to
       the state that it was in when raw mode was last established.


         int gl_normal_io(GetLine *gl);


       What this function does is first flush any pending output to the termi-
       nal,  then move the cursor to the start of the terminal line which fol-
       lows the end of the incompletely entered input line. At this  point  it
       is  safe  to  suspend  or terminate the process, and it is safe for the
       application to read and write to the terminal. To resume entry  of  the
       input line, the application should call the gl_raw_io() function.


         int gl_raw_io(GetLine *gl);


       This  function  starts  a  new line, redisplays the partially completed
       input line (if any), restores the cursor position within this  line  to
       where it was when gl_normal_io() was called, then switches back to raw,
       non-blocking terminal mode ready to continue entry of  the  input  line
       when gl_get_line() is next called.

       Note that in non-blocking server mode, if gl_get_line() is called after
       a call to gl_normal_io(), without an intervening call  to  gl_raw_io(),
       gl_get_line()  will  call  gl_raw_mode()  itself, and the terminal will
       remain in this mode when gl_get_line() returns.


SIGNAL HANDLING

       In the previous section it was pointed out that in non-blocking  server
       mode,  the  terminal must be restored to a sane state whenever a signal
       is received that either suspends or terminates the process.  In  normal
       I/O  mode,  this  is done for you by gl_get_line(), but in non-blocking
       server mode, since the terminal is left in raw mode  between  calls  to
       gl_get_line(),  this signal handling has to be done by the application.
       Since there are many signals that can suspend or terminate  a  process,
       as  well  as other signals that are important to gl_get_line(), such as
       the SIGWINCH signal, which tells it when the terminal size has changed,
       the  gl_tty_signals()  function  is provided for installing signal han-
       dlers for all pertinent signals.


         int gl_tty_signals(void (*term_handler)(int),
                            void (*susp_handler)(int),
                            void (*cont_handler)(int),
                            void (*size_handler)(int));


       What this does is use  gl_get_line()'s  internal  list  of  signals  to
       assign specified signal handlers to groups of signals. The arguments of
       this function are as follows.


         term_handler  -  This is the signal handler that is to be
                          used to trap signals that by default
                          terminate any process that receives
                          them (eg. SIGINT or SIGTERM).

         susp_handler  -  This is the signal handler that is to be
                          used to trap signals that by default
                          suspend any process that receives them,
                          (eg. SIGTSTP or SIGTTOU).

         cont_handler  -  This is the signal handler that is to be
                          used to trap signals that are usually
                          sent when a process resumes after being
                          suspended (usually SIGCONT). Beware that there is
                          nothing to stop a user from sending one of these
                          signals at other times.

         size_handler  -  This signal handler is used to trap
                          signals that are sent to processes when
                          their controlling terminals are resized
                          by the user (eg. SIGWINCH).


       These arguments can all be the same, if so desired, and you can specify
       SIG_IGN  (ignore  this  signal)  or  SIG_DFL  (use  the system-provided
       default signal handler) instead of a function where pertinent. In  par-
       ticular, it is rarely useful to trap SIGCONT, so the cont_handler argu-
       ment will usually be SIG_DFL or SIG_IGN.

       The gl_tty_signals() function uses the POSIX  sigaction()  function  to
       install  these  signal  handlers,  and it is careful to use the sa_mask
       member of each sigaction structure to ensure that  only  one  of  these
       signals  is  ever  delivered  at  a time. This guards against different
       instances of these signal handlers from simultaneously trying to  write
       to common global data, such as a shared sigsetjmp() buffer or a signal-
       received flag.

       The signal handlers that are installed by this  function,  should  call
       the gl_handle_signal().


         void gl_handle_signal(int signo, GetLine *gl, int ngl);


       The  signo  argument tells this function which signal it is being asked
       to respond to, and the gl argument should be a  pointer  to  the  first
       element  of  an  array of ngl GetLine objects. If your application only
       has one of these objects, just pass its pointer as the gl argument  and
       specify ngl as 1.

       Depending  on the signal that is being handled, this function does dif-
       ferent things.


   Terminal resize signals (SIGWINCH)
       If the signal indicates that the terminal was resized, then it arranges
       for the next call to gl_get_line() to ask the terminal for its new size
       and redraw the input line accordingly. In order that  gl_get_line()  be
       called as soon as possible to do this, gl_handle_signal() also arranges
       that the next call to gl_pending_io() will return  GLP_WRITE.  Thus  if
       the  application waits for I/O in select() or poll(), then the applica-
       tion needs to ensure that these functions will be reliably aborted when
       a  signal is caught and handled by the application. More on this below.


Process termination signals.

       If the signal that was caught is one of those that  by  default  termi-
       nates  any  process  that receives it, then gl_handle_signal() does the
       following steps.

       1. First it blocks the delivery of all signals that can be
          blocked (ie. SIGKILL and SIGSTOP can't be blocked)

       2. Next it calls gl_normal_io() for each of the ngl
          GetLine objects. Note that this does nothing to any of the
          GetLine objects that aren't currently in raw mode.

       3. Next it sets the signal handler of the signal to its default,
          process-termination disposition.

       4. Next it re-sends the process the signal that was caught.

       5. Finally it unblocks delivery of this signal, which
          results in the process being terminated.


Process suspension signals.

       If the default disposition of the signal is to suspend the process, the
       same steps are executed as for process termination signals, except that
       when the process is later resumed,  gl_handle_signal()  continues,  and
       does the following steps.

       6. It re-blocks delivery of the signal.

       7. It reinstates the signal handler of the signal to the one
          that was displaced when its default disposition was substituted.

       8. For any of the GetLine objects that were in raw mode when
          gl_handle_signal() was called, gl_handle_signal() then
          calls gl_raw_io(), to resume entry of the input lines on
          those terminals.

       9. Finally, it restores the signal process mask to how it
          was when gl_handle_signal() was called.

       Note  that  the  process  is suspended or terminated using the original
       signal that was caught, rather than using the uncatchable  SIGSTOP  and
       SIGKILL signals. This is important, because when a process is suspended
       or terminated, the parent of the process may wish  to  use  the  status
       value returned by the wait() system call to figure out which signal was
       responsible. In particular, most shells use this information to print a
       corresponding  message to the terminal. Users would be rightly confused
       if when their process received a SIGPIPE signal, the program  responded
       by  sending itself a SIGKILL signal, and the shell then printed out the
       provocative statement, "Killed!".


INTERRUPTING THE EVENT LOOP

       If a signal is caught and handled when the application's event loop  is
       waiting  in  select()  or  poll(), these functions will be aborted with
       errno set to EINTR. When  this  happens  the  event  loop  should  call
       gl_pending_io(),  before  calling  select()  or poll() again. It should
       then arrange for select() or poll() to wait for the type  of  I/O  that
       this reports. This is necessary, because any signal handler which calls
       gl_handle_signal(),  will  frequently  change  the  type  of  I/O  that
       gl_get_line() is waiting for.

       Unfortunately, if a signal arrives between the statements which config-
       ure the arguments of select() or poll() and the calls  to  these  func-
       tions,  then the signal will not be seen by these functions, which will
       then not be aborted. If these functions are waiting for keyboard  input
       from  the  user  when  the  signal  is received, and the signal handler
       arranges to redraw the input line to accomodate a  terminal  resize  or
       the resumption of the process, then this redisplay will be end up being
       delayed until the user hits the next key. Apart from puzzling the user,
       this  clearly  isn't  a serious problem. However there is a way, albeit
       complicated, to completely avoid this  race  condition.  The  following
       steps illustrate this.

       1. Block all of the signals that gl_get_line() catches,
          by passing the signal set returned by gl_list_signals() to
          sigprocmask().

       2. Call gl_pending_io() and set up the arguments of
          select() or poll() accordingly.

       3. Call sigsetjmp() with a non-zero savesigs argument.

       4. Initially this sigsetjmp() statement will return zero,
          indicating that control isn't resuming there after a matching
          call to siglongjmp().

       5. Replace all of the handlers of the signals that gl_get_line()
          is configured to catch, with a signal handler that first records
          the number of the signal that was caught, in a file-scope variable,
          then calls siglongjmp() with a non-zero value argument, to
          return execution to the above sigsetjmp()
          statement.  Registering these signal handlers can conveniently be
          done using the gl_tty_signals() function.

       6. Set the file-scope variable that the above signal handler uses to
          record any signal that is caught to -1, so that we can check
          whether a signal was caught by seeing if it contains a valid signal
          number.

       7. Now unblock the signals that were blocked in step 1. Any signal
          that was received by the process in between step 1 and now will
          now be delivered, and trigger our signal handler, as will any
          signal that is received until we block these signals again.

       8. Now call select() or poll().

       9. When select() returns, again block the signals that were
          unblocked in step 7.

       If a signal is arrived any time during the above steps, our signal han-
       dler will be triggered and cause control to return to  the  sigsetjmp()
       statement,  where this time, sigsetjmp() will return non-zero, indicat-
       ing that a signal was caught. When this  happens  we  simply  skip  the
       above  block of statements, and continue with the following statements,
       which are executed regardless of whether or not  a  signal  is  caught.
       Note  that when sigsetjmp() returns, regardless of why it returned, the
       process signal mask is returned to how  it  was  when  sigsetjmp()  was
       called.  Thus  the following statements are always executed with all of
       our signals blocked.

       9. Reinstate the signal handlers that were displaced in step 5.

       10. Check wether a signal was caught, by checking the file-scope
           variable that the signal handler records signal numbers in.

       11. If a signal was caught, send this signal to the application
           again, and unblock just this signal, so that it invokes the
           signal handler which we just reinstated in step 10.

       12. Unblock all of the signals that were blocked in step 7.

       Since this is complicated, note that demo3.c includes a working example
       of  how to do this. The method used there however, is more general than
       the above. What it provides is a wrapper function around select() which
       encompasses   steps   3  to  11.  In  this  wrapper,  rather  than  use
       gl_list_signals()  to  figure  out  the  signals  to  block,  and   and
       gl_tty_signals() to assign and revert signal handlers, one of its argu-
       ments is a sigset_t which specifies which signals to block  and  assign
       signal  handlers to. This function thus doesn't depend on gl_get_line()
       and can thus be used in other situations where race-condition-free sig-
       nal handling is required.


SIGNALS CAUGHT BY GL_GET_LINE

       Since  the  application  is  expected to handle signals in non-blocking
       server mode, gl_get_line() doesn't attempt to duplicate this when it is
       being  called.  If one of the signals that it is configured to catch is
       sent  to  the  application  while  gl_get_line()   is   being   called,
       gl_get_line() reinstates the caller's signal handlers, then just before
       returning, re-sends the signal to the process to let the  application's
       signal  handler handle it. If the process isn't terminated by this sig-
       nal, gl_get_line() returns NULL, and a following call to gl_return_sta-
       tus() returns the enumerated value GLR_SIGNAL.


ABORTING LINE INPUT

       Often,  rather  than  letting  it  terminate  the process, applications
       respond to the SIGINT user-interrupt signal  by  aborting  the  current
       input  line.  The  way to do this in non-blocking server-I/O mode is to
       not call gl_handle_signal() when this signal is caught, but instead  to
       call the gl_abandon_line().


         void gl_abandon_line(GetLine *gl);


       This function arranges that when gl_get_line() is next called, it first
       flushes any pending output to the terminal, then discardes the  current
       input  line,  outputs a new prompt on the next line, and finally starts
       accepting input of a new input line from the user.


SIGNAL SAFE FUNCTIONS

       Provided that certain rules are followed, the following  functions  can
       have  been  written  to  be safely callable from signal handlers. Other
       functions in this library should not be called from signal handlers.


         gl_normal_io()
         gl_raw_io()
         gl_handle_signal()
         gl_abandon_line()


       In order for this to be true, all signal handlers that call these func-
       tions  must  be  registered in such a way that only one instance of any
       one of them can be running at one time. The way to do this  is  to  use
       the  POSIX  sigaction()  function  to register all signal handlers, and
       when doing this, use the sa_mask member of the corresponding  sigaction
       structure,  to  indicate  that all of the signals who's handlers invoke
       the above functions, should be blocked when the current signal is being
       handled.  This prevents two signal handlers from operating on a GetLine
       object at the same time.

       To prevent signal  handlers  from  accessing  a  GetLine  object  while
       gl_get_line()  or  any of its associated public functions are operating
       on it, all public functions associated  with  gl_get_line(),  including
       gl_get_line()  itself,  temporarily  block the delivery of signals when
       they are accessing GetLine objects. Beware that the only  signals  that
       they  block  are the signals that gl_get_line() is currently configured
       to catch, so be sure that if you call any of the above  functions  from
       signal  handlers,  that the signals that these handlers are assigned to
       are configured to be caught by gl_get_line() (see gl_trap_signal()).


USING TIMEOUTS TO POLL

       If instead of using select() or poll() to wait for I/O,  your  applica-
       tion  just needs to get out of gl_get_line() periodically to briefly do
       something else before returning to accept input from the user, this can
       be  done  in  non-blocking server mode by using the gl_inactivity_time-
       out() function (see  gl_get_line),  to  specify  that  a
       callback  function that returns GLTO_CONTINUE should be called whenever
       gl_get_line() has been waiting for I/O for more than a specified amount
       of time.

       When  this callback is triggered, gl_get_line() will return NULL, and a
       following call to gl_return_status() will return GLR_BLOCKED.

       Beware that gl_get_line() won't return until the user  hasn't  typed  a
       key  for  the  specified  interval, so if the interval is long, and the
       user keeps typing, gl_get_line() may not return for a while.  In  other
       words  there is no guarantee that it will return in the time specified.


THE SERVER DEMO PROGRAM

       The demo3 program that is distributed  with  the  library,  provides  a
       working  example  of  how to use non-blocking server I/O mode in a real
       program. As far  as  the  user  is  concerned,  this  program  operates
       identically to the main demo program (called demo), except that whereas
       the main demo program uses the normal blocking I/O  mode,  demo3  using
       non-blocking  I/O  and  an  external event loop. The source code can be
       found in demo3.c, and the comments therein explain the various steps.


FILES

       libtecla.a      -    The tecla library
       libtecla.h      -    The tecla header file.


SEE ALSO

       libtecla, gl_get_line, tecla, ef_expand_file,
       cpl_complete_word, pca_lookup_file


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                     gl_io_mode
./libtecla/html/tecla.html0100644000076400007640000016247410141252550014064 0ustar mcsmcs Manual Page
tecla                                      tecla



NAME

       tecla, teclarc - The user interface provided by the Tecla library.

DESCRIPTION

       This  man  page  describes  the  command-line editing features that are
       available to users of programs that read keyboard input via  the  Tecla
       library.  Users  of  the  tcsh shell will find the default key-bindings
       very familiar. Users of the bash shell will also find it  quite  famil-
       iar,  but with a few minor differences, most notably in how forward and
       backward searches through the list  of  historical  commands  are  per-
       formed.  There  are  two  major editing modes, one with emacs-like key-
       bindings and another with vi-like key-bindings. By default  emacs  mode
       is  enabled,  but  vi mode can alternatively be selected via the user's
       configuration file. This file can also be used to change  the  bindings
       of individual keys to suit the user's preferences. By default, tab com-
       pletion is provided. If the application  hasn't  reconfigured  this  to
       complete  other  types  of symbols, then tab completion completes file-
       names.


KEY SEQUENCE NOTATION

       In the rest of this man page,  and  also  in  all  Tecla  configuration
       files, key-sequences are expressed as follows.


       ^A  or  C-a
           This is a control-A, entered by pressing the control key at
           the same time as the A key.

       \E    or   M-
           In key-sequences, both of these notations can be entered
           either by pressing the escape key, then the following key, or by
           pressing the Meta key at the same time as the following key. Thus
           the key sequence M-p can be typed in two ways, by pressing
           the escape key, followed by pressing p, or by pressing the
           Meta key at the same time as p.

       up
           This refers to the up-arrow key.

       down
           This refers to the down-arrow key.

       left
           This refers to the left-arrow key.

       right
           This refers to the right-arrow key.

       a
           This is just a normal A key.



THE TECLA CONFIGURATION FILE

       By  default, Tecla looks for a file called .teclarc in your home direc-
       tory (ie. ~/.teclarc).  If it finds this file, it reads it,  interpret-
       ing each line as defining a new key binding or an editing configuration
       option. Since the emacs keybindings are installed by  default,  if  you
       want to use the non-default vi editing mode, the most important item to
       go in this file is the following line:

         edit-mode vi

       This will re-configure the default bindings for vi-mode.  The  complete
       set of arguments that this command accepts are:

         vi     -  Install key-bindings like those of the vi
                   editor.
         emacs  -  Install key-bindings like those of the emacs
                   editor. This is the default.
         none   -  Use just the native line editing facilities
                   provided by the terminal driver.

       To  prevent the terminal bell from being rung, such as when an unrecog-
       nized control-sequence is typed, place the following line in  the  con-
       figuration file:

         nobeep

       An  example of a key binding line in the configuration file is the fol-
       lowing.

         bind M-[2~ insert-mode

       On many keyboards, the above key sequence is generated when one presses
       the  insert  key,  so  with this keybinding, one can toggle between the
       emacs-mode insert and overwrite modes by hitting  one  key.  One  could
       also  do  it by typing out the above sequence of characters one by one.
       As explained above, the M- part of this sequence can be typed either by
       pressing  the  escape  key before the following key, or by pressing the
       Meta key at the same time as the following key. Thus if you had set the
       above  key binding, and the insert key on your keyboard didn't generate
       the above key sequence, you could still type it in either of  the  fol-
       lowing 2 ways.

         1. Hit the escape key momentarily, then press '[', then '2', then
            finally '~'.

         2. Press the meta key at the same time as pressing the '[' key,
            then press '2', then '~'.

       If  you  set a keybinding for a key-sequence that is already bound to a
       function, the new binding overrides the old one. If in the new  binding
       you  omit the name of the new function to bind to the key-sequence, the
       original binding becomes undefined.

       Starting with versions of libtecla later than 1.3.3 it is now  possible
       to  bind keysequences that begin with a printable character. Previously
       key-sequences were required to start with a control or meta  character.

       Note  that  the special keywords "up", "down", "left" and "right" refer
       to the arrow keys, and are thus not treated as  keysequences.  So,  for
       example, to rebind the up and down arrow keys to use the history search
       mechanism instead of the simple history recall method, you could  place
       the following in your configuration file:

         bind up history-search-backwards
         bind down history-search-backwards

       To unbind an existing binding, you can do this with the bind command by
       omitting to name any action to rebind the key sequence to.   For  exam-
       ple,  by  not  specifying  an  action  function,  the following command
       unbinds the default beginning-of-line action from the ^A key sequence:

         bind ^A

       If you create a ~/.teclarc configuration file, but it appears  to  have
       no effect on the program, check the documentation of the program to see
       if the author chose a different name for this file.


FILENAME AND TILDE COMPLETION

       With the default key bindings, pressing the TAB key (aka.  ^I)  results
       in  Tecla  attempting to complete the incomplete filename that precedes
       the cursor. Tecla searches backwards from the cursor, looking  for  the
       start  of  the  filename,  stopping  when it hits either a space or the
       start of the line. If more than one file has the specified prefix, then
       Tecla  completes  the  filename  up to the point at which the ambiguous
       matches start to differ, then lists the possible matches.

       In addition to literally written filenames, Tecla  can  complete  files
       that  start  with  ~/  and  ~user/ expressions and that contain $envvar
       expressions. In particular, if you hit TAB within an incomplete  ~user,
       expression,  Tecla  will  attempt to complete the username, listing any
       ambiguous matches.

       The completion binding is implemented using the  cpl_word_completions()
       function,  which is also available separately to users of this library.
       See the cpl_word_completions(@LIBR_MANEXT@) man page for more  details.


FILENAME EXPANSION

       With  the default key bindings, pressing ^X* causes Tecla to expand the
       filename that precedes the cursor, replacing ~/ and ~user/  expressions
       with  the corresponding home directories, and replacing $envvar expres-
       sions with the value of the specified  environment  variable,  then  if
       there  are any wildcards, replacing the so far expanded filename with a
       space-separated list of the files which match the wild cards.

       The expansion binding is implemented using the  ef_expand_file()  func-
       tion.  See the ef_expand_file man page for more details.


RECALLING PREVIOUSLY TYPED LINES

       Every time that a new line is entered by the user, it is appended to  a
       list  of  historical input lines maintained within the GetLine resource
       object. You can traverse up and down this list using the  up  and  down
       arrow  keys.  Alternatively,  you  can  do the same with the ^P, and ^N
       keys, and in vi command mode you can alternatively  use  the  k  and  j
       characters.  Thus  pressing  up-arrow  once, replaces the current input
       line  with  the  previously  entered  line.  Pressing  up-arrow  again,
       replaces  this  with  the line that was entered before it, etc.. Having
       gone back one or more lines into the history list, one  can  return  to
       newer  lines  by  pressing down-arrow one or more times. If you do this
       sufficient times, you will return to the original line  that  you  were
       entering when you first hit up-arrow.

       Note  that  in  vi mode, all of the history recall functions switch the
       library into command mode.

       In emacs mode the M-p and M-n keys work just like the ^P and  ^N  keys,
       except  that  they  skip all but those historical lines which share the
       prefix that precedes the cursor. In vi command mode the  upper  case  K
       and  J  characters  do the same thing, except that the string that they
       search for includes the character under the cursor as well as what pre-
       cedes it.

       Thus for example, suppose that you were in emacs mode, and you had just
       entered the following list of commands in the order shown:

         ls ~/tecla/
         cd ~/tecla
         ls -l getline.c
         emacs ~/tecla/getline.c

       If you next typed:

         ls

       and then hit M-p, then rather than returning the previously typed emacs
       line, which doesn't start with "ls", Tecla would recall the "ls -l get-
       line.c" line. Pressing M-p again would recall the "ls ~/tecla/" line.

       Note that if the string that you are searching for, contains any of the
       special  characters, *, ?, or '[', then it is interpretted as a pattern
       to be matched. Thus, cotinuing with the above example, after typing  in
       the list of commands shown, if you then typed:

         *tecla*

       and  hit M-p, then the "emacs ~/tecla/getline.c" line would be recalled
       first, since it contains the word tecla somewhere in  the  line,  Simi-
       larly, hitting M-p again, would recall the "ls ~/tecla/" line, and hit-
       ting it once more would recall the "ls ~/tecla/" line. The pattern syn-
       tax  is  the  same  as  that  described  for filename expansion, in the
       ef_expand_file(@LIBR_MANEXT@ man page.


HISTORY FILES

       Authors of programs that use the Tecla library have the option of  sav-
       ing historical command-lines in a file before exiting, and subsequently
       reading them back in from this file when the program is  next  started.
       There  is no standard name for this file, since it makes sense for each
       application to use its own history file, so that commands from  differ-
       ent applications don't get mixed up.


INTERNATIONAL CHARACTER SETS

       Since  libtecla  version  1.4.0, Tecla has been 8-bit clean. This means
       that all 8-bit characters that are  printable  in  the  user's  current
       locale  are  now  displayed verbatim and included in the returned input
       line.  Assuming that the calling program correctly contains a call like
       the following,

         setlocale(LC_CTYPE, "");

       then  the  current locale is determined by the first of the environment
       variables LC_CTYPE, LC_ALL, and LANG, that is found to contain a  valid
       locale  name.  If  none  of these variables are defined, or the program
       neglects to call setlocale, then the default C locale is used, which is
       US  7-bit  ASCII.  On  most  unix-like platforms, you can get a list of
       valid locales by typing the command:

         locale -a

       at the shell prompt.


   Meta keys and locales
       Beware that in most locales other than the default C locale, meta char-
       acters  become  printable,  and  they  are then no longer considered to
       match M-c style key bindings. This allows international  characters  to
       be  entered  with  the compose key without unexpectedly triggering meta
       key bindings. You can still invoke meta bindings, since there are actu-
       ally  two  ways  to  do  this.  For example the binding M-c can also be
       invoked by pressing the escape key momentarily,  then  pressing  the  c
       key,  and  this  will  work regardless of locale. Moreover, many modern
       terminal emulators, such as gnome's gnome-terminal's and KDE's  konsole
       terminals,  already  generate  escape  pairs like this when you use the
       meta key, rather than a real meta character, and other  emulators  usu-
       ally  have  a  way to request this behavior, so you can continue to use
       the meta key on most systems.

       For example, although xterm terminal emulators generate real 8-bit meta
       characters by default when you use the meta key, they can be configured
       to output the equivalent escape pair by setting their  EightBitInput  X
       resource  to  False.  You can either do this by placing a line like the
       following in your ~/.Xdefaults file,

         XTerm*EightBitInput: False

       or by starting an xterm with an -xrm '*EightBitInput:  False'  command-
       line  argument. In recent versions of xterm you can toggle this feature
       on and off with the "Meta Sends Escape" option in the menu that is dis-
       played  when you press the left mouse button and the control key within
       an xterm window. In CDE, dtterms can be similarly coerced  to  generate
       escape pairs in place of meta characters, by setting the Dtterm*KshMode
       resource to True.


   Entering international characters
       If you don't have a keyboard that generates all  of  the  international
       characters  that  you  need,  there  is usually a compose key that will
       allow you to enter special characters, or a  way  to  create  one.  For
       example, under X windows on unix-like systems, if your keyboard doesn't
       have a compose key, you can designate a redundant  key  to  serve  this
       purpose  with  the  xmodmap  command. For example, on many PC keyboards
       there is a microsoft-windows key,  which  is  otherwise  useless  under
       Linux. On my laptop the xev program reports that pressing this key gen-
       erates keycode 115, so to turn this key into a compose key,  I  do  the
       following:

         xmodmap -e 'keycode 115 = Multi_key'

       I  can  then  enter an i with a umlaut over it by typing this key, fol-
       lowed by ", followed by i.


THE AVAILABLE KEY BINDING FUNCTIONS

       The following is a list of the editing functions provided by the  Tecla
       library.  The  names  in the leftmost column of the list can be used in
       configuration files to specify which function a given key  or  combina-
       tion of keys should invoke. They are also used in the next two sections
       to list the default key-bindings in emacs and vi modes.

         user-interrupt           -  Send a SIGINT signal to the
                                     parent process.
         abort                    -  Send a SIGABRT signal to the
                                     parent process.
         suspend                  -  Suspend the parent process.
         stop-output              -  Pause terminal output.
         start-output             -  Resume paused terminal output.
         literal-next             -  Arrange for the next character
                                     to be treated as a normal
                                     character. This allows control
                                     characters to be entered.
         cursor-right             -  Move the cursor one character
                                     right.
         cursor-left              -  Move the cursor one character
                                     left.
         insert-mode              -  Toggle between insert mode and
                                     overwrite mode.
         beginning-of-line        -  Move the cursor to the
                                     beginning of the line.
         end-of-line              -  Move the cursor to the end of
                                     the line.
         delete-line              -  Delete the contents of the
                                     current line.
         kill-line                -  Delete everything that follows
                                     the cursor.
         backward-kill-line       -  Delete all characters between
                                     the cursor and the start of the
                                     line.
         forward-word             -  Move to the end of the word
                                     which follows the cursor.
         forward-to-word          -  Move the cursor to the start of
                                     the word that follows the
                                     cursor.
         backward-word            -  Move to the start of the word
                                     which precedes the cursor.
         goto-column              -  Move the cursor to the
                                     1-relative column in the line
                                     specified by any preceding
                                     digit-argument sequences (see
                                     ENTERING REPEAT COUNTS below).
         find-parenthesis         -  If the cursor is currently
                                     over a parenthesis character,
                                     move it to the matching
                                     parenthesis character. If not
                                     over a parenthesis character
                                     move right to the next close
                                     parenthesis.
         forward-delete-char      -  Delete the character under the
                                     cursor.
         backward-delete-char     -  Delete the character which
                                     precedes the cursor.
         list-or-eof              -  This is intended for binding
                                     to ^D. When invoked when the
                                     cursor is within the line it
                                     displays all possible
                                     completions then redisplays
                                     the line unchanged. When
                                     invoked on an empty line, it
                                     signals end-of-input (EOF) to
                                     the caller of gl_get_line().
         del-char-or-list-or-eof  -  This is intended for binding
                                     to ^D. When invoked when the
                                     cursor is within the line it
                                     invokes forward-delete-char.
                                     When invoked at the end of the
                                     line it displays all possible
                                     completions then redisplays
                                     the line unchanged. When
                                     invoked on an empty line, it
                                     signals end-of-input (EOF) to
                                     the caller of gl_get_line().
         forward-delete-word      -  Delete the word which follows
                                     the cursor.
         backward-delete-word     -  Delete the word which precedes
                                     the cursor.
         upcase-word              -  Convert all of the characters
                                     of the word which follows the
                                     cursor, to upper case.
         downcase-word            -  Convert all of the characters
                                     of the word which follows the
                                     cursor, to lower case.
         capitalize-word          -  Capitalize the word which
                                     follows the cursor.
         change-case              -  If the next character is upper
                                     case, toggle it to lower case
                                     and vice versa.
         redisplay                -  Redisplay the line.
         clear-screen             -  Clear the terminal, then
                                     redisplay the current line.
         transpose-chars          -  Swap the character under the
                                     cursor with the character just
                                     before the cursor.
         set-mark                 -  Set a mark at the position of
                                     the cursor.
         exchange-point-and-mark  -  Move the cursor to the last
                                     mark that was set, and move
                                     the mark to where the cursor
                                     used to be.
         kill-region              -  Delete the characters that lie
                                     between the last mark that was
                                     set, and the cursor.
         copy-region-as-kill      -  Copy the text between the mark
                                     and the cursor to the cut
                                     buffer, without deleting the
                                     original text.
         yank                     -  Insert the text that was last
                                     deleted, just before the
                                     current position of the cursor.
         append-yank              -  Paste the current contents of
                                     the cut buffer, after the
                                     cursor.
         up-history               -  Recall the next oldest line
                                     that was entered. Note that
                                     in vi mode you are left in
                                     command mode.
         down-history             -  Recall the next most recent
                                     line that was entered. If no
                                     history recall session is
                                     currently active, the next
                                     line from a previous recall
                                     session is recalled. Note that
                                     in vi mode you are left in
                                     command mode.
         history-search-backward  -  Recall the next oldest line
                                     who's prefix matches the string
                                     which currently precedes the
                                     cursor (in vi command-mode the
                                     character under the cursor is
                                     also included in the search
                                     string).  Note that in vi mode
                                     you are left in command mode.
         history-search-forward   -  Recall the next newest line
                                     who's prefix matches the string
                                     which currently precedes the
                                     cursor (in vi command-mode the
                                     character under the cursor is
                                     also included in the search
                                     string).  Note that in vi mode
                                     you are left in command mode.
         history-re-search-backward -Recall the next oldest line
                                     who's prefix matches that
                                     established by the last
                                     invocation of either
                                     history-search-forward or
                                     history-search-backward.
         history-re-search-forward - Recall the next newest line
                                     who's prefix matches that
                                     established by the last
                                     invocation of either
                                     history-search-forward or
                                     history-search-backward.
         complete-word            -  Attempt to complete the
                                     incomplete word which
                                     precedes the cursor. Unless
                                     the host program has customized
                                     word completion, filename
                                     completion is attempted. In vi
                                     commmand mode the character
                                     under the cursor is also
                                     included in the word being
                                     completed, and you are left in
                                     vi insert mode.
         expand-filename          -  Within the command line, expand
                                     wild cards, tilde expressions
                                     and dollar expressions in the
                                     filename which immediately
                                     precedes the cursor. In vi
                                     commmand mode the character
                                     under the cursor is also
                                     included in the filename being
                                     expanded, and you are left in
                                     vi insert mode.
         list-glob                -  List any filenames which match
                                     the wild-card, tilde and dollar
                                     expressions in the filename
                                     which immediately precedes the
                                     cursor, then redraw the input
                                     line unchanged.
         list-history             -  Display the contents of the
                                     history list for the current
                                     history group. If a repeat
                                     count of > 1 is specified,
                                     only that many of the most
                                     recent lines are displayed.
                                     See the "ENTERING REPEAT
                                     COUNTS" section.
         read-from-file           -  Temporarily switch to reading
                                     input from the file who's
                                     name precedes the cursor.
         read-init-files          -  Re-read teclarc configuration
                                     files.
         beginning-of-history     -  Move to the oldest line in the
                                     history list. Note that in vi
                                     mode you are left in command
                                     mode.
         end-of-history           -  Move to the newest line in the
                                     history list (ie. the current
                                     line). Note that in vi mode
                                     this leaves you in command
                                     mode.
         digit-argument           -  Enter a repeat count for the
                                     next key-binding function.
                                     For details, see the ENTERING
                                     REPEAT COUNTS section.
         newline                  -  Terminate and return the
                                     current contents of the
                                     line, after appending a
                                     newline character. The newline
                                     character is normally '\n',
                                     but will be the first
                                     character of the key-sequence
                                     that invoked the newline
                                     action, if this happens to be
                                     a printable character. If the
                                     action was invoked by the
                                     '\n' newline character or the
                                     '\r' carriage return
                                     character, the line is
                                     appended to the history
                                     buffer.
         repeat-history           -  Return the line that is being
                                     edited, then arrange for the
                                     next most recent entry in the
                                     history buffer to be recalled
                                     when Tecla is next called.
                                     Repeatedly invoking this
                                     action causes successive
                                     historical input lines to be
                                     re-executed. Note that this
                                     action is equivalent to the
                                     'Operate' action in ksh.
         ring-bell                -  Ring the terminal bell, unless
                                     the bell has been silenced via
                                     the nobeep configuration
                                     option (see the THE TECLA
                                     CONFIGURATION FILE section).
         forward-copy-char        -  Copy the next character into
                                     the cut buffer (NB. use repeat
                                     counts to copy more than one).
         backward-copy-char       -  Copy the previous character
                                     into the cut buffer.
         forward-copy-word        -  Copy the next word into the cut
                                     buffer.
         backward-copy-word       -  Copy the previous word into the
                                     cut buffer.
         forward-find-char        -  Move the cursor to the next
                                     occurrence of the next
                                     character that you type.
         backward-find-char       -  Move the cursor to the last
                                     occurrence of the next
                                     character that you type.
         forward-to-char          -  Move the cursor to the
                                     character just before the next
                                     occurrence of the next
                                     character that the user types.
         backward-to-char         -  Move the cursor to the
                                     character just after the last
                                     occurrence before the cursor
                                     of the next character that the
                                     user types.
         repeat-find-char         -  Repeat the last
                                     backward-find-char,
                                     forward-find-char,
                                     backward-to-char or
                                     forward-to-char.
         invert-refind-char       -  Repeat the last
                                     backward-find-char,
                                     forward-find-char,
                                     backward-to-char, or
                                     forward-to-char in the
                                     opposite direction.
         delete-to-column         -  Delete the characters from the
                                     cursor up to the column that
                                     is specified by the repeat
                                     count.
         delete-to-parenthesis    -  Delete the characters from the
                                     cursor up to and including
                                     the matching parenthesis, or
                                     next close parenthesis.
         forward-delete-find      -  Delete the characters from the
                                     cursor up to and including the
                                     following occurence of the
                                     next character typed.
         backward-delete-find     -  Delete the characters from the
                                     cursor up to and including the
                                     preceding occurence of the
                                     next character typed.
         forward-delete-to        -  Delete the characters from the
                                     cursor up to, but not
                                     including, the following
                                     occurence of the next
                                     character typed.
         backward-delete-to       -  Delete the characters from the
                                     cursor up to, but not
                                     including, the preceding
                                     occurence of the next
                                     character typed.
         delete-refind            -  Repeat the last *-delete-find
                                     or *-delete-to action.
         delete-invert-refind     -  Repeat the last *-delete-find
                                     or *-delete-to action, in the
                                     opposite direction.
         copy-to-column           -  Copy the characters from the
                                     cursor up to the column that
                                     is specified by the repeat
                                     count, into the cut buffer.
         copy-to-parenthesis      -  Copy the characters from the
                                     cursor up to and including
                                     the matching parenthesis, or
                                     next close parenthesis, into
                                     the cut buffer.
         forward-copy-find        -  Copy the characters from the
                                     cursor up to and including the
                                     following occurence of the
                                     next character typed, into the
                                     cut buffer.
         backward-copy-find       -  Copy the characters from the
                                     cursor up to and including the
                                     preceding occurence of the
                                     next character typed, into the
                                     cut buffer.
         forward-copy-to          -  Copy the characters from the
                                     cursor up to, but not
                                     including, the following
                                     occurence of the next
                                     character typed, into the cut
                                     buffer.
         backward-copy-to         -  Copy the characters from the
                                     cursor up to, but not
                                     including, the preceding
                                     occurence of the next
                                     character typed, into the cut
                                     buffer.
         copy-refind              -  Repeat the last *-copy-find
                                     or *-copy-to action.
         copy-invert-refind       -  Repeat the last *-copy-find
                                     or *-copy-to action, in the
                                     opposite direction.
         vi-mode                  -  Switch to vi mode from emacs
                                     mode.
         emacs-mode               -  Switch to emacs mode from vi
                                     mode.
         vi-insert                -  From vi command mode, switch to
                                     insert mode.
         vi-overwrite             -  From vi command mode, switch to
                                     overwrite mode.
         vi-insert-at-bol         -  From vi command mode, move the
                                     cursor to the start of the line
                                     and switch to insert mode.
         vi-append-at-eol         -  From vi command mode, move the
                                     cursor to the end of the line
                                     and switch to append mode.
         vi-append                -  From vi command mode, move the
                                     cursor one position right, and
                                     switch to insert mode.
         vi-replace-char          -  From vi command mode, replace
                                     the character under the cursor
                                     with the the next character
                                     entered.
         vi-forward-change-char   -  From vi command mode, delete
                                     the next character then enter
                                     insert mode.
         vi-backward-change-char  -  From vi command mode, delete
                                     the preceding character then
                                     enter insert mode.
         vi-forward-change-word   -  From vi command mode, delete
                                     the next word then enter
                                     insert mode.
         vi-backward-change-word  -  From vi command mode, delete
                                     the preceding word then
                                     enter insert mode.
         vi-change-rest-of-line   -  From vi command mode, delete
                                     from the cursor to the end of
                                     the line, then enter insert
                                     mode.
         vi-change-line           -  From vi command mode, delete
                                     the current line, then enter
                                     insert mode.
         vi-change-to-bol         -  From vi command mode, delete
                                     all characters between the
                                     cursor and the beginning of
                                     the line, then enter insert
                                     mode.
         vi-change-to-column      -  From vi command mode, delete
                                     the characters from the cursor
                                     up to the column that is
                                     specified by the repeat count,
                                     then enter insert mode.
         vi-change-to-parenthesis -  Delete the characters from the
                                     cursor up to and including
                                     the matching parenthesis, or
                                     next close parenthesis, then
                                     enter vi insert mode.
         vi-forward-change-find   -  From vi command mode, delete
                                     the characters from the
                                     cursor up to and including the
                                     following occurence of the
                                     next character typed, then
                                     enter insert mode.
         vi-backward-change-find  -  From vi command mode, delete
                                     the characters from the
                                     cursor up to and including the
                                     preceding occurence of the
                                     next character typed, then
                                     enter insert mode.
         vi-forward-change-to     -  From vi command mode, delete
                                     the characters from the
                                     cursor up to, but not
                                     including, the following
                                     occurence of the next
                                     character typed, then enter
                                     insert mode.
         vi-backward-change-to    -  From vi command mode, delete
                                     the characters from the
                                     cursor up to, but not
                                     including, the preceding
                                     occurence of the next
                                     character typed, then enter
                                     insert mode.
         vi-change-refind         -  Repeat the last
                                     vi-*-change-find or
                                     vi-*-change-to action.
         vi-change-invert-refind  -  Repeat the last
                                     vi-*-change-find or
                                     vi-*-change-to action, in the
                                     opposite direction.
         vi-undo                  -  In vi mode, undo the last
                                     editing operation.
         vi-repeat-change         -  In vi command mode, repeat the
                                     last command that modified the
                                     line.


DEFAULT KEY BINDINGS IN EMACS MODE

       The following default key bindings, which can be overriden by the Tecla
       configuration  file,  are designed to mimic most of the bindings of the
       unix tcsh shell, when it is in emacs editing mode.

       This is the default editing mode of the Tecla library.

       Under UNIX the terminal driver sets a number of special keys  for  cer-
       tain  functions. The tecla library attempts to use the same keybindings
       to maintain consistency. The key sequences shown for  the  following  6
       bindings  are  thus just examples of what they will probably be set to.
       If you have used the stty  command  to  change  these  keys,  then  the
       default bindings should match.

         ^C     ->   user-interrupt
         ^\     ->   abort
         ^Z     ->   suspend
         ^Q     ->   start-output
         ^S     ->   stop-output
         ^V     ->   literal-next

       The  cursor  keys are refered to by name, as follows. This is necessary
       because different types of terminals generate different  key  sequences
       when their cursor keys are pressed.

         right  ->   cursor-right
         left   ->   cursor-left
         up     ->   up-history
         down   ->   down-history

       The remaining bindings don't depend on the terminal setttings.

         ^F     ->   cursor-right
         ^B     ->   cursor-left
         M-i    ->   insert-mode
         ^A     ->   beginning-of-line
         ^E     ->   end-of-line
         ^U     ->   delete-line
         ^K     ->   kill-line
         M-f    ->   forward-word
         M-b    ->   backward-word
         ^D     ->   del-char-or-list-or-eof
         ^H     ->   backward-delete-char
         ^?     ->   backward-delete-char
         M-d    ->   forward-delete-word
         M-^H   ->   backward-delete-word
         M-^?   ->   backward-delete-word
         M-u    ->   upcase-word
         M-l    ->   downcase-word
         M-c    ->   capitalize-word
         ^R     ->   redisplay
         ^L     ->   clear-screen
         ^T     ->   transpose-chars
         ^@     ->   set-mark
         ^X^X   ->   exchange-point-and-mark
         ^W     ->   kill-region
         M-w    ->   copy-region-as-kill
         ^Y     ->   yank
         ^P     ->   up-history
         ^N     ->   down-history
         M-p    ->   history-search-backward
         M-n    ->   history-search-forward
         ^I     ->   complete-word
         ^X*    ->   expand-filename
         ^X^F   ->   read-from-file
         ^X^R   ->   read-init-files
         ^Xg    ->   list-glob
         ^Xh    ->   list-history
         M-<    ->   beginning-of-history
         M->    ->   end-of-history
         \n     ->   newline
         \r     ->   newline
         M-o    ->   repeat-history
         M-^V   ->   vi-mode

         M-0, M-1, ... M-9  ->  digit-argument  (see below)

       Note  that  ^I is what the TAB key generates, and that ^@ can be gener-
       ated not only by pressing the control key and the @ key simultaneously,
       but  also  by  pressing  the  control key and the space bar at the same
       time.


DEFAULT KEY BINDINGS IN VI MODE

       The following default key bindings are designed to mimic the  vi  style
       of  editing  as  closely  as possible. This means that very few editing
       functions are provided in the initial  character  input  mode,  editing
       functions  instead  being  provided  by the vi command mode. Vi command
       mode is entered whenever the escape character is pressed, or whenever a
       key-sequence  that starts with a meta character is entered. In addition
       to mimicing vi, libtecla provides bindings for  tab  completion,  wild-
       card expansion of file names, and historical line recall.

       To  learn  how  to tell the Tecla library to use vi mode instead of the
       default emacs editing mode, see the earlier section entitled THE  TECLA
       CONFIGURATION FILE.

       Under  UNIX  the terminal driver sets a number of special keys for cer-
       tain functions. The Tecla library attempts to use the same  keybindings
       to maintain consistency, binding them both in input mode and in command
       mode. The key sequences shown for the following  6  bindings  are  thus
       just  examples  of  what they will probably be set to. If you have used
       the stty command to change these keys, then the default bindings should
       match.

         ^C     ->   user-interrupt
         ^\     ->   abort
         ^Z     ->   suspend
         ^Q     ->   start-output
         ^S     ->   stop-output
         ^V     ->   literal-next
         M-^C   ->   user-interrupt
         M-^\   ->   abort
         M-^Z   ->   suspend
         M-^Q   ->   start-output
         M-^S   ->   stop-output

       Note  that above, most of the bindings are defined twice, once as a raw
       control code like ^C and then a second time as a  meta  character  like
       M-^C.  The  former is the binding for vi input mode, whereas the latter
       is the binding for vi command mode.  Once  in  command  mode  all  key-
       sequences  that the user types that they don't explicitly start with an
       escape or a meta key, have their first key secretly converted to a meta
       character  before  the key sequence is looked up in the key binding ta-
       ble. Thus, once in command mode, when you type the letter i, for  exam-
       ple, the Tecla library actually looks up the binding for M-i.

       The  cursor  keys are refered to by name, as follows. This is necessary
       because different types of terminals generate different  key  sequences
       when their cursor keys are pressed.

         right  ->   cursor-right
         left   ->   cursor-left
         up     ->   up-history
         down   ->   down-history

       The  cursor  keys  normally  generate  a keysequence that start with an
       escape character, so beware that using the arrow keys will put you into
       command mode (if you aren't already in command mode).

       The  following  are  the terminal-independent key bindings for vi input
       mode.

         ^D     ->   list-or-eof
         ^G     ->   list-glob
         ^H     ->   backward-delete-char
         ^I     ->   complete-word
         \r     ->   newline
         \n     ->   newline
         ^L     ->   clear-screen
         ^N     ->   down-history
         ^P     ->   up-history
         ^R     ->   redisplay
         ^U     ->   backward-kill-line
         ^W     ->   backward-delete-word
         ^X*    ->   expand-filename
         ^X^F   ->   read-from-file
         ^X^R   ->   read-init-files
         ^?     ->   backward-delete-char

       The following are the key bindings that are defined in vi command mode,
       this  being  specified  by  them all starting with a meta character. As
       mentioned above, once in command mode the  initial  meta  character  is
       optional.  For example, you might enter command mode by typing Esc, and
       then press h twice to move the cursor two positions to the left. Both h
       characters  get  quietly  converted to M-h before being compared to the
       key-binding table, the first one because Escape followed by a character
       is  always  converted  to the equivalent meta character, and the second
       because command mode was already active.

         M-\     ->   cursor-right     (Meta-space)
         M-$     ->   end-of-line
         M-*     ->   expand-filename
         M-+     ->   down-history
         M--     ->   up-history
         M-<     ->   beginning-of-history
         M->     ->   end-of-history
         M-^     ->   beginning-of-line
         M-;     ->   repeat-find-char
         M-,     ->   invert-refind-char
         M-|     ->   goto-column
         M-~     ->   change-case
         M-.     ->   vi-repeat-change
         M-%     ->   find-parenthesis
         M-a     ->   vi-append
         M-A     ->   vi-append-at-eol
         M-b     ->   backward-word
         M-B     ->   backward-word
         M-C     ->   vi-change-rest-of-line
         M-cb    ->   vi-backward-change-word
         M-cB    ->   vi-backward-change-word
         M-cc    ->   vi-change-line
         M-ce    ->   vi-forward-change-word
         M-cE    ->   vi-forward-change-word
         M-cw    ->   vi-forward-change-word
         M-cW    ->   vi-forward-change-word
         M-cF    ->   vi-backward-change-find
         M-cf    ->   vi-forward-change-find
         M-cT    ->   vi-backward-change-to
         M-ct    ->   vi-forward-change-to
         M-c;    ->   vi-change-refind
         M-c,    ->   vi-change-invert-refind
         M-ch    ->   vi-backward-change-char
         M-c^H   ->   vi-backward-change-char
         M-c^?   ->   vi-backward-change-char
         M-cl    ->   vi-forward-change-char
         M-c\    ->   vi-forward-change-char  (Meta-c-space)
         M-c^    ->   vi-change-to-bol
         M-c0    ->   vi-change-to-bol
         M-c$    ->   vi-change-rest-of-line
         M-c|    ->   vi-change-to-column
         M-c%    ->   vi-change-to-parenthesis
         M-dh    ->   backward-delete-char
         M-d^H   ->   backward-delete-char
         M-d^?   ->   backward-delete-char
         M-dl    ->   forward-delete-char
         M-d     ->   forward-delete-char    (Meta-d-space)
         M-dd    ->   delete-line
         M-db    ->   backward-delete-word
         M-dB    ->   backward-delete-word
         M-de    ->   forward-delete-word
         M-dE    ->   forward-delete-word
         M-dw    ->   forward-delete-word
         M-dW    ->   forward-delete-word
         M-dF    ->   backward-delete-find
         M-df    ->   forward-delete-find
         M-dT    ->   backward-delete-to
         M-dt    ->   forward-delete-to
         M-d;    ->   delete-refind
         M-d,    ->   delete-invert-refind
         M-d^    ->   backward-kill-line
         M-d0    ->   backward-kill-line
         M-d$    ->   kill-line
         M-D     ->   kill-line
         M-d|    ->   delete-to-column
         M-d%    ->   delete-to-parenthesis
         M-e     ->   forward-word
         M-E     ->   forward-word
         M-f     ->   forward-find-char
         M-F     ->   backward-find-char
         M--     ->   up-history
         M-h     ->   cursor-left
         M-H     ->   beginning-of-history
         M-i     ->   vi-insert
         M-I     ->   vi-insert-at-bol
         M-j     ->   down-history
         M-J     ->   history-search-forward
         M-k     ->   up-history
         M-K     ->   history-search-backward
         M-l     ->   cursor-right
         M-L     ->   end-of-history
         M-n     ->   history-re-search-forward
         M-N     ->   history-re-search-backward
         M-p     ->   append-yank
         M-P     ->   yank
         M-r     ->   vi-replace-char
         M-R     ->   vi-overwrite
         M-s     ->   vi-forward-change-char
         M-S     ->   vi-change-line
         M-t     ->   forward-to-char
         M-T     ->   backward-to-char
         M-u     ->   vi-undo
         M-w     ->   forward-to-word
         M-W     ->   forward-to-word
         M-x     ->   forward-delete-char
         M-X     ->   backward-delete-char
         M-yh    ->   backward-copy-char
         M-y^H   ->   backward-copy-char
         M-y^?   ->   backward-copy-char
         M-yl    ->   forward-copy-char
         M-y\    ->   forward-copy-char  (Meta-y-space)
         M-ye    ->   forward-copy-word
         M-yE    ->   forward-copy-word
         M-yw    ->   forward-copy-word
         M-yW    ->   forward-copy-word
         M-yb    ->   backward-copy-word
         M-yB    ->   backward-copy-word
         M-yf    ->   forward-copy-find
         M-yF    ->   backward-copy-find
         M-yt    ->   forward-copy-to
         M-yT    ->   backward-copy-to
         M-y;    ->   copy-refind
         M-y,    ->   copy-invert-refind
         M-y^    ->   copy-to-bol
         M-y0    ->   copy-to-bol
         M-y$    ->   copy-rest-of-line
         M-yy    ->   copy-line
         M-Y     ->   copy-line
         M-y|    ->   copy-to-column
         M-y%    ->   copy-to-parenthesis
         M-^E    ->   emacs-mode
         M-^H    ->   cursor-left
         M-^?    ->   cursor-left
         M-^L    ->   clear-screen
         M-^N    ->   down-history
         M-^P    ->   up-history
         M-^R    ->   redisplay
         M-^D    ->   list-or-eof
         M-^I    ->   complete-word
         M-\r    ->   newline
         M-\n    ->   newline
         M-^X^R  ->   read-init-files
         M-^Xh   ->   list-history

         M-0, M-1, ... M-9  ->  digit-argument  (see below)

       Note that ^I is what the TAB key generates.


ENTERING REPEAT COUNTS

       Many of  the  key  binding  functions  described  previously,  take  an
       optional count, typed in before the target keysequence.  This is inter-
       preted as a repeat count by most bindings. A notable exception  is  the
       goto-column binding, which interprets the count as a column number.

       By default you can specify this count argument by pressing the meta key
       while typing in the numeric count. This relies  on  the  digit-argument
       action  being bound to Meta-0, Meta-1 etc.  Once any one of these bind-
       ings has been activated, you can optionally take your  finger  off  the
       meta  key  to type in the rest of the number, since every numeric digit
       thereafter is treated as part of the number, unless it is  preceded  by
       the  literal-next binding. As soon as a non-digit, or literal digit key
       is pressed the repeat count is terminated and either  causes  the  just
       typed  character to be added to the line that many times, or causes the
       next key-binding function to be given that argument.

       For example, in emacs mode, typing:

         M-12a

       causes the letter 'a' to be added to the line 12 times, whereas

         M-4M-c

       Capitalizes the next 4 words.

       In vi command mode the Meta modifier  is  automatically  added  to  all
       characters  typed  in,  so  to  enter  a count in vi command-mode, just
       involves typing in the number, just as it does in the vi editor itself.
       So for example, in vi command mode, typing:

         4w2x

       moves  the cursor four words to the right, then deletes two characters.

       You can also bind digit-argument to other key sequences. If  these  end
       in  a  numeric  digit,  that  digit gets appended to the current repeat
       count. If it doesn't end in a numeric digit,  a  new  repeat  count  is
       started  with  a  value  of zero, and can be completed by typing in the
       number, after letting go of the key which triggered the  digit-argument
       action.


FILES

       libtecla.a      -    The Tecla library
       libtecla.h      -    The Tecla header file.
       ~/.teclarc      -    The personal Tecla customization file.


SEE ALSO

       libtecla, gl_get_line, gl_io_mode, ef_expand_file,
       cpl_complete_word, pca_lookup_file


AUTHOR

       Martin Shepherd  (mcs@astro.caltech.edu)



                                                          tecla
./libtecla/LICENSE.TERMS0100644000076400007640000000275510027466660013047 0ustar mcsmcsCopyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. ./libtecla/CHANGES0100644000076400007640000042430510141252116012125 0ustar mcsmcsIn the following log, modification dates are listed using the European convention in which the day comes before the month (ie. DD/MM/YYYY). The most recent modifications are listed first. 31/10/2004 mcs@astro.caltech.edu (problem reported by Godfrey van der Linden) getline.c The gl_event_handler() function had the endif of a conditional compilation clause in the wrong place. This only upset the compiler on unusual systems that don't have select(). The problem was seen under Mac OS X, due to the configuration problem in 1.6.0 that caused the configure script to mistakenly report that select wasn't available. 31/10/2004 mcs@astro.caltech.edu (info provided by Ivan Rayner) configure.in configure Makefile.in Ivan reported that under IRIX 6.5 it is necessary to add -D_XOPEN_SOURCE=500 to the compiler flags, when compiling the reentrant version of the library. Thus, whereas previously I hardwired the value of DEFINES_R in Makefile.in, I have now made this a variable in the configure script, which is augmented with the above addition, within an IRIX-specific switch clause. Also apparently configure leaves the RANLIB variable blank, instead of setting it to ":", so I have now explicitly set this to ":", within the new IRIX clause of the configure script. 31/10/2004 mcs@astro.caltech.edu (info provided by Ivan Rayner) getline.c Under IRIX, the compiler warned that gl_read_unmasked() was returning an int, which was then being assigned to an enumeration type. This is techically fine, but it highlighted the fact that I had meant to declare gl_read_unmasked() to directly return the enumerated type. I have now done so. 26/09/2004 mcs@astro.caltech.edu getline.c Users can now turn off interactive command-line editing by setting the TERM environment variable to the word "dumb". 18/07/2004 mcs@astro.caltech.edu (problem noted by Michael MacFaden) getline.c Calling gl_terminal_size() on a system without support for SIGWINCH caused a divide-by-zero error in an unintended call to gl_erase_line(), because gl_update_size() was incorrectly being called to query the terminal size, instead of gl_query_size(). 18/07/2004 Padraig Brady (documented here by mcs@astro.caltech.edu) getline.c The suspend and termination signal-handlers installed by gl_tty_signals(), were being installed swapped. 03/06/2004 Mike Meaney (documented here by mcs@astro.caltech.edu) getline.c Mike pointed out the fact that the curses setupterm() function is actually documented to exit the application if an error occurs while its optional errret argument is NULL. I hadn't noticed this, and because I didn't need the extra information returned in the errret argument, I was passing it a NULL. As suggested by Mike, I now pass this argument a pointer to a dummy errret variable. 23/05/2004 mcs@astro.caltech.edu (problem noted by John Beck) man/func/cpl_complete_word.in Some of the prototypes of functions and types documented by the cpl_complete_word man page, weren't listed in the Synopsis section of this man page. They are now listed there. 23/05/2004 mcs@astro.caltech.edu getline.c man/func/gl_get_line.in I have now added support for calling gl_normal_io() from any callback functions that the application installs by calling either gl_inactivity_timeout(), or gl_watch_fd(). Previously, if one of these callback functions called gl_normal_io(), then after returning to gl_get_line(), gl_get_line() would incorrectly assume that the terminal was still in raw I/O mode. Now, gl_get_line() checks to see if gl_normal_io() was called by the callback, and if so, calls _gl_raw_io() to reinstate raw I/O mode. 21/05/2004 mcs@astro.caltech.edu configure.in configure On Mac OS X the code that the configure script used to check for select() failed due to missing symbols in sys/select.h. Moving the inclusion of sys/select.h to after the inclusion of sys/time.h, sys/types.h and sys/unistd.h fixed this. 11/05/2004 mcs@astro.caltech.edu getline.c man/func/gl_get_line.in If the line buffer returned by one call to gl_get_line() was passed as the start_line argument of the next call to gl_get_line(), then instead of the just-entered line being presented back to the user for further editing, the start_line argument was effectively ignored, because the line buffer whose pointer was being passed back, was being cleared before the start_line pointer was examined. This appears to have been a case of me incorrectly thinking that I had forgotten to initialize gl->line[] and gl->ntotal in the gl_reset_input_line() function, and then "fixing" this supposed omission. Removing this erroneous fix, restored things to how they were meant to be. To make it unlikely that I will make the same mistake again, I have renamed the function from gl_reset_input_line() to gl_reset_editor(), to stop it looking as though it is meant to reset the contents of the input line (that is what gl_truncate_buffer() is for), explicitly stated that it doesn't clear the input line, in the header comments of the function, and added a prominent warning comment in the body of the function. Also, since support for passing back the returned line pointer via the start_line argument of the next call to gl_get_line(), wasn't documented in the man page, but was meant to be supported, and definitely used to work, I have now amended the man page documentation of gl_get_line() to explicitly state that this feature is officially supported. 2?/04/2004 Released 1.6.0 22/04/2004 mcs@astro.caltech.edu (Fixed a bug reported by John Beck) getline.c When an error, signal, or other abnormal event aborted gl_get_line(), the cleanup code that restored the terminal to a sane state, also overwrote the value of errno that was associated with the aborting event. An I/O error occurring in the cleanup code would have also overwritten the value to be returned by gl_return_status(), and thus remove any possibility of the caller finding out what really caused gl_get_line() to abort. I have now written a new internal function called, gl_record_status(), which records the completion status to be returned by gl_return_status(), and the value to assign to errno just before gl_get_line() returns. This is called wherever code detects conditions that require gl_get_line() to return early. The function ensures that once an abnormal completion status has been recorded for return, subsequent completions statuses aren't recorded. This ensures that the caller sees the original cause of the abnormal return, rather than any error that occurs during cleaning up from this before return. 17/04/2004 mcs@astro.caltech.edu getline.c If an application's callback called gl_read_char() after calling gl_normal_io(), it would inappropriately redisplay the input line, when it called _gl_raw_io() to temporarily switch the terminal back into raw mode. To fix this, _gl_raw_io() now takes a new 'redisplay' argument, which specifies whether or not to queue a redisplay of the input line. I also created a new gl->postpone flag, which is set by gl_normal_io(), and cleared by _gl_raw_io() (when its redisplay argument is true). When this flag is set, gl_flush_output() ignores queued redisplays, as it generally should between calls to gl_normal_io() and gl_raw_io(). Thus its effect is to postpone redisplays while line editing is suspended. 11/04/2004 mcs@astro.caltech.edu history.c man/misc/tecla.in History searches can now include the globbing operators *, ?, []. When a search prefix is found to have at least one of these characters, then only history lines that completely match that pattern are returned. 11/04/2004 mcs@astro.caltech.edu (issue raised by Mark Coiley) getline.c ioutil.c There appears to be a bug in Solaris's terminal I/O. When the terminal file descriptor is placed in non-blocking I/O mode, and the terminal is switched from canonical to raw mode, characters that were previously entered in canonical I/O mode don't become available to be read until the user types one character more. Select() incorrectly says that there are no characters available, and read() returns EAGAIN. This is only a problem for gl_get_line() when gl_get_line() is in non-blocking server I/O mode, so most users won't have experienced any problems with this. The only way that I have found to get read() to return the characters, without the user first having to type another character, is to turn off non-blocking I/O before calling read(). Select() still claims that there are no characters available to be read, but read happily returns them anyway. Fortunately, one can perform non-blocking terminal reads without setting the non-blocking I/O flag of the file descriptor, simply by setting the VTIME terminal attribute to zero (which I already was doing). Thus, when in non-blocking server I/O, I now turn off the non-blocking I/O flag, attempt to read one character and only if this fails, do I then call the select() based event handler to implement any configured non-zero timeout, before attempting the read again. Of course the non-blocking I/O flag is still needed for writing, so I only turn it off temporarily while reading. 25/03/2004 mcs@astro.caltech.edu (bug reported by Gregory Harris) Makefile.in It appears that when in February, I patched Makefile.in to add abolute paths to the install-sh shell-script, I accidentally replaced install-sh with install.sh. I corrected the name in the Makefile. 25/03/2004 Gregory Harris (documented here by mcs) configure.in configure Greg added the configuration parameters needed to build the shared version of the libtecla library under FreeBSD. 25/03/2004 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man/func/gl_get_line.in man/func/gl_read_char.in I wrote a public function called gl_read_char(). Unlike gl_query_char(), this function neither prompts the user for input, nor displays the character that was entered. In fact it doesn't write anything to the terminal, and takes pains not to disturb any incompletely entered input line, and can safely be called from application callback functions. 21/03/2004 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man/func/gl_get_line.in man/func/gl_query_char.in I wrote a public function called gl_query_char(), which prompts the user and awaits a single-character reply, without the user having to hit return. 23/02/2004 mcs@astro.caltech.edu (bug reported by Gregory Harris) configure.in configure getline.c enhance.c demo3.c The configure script now checks for the sys/select.h header file, and arranges for a C macro called HAVE_SYS_SELECT_H to be set if it exists. Thus the files that use select() now use this macro to conditionally include sys/select.h where available. Apparently this header is required under FreeBSD 5.1. 23/02/2004 mcs@astro.caltech.edu getline.c libtecla.h man/func/gl_get_line.in I wrote two new public functions, gl_append_history() and gl_automatic_history(). Together these allow the application to take over the responsibility of adding lines to the history list from gl_get_line(). I then documented their functionality in the gl_get_line man page. Version 1.6.0 I incremented the minor version number of the library, to comply with the requirement to do so when additions are made to the public interface. See libtecla.map for details. libtecla.map I added a new 1.6.0 group for the new minor version, and added the above pair of functions to it. 15/02/2004 mcs@astro.caltech.edu (fixes a bug reported by Satya Sahoo) history.c Calling gl_load_history() multiple times, eventually led to a segmentation fault. This was due to the head of the list of unused history string segments not getting reset when the history buffer was cleared. While debugging this problem I also noticed that the history resizing function was way too complicated to verify, so after fixing the above bug, I heavily simplified the history resizing function, trading off a small reduction in memory efficiency, for greatly improved clarity, and thus made it much more verifiable and maintainable. 14/02/2004 mcs@astro.caltech.edu (fixes a bug reported by Tim Burress). getline.c If gl_change_terminal() was first used to tell gl_get_line to read input from a file, then called later to tell it to read subsequent input from a terminal, no prompt would be displayed for the first line of interactive input. The problem was that on reaching the end of the input file, gl_get_line() should have called gl_abandon_line(), to tell the next call to gl_get_line() to start inputting a new line from scratch. I have added this now. 14/02/2004 Krister Walfridsson (documented here by mcs@astro.caltech.edu) Makefile.in Krister noticed that I had failed to put $(srcdir)/ in front of some invokations of install.sh. I have remedied this. config.guess config.sub I hadn't updated these for a long time, so apparently they didn't recognise the BSD system that Krister was using. I have now updated them to the versions that come with autoconf-2.59. 22/01/2004 mcs@astro.caltech.edu keytab.c When parsing key-binding specifications, backslash escaped characters following ^ characters were not being expanded. Thus ^\\ got interpretted as a control-\ character followed by a \ character, rather than simply as a control-\ character. 12/01/2004 mcs@astro.caltech.edu cplfile.c cplmatch.c demo2.c demo3.c demo.c direader.c expand.c getline.c history.c homedir.c pathutil.c pcache.c configure.in configure INSTALL The configuration script now takes a "--without-file-system" argument. This is primarily for intended for embedded systems that either don't have filesystems, or where the file-system code in libtecla is unwanted bloat. It sets the WITHOUT_FILE_SYSTEM macro. This removes all code related to filesystem access, including the entire public file-expansion, file-completion and path-lookup facilities. Note that the general word completion facility is still included, but without the normally bundled file completion callback. Actually the callback is still there, but it reports no completions, regardless of what string you ask it to complete. This option is described in the INSTALL document. 12/01/2004 mcs@astro.caltech.edu getline.c configure.in configure INSTALL The configuration script now takes a "--without-file-actions" argument. This allows an application author/installer to prevent users of gl_get_line() from accessing the filesystem from the builtin actions of gl_get_line(). It defines a macro called HIDE_FILE_SYSTEM. This causes the "expand-filename", "read-from-file", "read-init-files", and "list-glob" action functions to be completely removed. It also changes the default behavior of actions such as "complete-word" and "list-or-eof" to show no completions, instead of the normal default of showing filename completions. This option is described in the INSTALL document. 11/01/2004 mcs@astro.caltech.edu getline.c man/func/gl_get_line.in In case an application's customized completion handler needs to write to the terminal for some unforseen reason, there needs to be a way for the it to cleanly suspend raw line editing, before writing to the terminal, and the caller then needs to be aware that it may need to resurrect the input line when the callback returns. I have now arranged that the completion callback functions can call the gl_normal_io() function for this purpose, and documented this in the gl_get_line() man page. 11/01/2004 mcs@astro.caltech.edu (In response to a bug report by Satya Sahoo) getline.c The gl_configure_getline() function makes a malloc'd copy of the names of the configuration files that it is asked to read. Before the bug fix, if the application made one or more calls to this function, the memory allocated by the final call that it made before calling del_GetLine(), wasn't being freed. Note that memory allocated in all but the final call was being correctly freed, so the maximum extent of the memory leak was the length of the file name(s) passed in the final call to gl_configure_getline(), and an application that didn't call gl_configure_getline() didn't suffer any leak. 20/12/2003 mcs@astro.caltech.edu history.c Ellen tested the history fix that I reported below, and pointed out that it still had a problem. This turned out to be because getline.c was making some incorrect assumptions about the new behavior of history.c. This problem and the previous one both revolved around how search prefixes were stored and discarded, so I have now re-written this part of the code. Previously the search prefix was retained by looking for a line with that prefix, and keeping a pointer to that line. This saved memory, compared to storing a separate copy of the prefix, but it led to all kinds of hairy interdependencies, so I have now changed the code to keep a separate copy of search prefixes. To keep the memory requirements constant, the search prefix is stored in the history buffer, like normal history lines, but not referenced by the time-ordered history list. The prefix can now be kept around indefinitely, until a new search prefix is specified, regardless of changes to the archived lines in the history buffer. This is actually necessary to make the vi-mode re-search actions work correctly. In particular, I no longer discard the search prefix whenever a history search session ends. Also, rather than have getline.c keep its own record of when a history session is in progress, it now consults history.c, so that failed assumptions can't cause the kind of discrepancy that occurred before. For this to work, getline.c now explicitly tells history.c to cancel search sessions whenever it executes any non-history action. 14/12/2003 mcs@astro.caltech.edu (bug reported by Ellen Oschmann) history.c If one searched backwards for a prefix, then returned to the original line, changed that line, then started another backwards prefix search, getline incorrectly discarded the new search prefix in the process of throwing away its cached copy of the previous pre-search input line. In other words getline was belatedly cancelling a previous search, after a new search had already partially begun, and thus messed up the new search. The obvious fix was to arrange for the current search to be cancelled whenever the history pointer returns to its starting point, rather than waiting for the next search to begin from there. 14/12/2003 mcs@astro.caltech.edu history.c _glh_recall_line() was returning the last line in the history buffer instead of the line requested by the caller. This only affected the obscure "repeat-history" action-function, which probably isn't used by anybody. 09/12/2003 Version 1.5.0 released. 28/09/2003 mcs@astro.caltech.edu homedir.c When the home directory of the login user is requested, see if the HOME environment variable exists, and if so return its value, rather than looking up the user's home directory in the password file. This seems to be the convention adopted by other unix programs that perform tilde expansion, and it works around a strange problem, where a third-party libtecla program, statically compiled under an old version of RedHat, unexpectedly complained that getpwd() returned an error when the program was run under RedHat 9. 01/09/2003 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man/func/gl_get_line.in man/func/gl_register_action.in. It is now possible for an application to register external functions as action functions. These actions are initially bound to specified key-sequences, but if they are registered before the user's configuration file is loaded, they can also be re-bound by the user to different key-sequences. The function used to register a new action, is called gl_register_action(). Action functions are passed a readonly copy of the input line and the cursor position. They can display text to the terminal, or perform other operations on the application environment. Currently, they can't edit the input line or move the cursor. This will require the future addition of functions to queue the invokation of the built-in action functions. 26/08/2003 mcs@astro.caltech.edu getline.c I modified gl_update_buffer() to ensure that the cursor stays within the input line after external line modifications, and to queue a redisplay of the potentially modified input line. 21/07/2003 mcs@astro.caltech.edu configure.in configure Makefile.in Makefile.stub INSTALL By specifying --without-man-pages or --with-man-pages=no as command-line arguments to the configure script, it is now possible to have the configure script skip the man-page preprocessing step, and arrange for the man-page installation targets in the Makefile to do nothing. This option is designed for people who embed libtecla within other packages. It is also used by Makefile.stub when the distclean target is specified. 21/07/2003 mcs@astro.caltech.edu configure.in configure The previous workaround for recent versions of gcc placing /usr/local/include at the start of the system inlcude-file search path, broke something else. The fix placed /usr/include before gcc's include area, which meant that gcc's modified version of stdarg.h was being ignored in deference to the version in /usr/include. I have changed the fix to have gcc report the search path, then have awk add options to CFLAGS to reorder this path, plaing /usr/local/include at the end. Also, under Solaris 9, including term.h without first including curses.h results in complaints about undefined symbols, such as bool. As a result the configure script's test for term.h was failing. I have now modified it to include curses.h in the test code that it uses to check for term.h. In the process I also improved the tests for curses.h and term.h to prevent an ncurses version of term.h from being used with the system-default version of curses.h. 29/06/2003 mcs@astro.caltech.edu Makefile.in direader.c homedir.c On some systems (eg. linux) the _POSIX_C_SOURCE feature-test macro is set by system headers, rather than being an option set by a project's Makefile at compilation time. In software, such as tecla, where the definition of this macro is used as an indication of whether to use the non-reentrant or reentrant versions of system functions, this means that the reentrant functions are always used, regardless of whether this macro is set or not by the project Makefile. Thus, on such systems the reentrant and non-reentrant versions of the tecla library are essentially identical. This has a couple of drawbacks. First, since thread-safe functions for traversing the password file don't exist, the supposedly non-reentrant version of the tecla library can't support ambiguous tab-completion of usernames in ~username/ constructions. Secondly, on some systems the use of reentrant system functions dictates the use of a shared library that isn't needed for the non-reentrant functions, thus making it more difficult to distribute binary versions of the library. To remedy this situation I have modified the DEFINES_R variable in Makefile.in to arrange for the compiler to define a C macro called PREFER_REENTRANT when it is compiling the reentrant version of the tecla library. This macro is now used in the source code to determine when to require reentrant code. Whithin the source code, wherever a potentially non-reentrant interface is used, the existance of both this macro and a suitably valued _POSIX_C_SOURCE macro, are tested for to see if a reentrant alternative to the problem code should be used. 22/06/2003 mcs@astro.caltech.edu getline.c I changed the way that redisplays are requested and performed. Redisplays are now queued by calling gl_queue_redisplay(), and subsequently performed by gl_flush_output(), when the queue of already pending output has been completely dispatched. This was necessary to prevent event handlers from filling up the output queue with redisplays, and it also simplifies a number of things. In the process I removed the gl_queue_display() function. I also wrote a gl_line_erased() function, which is now called by all functions that erase the input line. I also split the gl_abandon_line() function into public and private callable parts, and used the private version internally to arrange to discard the input line after errors. The raw_mode flag was not being initialized by new_GetLine(). It is now initialized to zero. I removed the zapline flag, since using the endline flag to communicate the desire to terminate the line, did the same thing. gl_terminal_move_cursor() now does nothing when the input line isn't displayed. 18/03/2003 mcs@astro.caltech.edu getline.c Fixed bug which was causing newlines not to be output at the end of each newly entered line. I was interpreting the gl->endline flag in conflicting ways in two places. To fix this I have created a gl->displayed flag. This flags whether an input line is currently displayed. 17/03/2003 mcs@astro.caltech.edu getline.c libtecla.h man/func/gl_get_line.in man/func/gl_erase_terminal.in libtecla.map I added a new function that programs can call to clear the terminal between calls to gl_get_line(). 11/03/2003 mcs@astro.caltech.edu configure.in configure Under linux when _POSIX_C_SOURCE is defined, getpwent() and associated functions become undefined, because _SVID_SOURCE and _BSD_SOURCE become undefined. Adding these feature macros back to CFLAGS resolves this. 06/03/2003 mcs@astro.caltech.edu getline.c libtecla.map man/func/gl_get_line.in Following the lead of Edward Chien, I wrote a function called gl_bind_keyseq(), which binds a specified key-sequence to a given action, or unbinds the key-sequence. 24/02/2003 mcs@astro.caltech.edu getline.c libtecla.map man/func/cpl_complete_word.in I implemented a simple function called cpl_recall_matches(). This recalls the return value of the last call to cpl_complete_word(). 19/01/2003 mcs@astro.caltech.edu getline.c The documented signal handling, fd event-handling, inactivity timeout handling, and server-mode non-blocking I/O features are now implemented for non-interactive input streams, such as pipes and files. 19/01/2003 mcs@astro.caltech.edu getline.c libtecla.h man/func/gl_get_line.in demo3.c I added a new return status enumerator to report when an end-of-file condition causes gl_get_line() to return NULL. 13/01/2003 mcs@astro.caltech.edu history.c I rewrote the history facility. The previous circular buffer implementation was a nightmare to change, and it couldn't efficiently support certain newly requested features. The new implementation stores history lines in linked lists of fixed sized string segments, taken from the buffer, with each line being reference counted and recorded in a hash table. If the user enters a line multiple times, only one copy of the line is now stored. Not only does this make better use of the available buffer space, but it also makes it easy to ensure that a line whose prefix matches the current search prefix, isn't returned more than once in sequence, since we can simply see if the latest search result has the same hash-table pointer as the previous one, rather than having to compare strings. Another plus is that due to the use of linked lists of nodes of fixed size line segments, there is no longer any need to continually shuffle the contents of the buffer in order to defragment it. As far as the user is concerned, the visible differences are as follows: 1. If the user enters a given line multiple times in a row, each one will be recorded in the history list, and will thus be listed by gl_show_history(), and saved in the history file. Previously only one line was recorded when consecutive duplicates were entered. This was a kludge to prevent history recall from recalling the same line multiple times in a row. This only achieved the desired result when not recalling by prefix. 2. Not only simple recall, but prefix-based history line recalls now don't return the same line multiple times in a row. As mentioned in (1) above, previously this only worked when performing a simple recall, without a search prefix. 28/12/2002 mcs@astro.caltech.edu getline.c The one-line function, gl_buff_curpos_to_term_curpos() was only being used by gl_place_cursor(), so I inlined it in that function, and removed it. 28/12/2002 mcs@astro.caltech.edu getline.c gl_suspend_process() was calling the application-level gl_normal_io() and gl_raw_io() functions, where it should have been calling the internal versions _gl_normal_io() and _gl_raw_io(). Also gl_handle_signal() was masking and unmasking just the signals of the first element of the gl[] array argument. It now masks and unmasks all trappable signals. 28/12/2002 mcs@astro.caltech.edu getline.c Now that the number of terminal characters used to display the current input line, is recorded, the relative line on which the last character of the input line resides can be determined without having to call gl_buff_curpos_to_term_curpos(). This is now used by gl_normal_io() via gl_start_newline(), so there is now no need for gl_buff_curpos_to_term_curpos() to be async-signal safe. I have thus removed the annoying gl->cwidth[] array, and gl_buff_curpos_to_term_curpos() now calls gl_width_of_char() directly again. There is also now no need for the gl_line_of_char_start() and gl_line_of_char_end() functions, so I have removed them. 28/12/2002 mcs@astro.caltech.edu getline.c Unfortunately it turns out that the terminfo/termcap control sequence which is defined to delete everything from the current position to the end of the terminal, is only defined to work when at the start of a terminal line. In gnome terminals in RedHat 8.0, if it is used within a terminal line, it erases the whole terminal line, rather than just what follows the cursor. Thus to portably truncate the displayed input line it is necessary to first use the control sequence which deletes from the cursor position to the end of the line, then if there are more terminal lines, move to the start of the next line, and use the delete to end-of-terminal control sequence, then restore the cursor position. This requires that one know how many physical terminal lines are used by the current input line, so I now keep a record of the number of characters so far displayed to the terminal following the start of the prompt, and the new gl_truncate_display() function uses this information to truncate the displayed input line from the current cursor position. 28/12/2002 mcs@astro.caltech.edu getline.c gl_start_newline() now moves to an empty line following the input line, rather than just to the next line. It also arranges for the input line to be redisplayed before editing resumes. A major user of this is gl_print_info(), which now need not be followed by an explicit call to gl_redisplay(), since the terminal input loop in gl_get_input_line() ensures that gl_redisplay() is called after any action function that asserts gl->redisplay. Also, all functions that erase the displayed input line can now call the gl_erase_line() function, which is designed to work correctly even when a terminal resize invalidates the horizontal cursor position. Finally, the new gl_queue_display() function is now used by functions that need to arrange for the input line to be displayed from scratch after the displayed line has been erased or invalidated by other text being written to the terminal. All of these changes are aimed at reducing the number of places that directly modify gl->term_curpos and gl->redisplay. 22/12/2002 Markus Gyger (logged here by mcs) Makefile.in update_html In places where echo and sed were being used to extract the base names of files, Markus substituted the basename command. He also replaced explicit cp and chmod commands with invokations of the install-sh script. configure.in Use $target_os and $target_cpu, where appropriate, instead of $target. configure.in The Solaris man function and library man pages should be in sections 3lib and 3tecla respectively, only in Solaris version 2.8 and above. configure.in Markus provided values for the man page configuration variables for HPUX. man/*/*.in I had missed parameterizing man page section numbers in the man page titles, Markus corrected this. man/func/libtecla_version.in Fixed incorrect section number in the link to the libtecla man page. homedir.c When compiled to be reentrant, although one can't use the non-reentrant getpwent() function to scan the password file for username completions, one can at least see if the prefix being completed is a valid username, and if the username of the current user minimally matches the prefix, and if so list them. I simplified Markus' modification by adding a prefix argument to the _hd_scan_user_home_dirs() function, and redefining the function description accordingly, such that now it reports only those password file entries who's usernames minimally match the specified prefix. Without this, it would have been necessary to peak inside the private data argument passed in by cf_complete_username(). Markus also provided code which under Solaris uses the non-reentrant interfaces if the reentrant version of the library isn't linked with the threads library. 19/12/2002 mcs@astro.caltech.edu Makefile.in Markus pointed out that LDFLAGS was being picked up by the configure script, but not then being interpolated into te Makefile. I have thus added the necessary assignment to Makefile.in and arranged for the value of LDFLAGS to be passed on to recursive make's. I also did the same for CPPFLAGS, which had also been omitted. 18/12/2002 mcs@astro.caltech.edu man/* man/*/* configure.in configure Makefile.in update_html It turns out that the assignment of man page sections to topics differs somewhat from system to system, so this is another thing that needs to be configured by the main configuration script, rather than being hardwired. All man pages have now been moved into suitably named topic-specific sub-directories of the top-level man directory, and instead of having a numeric suffix, now have the .in suffix, since they are now preprocessed by the configure script, in the same fashion as Makefile.in. Whithin these *.in versions of the man pages, and within Makefile.in, the installation subdirectory (eg. man1) and the file-name suffix (eg. 1), are written using configuration macros, so that they get expanded to the appropriate tokens when the configure script is run. In principle, the man pages could also take advantage of other configuration macros, such as the one which expands to the library installation directory, to include full path names to installed files in the documentation, so in the future this feature could have more uses than just that of parameterizing man page sections. 18/12/2002 mcs@astro.caltech.edu man3 man3/* Makefile.in html/index.html update_html Markus suggested splitting the gl_get_line(3) man page into user and developer sections, and also pointed out that the enhance man page should be in section 1, not section 3. I have thus created a top-level man directory in which to place the various sections, and moved the man3 directory into it. The enhance.3 man page is now in man/man1/enhance.1. I have extracted all user-oriented sections from the gl_get_line(3) man page and placed them in a new man7/tecla.7 man page. 18/12/2002 mcs@astro.caltech.edu getline.c Terminal resizing was broken in normal mode, due to me forcing the terminal cursor position to zero in the wrong place in gl_check_caught_signal(). 14/12/2002 Markus Gyger (logged here by mcs) configure.in configure Under Solaris, recent versions of gcc search /usr/local/include for header files before the system directories. This caused a problem if ncurses was installed under Solaris, since the termcap.h include file in /usr/local/include ended up being used at compile time, whereas the system default version of the curses library was used at link time. Since the two libraries declare tputs() differently, this evoked a complaint from gcc. Markus came up with a way to force Gnu cpp to move /usr/local/include to the end of the system-include-file search path, where it belongs. 13/12/2002 mcs@astro.caltech.edu man3/gl_io_mode.3 I rewrote the man page which documents the new non-blocking server I/O mode. 12/12/2002 mcs@astro.caltech.edu demo3.c I wrote a new version of demo3.c, using signal handlers that call gl_handle_signal() and gl_abandon_line(), where previously in this demo, these functions were called from the application code. 05/12/2002 mcs@astro.caltech.edu getline.c gl_normal_io(), gl_raw_io() and gl_handle_signal() and gl_abandon_line() are now signal safe, provided that signal handlers that call them are installed with sa_mask's that block all other signals who's handlers call them. This is the case if gl_tty_signals() is used to install signal handlers that call any of these functions. A major stumbling block that had to be overcome was that gl_displayed_char_width() calls isprint(), which can't safely be called from a signal handler (eg. under linux, the is*() functions all use thread-specific data facilities to support per-thread locales, and the thread-specific data facilities aren't signal safe). To work around this, all functions that modify the input-line buffer, now do so via accessor functions which also maintain a parallel array of character widths, for use by gl_buff_curpos_to_term_curpos() in place of gl_displayed_char_width(). Other minor problems were the need to avoid tputs(), who's signal safety isn't defined. 05/12/2002 Eric Norum (logged here by mcs@astro.caltech.edu) configure.in Eric provided the configuration information needed to build shared libraries under Darwin (Max OS X). 05/12/2002 Richard Mlynarik (logged here by mcs@astro.caltech.edu) configure.in AC_PROG_RANLIB gets the wrong version of ranlib when cross compiling, so has now been replaced by an invokation of AC_CHECK_TOOL. In addition, AC_CHECK_TOOL is also now used to find an appropriate version of LD. 05/12/2002 mcs@astro.caltech.edu (based on patch by Pankaj Rathore) getline.c libtecla.h libtecla.map man3/gl_get_line.3 The new gl_set_term_size() function provides a way to tell gl_get_line() about changes in the size of the terminal in cases where the values returned by ioctl(TIOCGWINSZ) isn't correct. 05/12/2002 mcs@astro.caltech.edu getline.c Rather than calling sprintf() to see how much space would be needed to print a given number in octal, I wrote a gl_octal_width() function, for use by gl_displayed_char_width(). This makes the latter function async signal safe. 05/12/2002 mcs@astro.caltech.edu chrqueue.c Whenever the buffer is exhausted, and getting a new buffer node would require a call to malloc(), attempt to flush the buffer to the terminal. In blocking I/O mode this means that the buffer never grows. In non-blocking I/O mode, it just helps keep the buffer size down. 05/12/2002 mcs@astro.caltech.edu freelist.h freelist.c The new _idle_FreeListNodes() function queries the number of nodes in the freelist which aren't currently in use. 05/12/2002 mcs@astro.caltech.edu Makefile.stub This now accepts all of the targets that the configured makefile does, and after configuring the latter makefile, it invokes it with the same options. 03/12/2002 mcs@astro.caltech.edu mans3/gl_io_mode.3 I completed the man page for all of the new functions related to non-blocking I/O. 01/12/2002 mcs@astro.caltech.edu man3/gl_get_line.3 I wrote a long section on reliable signal handling, explaining how gl_get_line() does this, how to make use of this in a program, and how to handle signals reliably when faced with other blocking functions. This basically documents what I have learnt about signal handling while working on this library. 01/12/2002 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 In non-blocking server mode, the gl_replace_prompt() function can now be used between calls to gl_get_line() if the application wants to change the prompt of the line that is being edited. 01/12/2002 mcs@astro.caltech.edu man3/gl_get_line.3 I documented the new gl_return_status() and gl_error_message() functions. 01/12/2002 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 Added SIGPOLL and SIGXFSZ to the list of signals that are trapped by default. These are process termination signals, so the terminal needs to be restored to a usable state before they terminate the process. 27/11/2002 mcs@astro.caltech.edu getline.c libtecla.h Completed the essential changes needed to support non-blocking server-I/O mode. The new gl_io_mode() function allows one to switch to and from non-blocking server-I/O mode. The new gl_raw_io() function is used in non-blocking server-I/O mode to switch the terminal into non-blocking raw I/O mode. The new gl_normal_io() function is used in non-blocking server-I/O mode to switch the restore the terminal to a normal, blocking state. This is used to suspend line input before suspending the process or writing messages to the terminal. The new gl_tty_signals() function installs specified signals handlers for all signals that suspend, terminate or resume processes, and also for signals that indicate that the terminal has been resized. This not only saves the application from having to keep its own ifdef'd list of such signals, of which there are many, but it also makes sure that these signal handlers are registered correctly. This includes using the sa_mask member of each sigaction structure to ensure that only one of these handlers runs at a time. This is essential to avoid the signal handlers all trying to simultaneously modify shared global data. The new gl_handle_signal() function is provided for responding (from application level) to signals caught by the application. It handles process suspension, process termination and terminal resize signals. The new gl_pending_io() function tells the application what direction of I/O gl_get_line() is currently waiting for. In non-blocking server I/O mode, the new gl_abandon_line() function can be called between calls to gl_get_line() to discard an input line and force the next call to gl_get_line() to start the input of a new line. Also, in non-blocking server-I/O gl_get_line() doesn't attempt to do anything but return when one of the signals that it is configured to catch is caught. This is necessary because when in this mode, the application is required to handle these signals when gl_get_line() is running, and the default configuration of most of these signals in gl_get_line() is to restore the terminal then call the application signal handlers. This would be a case of too many cooks spoiling the broth, so in this mode, gl_get_line() always defers to the application's signal handlers. 26/11/2002 mcs@astro.caltech.edu getline.c libtecla.h I implemented a couple of new functions to support reliable signal handling, as now documented (see above) in the gl_get_line(3) man page. The new gl_catch_blocked() function tells gl_get_line() to unblock all configured signals around calls to long-running functions, not only those that aren't blocked when gl_get_line() is called. This allows the caller to implement reliable signal handling, since the unblocking is only done from within code protected by sigsetjmp(), which avoids race conditions. The new gl_list_signals() function fills a provided sigset_t with the set of signals that gl_get_line() is currently configured to catch. This allows callers to block said signals, such that they are only unblocked by gl_get_line() when it is waiting for I/O. When used in conjunction with the gl_catch_blocked() function, this removes the potential for race conditions. Also, when gl_get_line() installs its signal handler, it uses the sa_mask member of the sigaction structure to ensure that only one instance of this signal handler will ever be executing at a time. 25/11/2002 mcs@astro.caltech.edu (bug reported by Pankaj Rathore) getline.c When any history recall action was invoked when the input line buffer was full, an error message would be displayed complaining about the length of the string in the line input buffer being inconsistent with the specified allocated size. This was because instead of sending the allocated size of the input line, I was sending the length excluding the element that is reserved for the '\0' terminator. Sending it the correct size corrected the problem. 24/11/2002 mcs@astro.caltech.edu getline.c All public functions which take GetLine objects as arguments now block signals on entry and restore the signal mask on return. This was an attempt to make it safe to call getline functions from signal handlers, but the fact is that the functions that I really wanted this to apply to, potentially call malloc(), so this currently isn't the case. 23/11/2002 mcs@astro.caltech.edu getline.c libtecla.h The new gl_return_status() function returns an enumerated return status which can be used to query what caused gl_get_line() to return. 22/11/2002 mcs@astro.caltech.edu Most existing .c and .h files, plus errmsg.c errmsg.h Makefile.rules Until now, many library functions would report error messages to stderr. This isn't appropriate for library functions, so in place of this behavior, error messages are now recorded in internal ErrMsg objects, and passed between modules via new module-specific error querying functions. In addition, errno is now set appropriately. Thus when gl_get_line() and related functions return an error, strerror() can be used to look up system errors, and gl_error_message() can be used to recover a higher level error message. Note that error messages that are responses to user actions continue to be reported to the terminal, as before. 21/11/2002 mcs@astro.caltech.edu getline.c keytab.h keytab.c Makefile.rules I wrote a new version of _kt_lookup_binding() that didn't require the caller to have access to the innards of a KeyTab object. This then enabled me to move the definition of KeyTab objects into keytab.c and make the typedef in keytab.h opaque. Many nested includes were also moved from keytab.h into keytab.c. 05/11/2002 mcs@astro.caltech.edu getline.c libtecla.map libtecla.h demo3.c I split the old gl_resize_terminal() function into two parts, gl_query_size() and gl_update_size(), with the latter calling the former to get the new terminal size. 05/11/2002 mcs@astro.caltech.edu getline.c I fixed a long time bug in the terminal resizing code. When the cursor wasn't on the last terminal line of the input line, the resizing code would redisplay the the line one or more lines above where it should be restored. This was due to an error in the calculation of the number of lines above the cursor position. 04/11/2002 mcs@astro.caltech.edu demo.c demo2.c demo3.c I used the new gl_display_text() function to display introductory text at the startup of each of the demo programs. The text is enclosed within a box of asterixes, drawn dynamically to fit within the confines of the available terminal width. 04/11/2002 mcs@astro.caltech.edu libtecla.h getline.c ioutil.c ioutil.h Makefile.rules libtecla.map man3/gl_get_line.3 man3/gl_display_text.3 Needing a way to display introductory text intelligently in the demo programs, I wrote and documented the gl_display_text() function. This justifies arbitrary length text within the bounds of the terminal width, with or without optional indentation, prefixes and suffixes. 03/11/2002 mcs@astro.caltech.edu demo3.c Makefile.rules I wrote a new demonstration program. This program acts exactly like the main demonstration program, except that it uses an external event loop instead of using the gl_get_line() internal event loop. This is thus an example of the new non-blocking server I/O facility. 02/11/2002 mcs@astro.caltech.edu getline.c keytab.c keytab.h libtecla.h man3/gl_get_line.3 man3/gl_completion_action.3 I added the ability to register additional word completion actions via the new function gl_completion_action(). All action functions now take a new (void *data) argument, which is stored with the function in the symbol table of actions. The new gl_completion_action() function uses this feature to record dynamically allocated objects containing the specified completion function and callback data along with either the gl_complete_word() action function, or the gl_list_completions() action function. These two actions continue to use the builtin completion functions when their data pointer is NULL. 20/10/2002 mcs@astro.caltech.edu The following are changes merged from the non-blocking gl_get_line() development branch. getline.c I wrote a gl_start_newline() function, to replace all of the explicit calls to output \r\n to stdout. Informational messages are now written to the terminal using a new variadic function called gl_print_info(). This starts a newline, writes string arguments until a special argument, GL_END_INFO, is seen, then starts another newline. Changed _output_ to _print_ in the following function names gl_output_control_sequence(), gl_output_char(), gl_output_string() and gl_output_raw_string(). gl_print_raw_string() now has a length argument, so that strings that aren't terminated with '\0' can be printed. The display of the initial contents of a new line to be edited has been moved into a new function called gl_present_line(). The gl_get_input_line() function now takes the prompt string as an argument so that gl_replace_prompt() can be called from within this function instead of from gl_get_line(). Keyboard input is now buffered in a persistent buffer in the parent GetLine object. gl_read_character() checks this for unprocessed characters in preference to calling gl_read_terminal() to append characters to it. A new function, gl_discard_chars(), removes processed characters from this buffer. This change is in preparation for a non-blocking version of gl_get_line(), where partially input key-sequences must be stored between calls to gl_get_line(). getline.c getline.h history.c history.h cplmatch.c \ cplmatch.h expand.c expand.h All terminal output from gl_get_line() is now routed through a GL_WRITE_FN() callback function called gl_write_fn. Internal functions in cplmatch.c, expand.c and history.c have been created which take such callbacks to write output. These are used both by functions in getline.c, to display file completions, expansions, history etc, and as the internals of existing public functions in these files that print to stdio streams. In the latter case an internal stdio GL_WRITE_FN() callback is substituted, so that the functions behave as before. getline.c chrqueue.c chrqueue.h The gl_write_fn() callback used by gl_get_line() now writes to a queue, implemented in chrqueue.c. This queue is implemented as a list of blocks of buffer segments, the number of which shrink and grow as needed. The contents of the queue are flushed to the terminal via another GL_WRITE_FN() callback passed to the queue object. Currently gl_get_line() passes an internal function assigned to gl->flush_fn, called gl_flush_terminal(), which writes the contents of the queue to the terminal, and knows how to handle both blocking and non-blocking I/O. The output queue is designed to be flushed to the terminal incrementally, and thereby also facilitates non-blocking I/O. getline.c getline.h gl_get_line() now reads all input via the GL_READ_FN() callback, assigned to gl->read_fn. Currently this is set to an internal function called gl_read_terminal(), which knows how to handle both blocking and non-blocking I/O. getline.c libtecla.h The new gl_set_nonblocking() function can be used to enable or disable non-blocking I/O. The default is still blocking I/O. In non-blocking mode, the terminal is told not to wait when either reading or writing would block. gl_get_line() then returns, with a return value of NULL, but with the terminal left in raw mode, so that the caller's event loop can detect key presses. The caller should call gl_return_status() to check whether the NULL return value was due to an error, lack of input, or inability to write to the terminal without waiting. If either reading or writing was said to have blocked, the user then should check for I/O readiness in the specified direction before calling gl_get_line() again to incrementally build up the input line. 05/08/2002 mcs@astro.caltech.edu man3/gl_get_line.3 man3/gl_inactivity_timeout.3 I documented the new gl_inactivity_timeout() function. 08/07/2002 mcs@astro.caltech.edu libtecla.h getline.c libtecla.map I added a new gl_inactivity_timeout() function. On systems that have the select system call, this provides the option of registering a function that is then called whenever no I/O activity has been seen for more than a specified period of time. Like the gl_watch_fd() facility, timeout callbacks return a code which tells gl_get_line() how to proceed after the timeout has been handled. 04/07/2002 mcs@astro.caltech.edu (based on a bug report from Michael MacFaden) getline.c The internal event handler wasn't responding to write events on client file descriptors, due to a typo which resulted in read events being checked for twice, and writes not checked for at all. pathutil.c The amount of space to allocate for pathnames is supposed to come from PATH_MAX in limits.h, but I had neglected to include limits.h. This went unnoticed because on most systems the equivalent number is deduced by calling pathconf(). Apparently under NetBSD this function doesn't work correctly over NFS mounts. 30/05/2002 Version 1.4.1 released. 25/05/2002 mcs@astro.caltech.edu (based on suggestions by Paul Smith) pathutil.c Apparently, under QNX pathconf("/",_PC_PATH_MAX) returns EINVAL. At Paul's suggestion I have modified the code to silently substitute the existing MAX_PATHLEN_FALLBACK value if pathconf() returns an error of any kind. homedir.c Under QNX, sysconf(_SC_GETPW_R_SIZE_MAX) also apparently returns EINVAL, so as with pathconf() I modified the code to substitute a fallback default, rather than complaining and failing. enhance.c Paul told me that the inclusion of sys/termios.h was causing compilation of enhance.c to fail under QNX. This line is a bug. The correct thing to do is include termios.h without a sub-directory prefix, as I was already doing futher up in the file, so I have just removed the errant include line. 07/05/2002 mcs@astro.caltech.edu (async development branch only) getline.c gl_read_character() now caches and reads unprocessed characters from a key-press lookahead buffer. Whenever gl_intepret_char() receives a new character which makes an initially promising key-sequence no longer match the prefix of any binding, it now simply discards the first character from the key-press buffer and resets the buffer pointer so that the next call to gl_read_character() returns the character that followed it, from the buffer. getline.c The part of gl_get_input_line() which preloads, displays and prepares to edit a new input line, has now been moved into a function called gl_present_line(). 12/02/2002 mcs@astro.caltech.edu getline.c configure.in configure Mac OS X doesn't have a term.h or termcap.h, but it does define prototypes for tputs() and setupterm(), so the default prototypes that I was including if no headers where available, upset it. I've removed these prototypes. I also now conditionally include whichever is found of curses.h and ncurses/curses.h for both termcap and terminfo (before I wasn't including curses.h when termcap was selected). 12/02/2002 mcs@astro.caltech.edu Updated version number to 1.4.1, ready for a micro release. 12/02/2002 mcs@astro.caltech.edu html/index.html Added Mac OS X and Cygwin to the list of systems that can compile libtecla. 12/02/2002 mcs@astro.caltech.edu getline.c Under Mac OS X, the tputs() callback function returns void, instead of the int return value used by other systems. This declaration is now used if both __MACH__ and __APPLE__ are defined. Hopefully these are the correct system macros to check. Thanks for Stephan Fiedler for providing information on Mac OS X. 11/02/2002 mcs@astro.caltech.edu configure.in configure getline.c Some systems don't have term.h, and others have it hidden in an ncurses sub-directory of the standard system include directory. If term.h can't be found, simply don't include it. If it is in an ncurses sub-directory, include ncurses/term.h instead of term.h. 04/02/2002 mcs@astro.caltech.edu configure.in configure Makefile.in Makefile.rules Use ranlib on systems that need it (Mac OS X). Also, make all components of the installation directories where needed, instead of assuming that they exist. 04/02/2002 mcs@astro.caltech.edu getline.c When the tab completion binding was unbound from the tab key, hitting the tab key caused gl_get_line() to ring the bell instead of inserting a tab character. This is problematic when using the 'enhance' program with Jython, since tabs are important in Python. I have corrected this. 10/12/2001 Version 1.4.0 released. 10/12/2001 mcs@astro.caltech.edu getline.c If the TIOCGWINSZ ioctl doesn't work, as is the case when running in an emacs shell, leave the size unchanged, rather than returning a fatal error. 07/12/2001 mcs@astro.caltech.edu configure.in configure Now that the configure version of CFLAGS is included in the makefile, I noticed that the optimization flags -g and -O2 had been added. It turns out that if CFLAGS isn't already set, the autoconf AC_PROG_CC macro initializes it with these two optimization flags. Since this would break backwards compatibility in embedded distributions that already use the OPT= makefile argument, and because turning debugging on needlessly bloats the library, I now make sure that CFLAGS is set before calling this macro. 07/12/2001 mcs@astro.caltech.edu enhance.c Use argv[0] in error reports instead of using a hardcoded macro. 07/12/2001 mcs@astro.caltech.edu getline.c The cut buffer wasn't being cleared after being used as a work buffer by gl_load_history(). 06/12/2001 mcs@astro.caltech.edu configure.in configure I removed my now redundant definition of SUN_TPUTS from CFLAGS. I also added "-I/usr/include" to CFLAGS under Solaris to prevent gcc from seeing conflicting versions of system header files in /usr/local/include. 06/12/2001 Markus Gyger (logged here by mcs) Lots of files. Lots of corrections to misspellings and typos in the comments. getline.c Markus reverted a supposed fix that I added a day or two ago. I had incorrectly thought that in Solaris 8, Sun had finally brought their declaration of the callback function of tputs() into line with other systems, but it turned out that gcc was pulling in a GNU version of term.h from /usr/local/include, and this was what confused me. 05/12/2001 mcs@astro.caltech.edu Makefile.in I added @CFLAGS@ to the CFLAGS assignment, so that if CFLAGS is set as an environment variable when configure is run, the corresponding make variable includes its values in the output makefile. 05/12/2001 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man3/gl_get_line.3 man3/gl_last_signal.3 I added a function that programs can use to find out which signal caused gl_get_line() to return EINTR. 05/12/2001 mcs@astro.caltech.edu getline.c When the newline action was triggered by a printable character, it failed to display that character. It now does. Also, extra control codes that I had added, to clear to the end of the display after the carriage return, but before displaying the prompt, were confusing expect scripts, so I have removed them. This step is now done instead in gl_redisplay() after displaying the full input line. 05/12/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 A user convinced me that continuing to invoke meta keybindings for meta characters that are printable is a bad idea, as is allowing users to ask to have setlocale() called behind the application's back. I have thus changed this. The setlocale configuration option has gone, and gl_get_line() is now completely 8-bit clean, by default. This means that if a meta character is printable, it is treated as a literal character, rather than a potential M-c binding. Meta bindings can still be invoked via their Esc-c equivalents, and indeed most terminal emulators either output such escape pairs by default when the meta character is pressed, or can be configured to do so. I have documented how to configure xterm to do this, in the man page. 03/12/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 gl_get_line() by default now prints any 8-bit printable characters that don't match keybindings. Previously characters > 127 were only printed if preceded by the literal-next action. Alternatively, by placing the command literal_if_printable in the tecla configuration file, all printable characters are treated as literal characters, even if they are bound to action functions. For international users of programs written by programmers that weren't aware of the need to call setlocale() to support alternate character sets, the configuration file can now also contain the single-word command "setlocale", which tells gl_get_line() to remedy this. 27/11/2001 mcs@astro.caltech.edu demo.c demo2.c enhance man3/gl_get_line.3 All demos and programs now call setlocale(LC_CTYPE,""). This makes them support character sets of different locales, where specified with the LC_CTYPE, LC_ALL, or LANG environment variables. I also added this to the demo in the man page, and documented its effect. 27/11/2001 mcs@astro.caltech.edu getline.c When displaying unsigned characters with values over 127 literally, previously it was assumed that they would all be displayable. Now isprint() is consulted, and if it says that a character isn't printable, the character code is displayed in octal like \307. In non-C locales, some characters with values > 127 are displayable, and isprint() tells gl_get_line() which are and which aren't. 27/11/2001 mcs@astro.caltech.edu getline.c pathutil.c history.c enhance.c demo2.c All arguments of the ctype.h character class functions are now cast to (int)(unsigned char). Previously they were cast to (int), which doesn't correctly conform to the requirements of the C standard, and could cause problems for characters with values > 127 on systems with signed char's. 26/11/2001 mcs@astro.caltech.edu man3/enhance.3 man3/libtecla.3 I started writing a man page for the enhance program. 26/11/2001 mcs@astro.caltech.edu Makefile.in Makefile.rules INSTALL It is now possible to specify whether the demos and other programs are to be built, by overriding the default values of the DEMOS, PROGRAMS and PROGRAMS_R variables. I have also documented the BINDIR variable and the install_bin makefile target. 22/11/2001 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man3/gl_get_line.3 man3/gl_ignore_signal.3 man3/gl_trap_signal.3 Signal handling has now been modified to be customizable. Signals that are trapped by default can be removed from the list of trapped signals, and signals that aren't currently trapped, can be added to the list. Applications can also specify the signal and terminal environments in which an application's signal handler is invoked, and what gl_get_line() does after the signal handler returns. 13/11/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 Added half-bright, reverse-video and blinking text to the available prompt formatting options. getline.c Removed ^O from the default VT100 sgr0 capability string. Apparently it can cause problems with some terminal emulators, and we don't need it, since it turns off the alternative character set mode, which we don't use. getline.c gl_tigetstr() and gl_tgetstr() didn't guard against the error returns of tigetstr() and tgetstr() respectively. They now do. 11/11/2001 mcs@astro.caltech.edu getline.c libtecla.h libtecla.map man3/gl_get_line.3 man3/gl_prompt_style.3 Although the default remains to display the prompt string literally, the new gl_prompt_style() function can be used to enable text attribute formatting directives in prompt strings, such as underlining, bold font, and highlighting directives. 09/11/2001 mcs@astro.caltech.edu enhance.c Makefile.rules configure.in configure I added a new program to the distribution that allows one to run most third party programs with the tecla library providing command-line editing. 08/11/2001 mcs@astro.caltech.edu libtecla.h getline.c man3/gl_get_line.3 history.c history.h I added a max_lines argument to gl_show_history() and _glh_show_history(). This can optionally be used to set a limit on the number of history lines displayed. libtecla.h getline.c man3/gl_get_line.3 I added a new function called gl_replace_prompt(). This can be used by gl_get_line() callback functions to request that a new prompt be use when they return. 06/11/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 I implemented, bound and documented the list-history action, used for listing historical lines of the current history group. getline.c man3/gl_get_line.3 man3/gl_echo_mode.3 I wrote functions to specify and query whether subsequent lines will be visible as they are being typed. 28/10/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 For those cases where a terminal provides its own high-level terminal editing facilities, you can now specify an edit-mode argument of 'none'. This disables all tecla key bindings, and by using canonical terminal input mode instead of raw input mode, editing is left up to the terminal driver. 21/10/2001 mcs@astro.caltech.edu libtecla.h getline.c history.c history.h man3/gl_get_line.3 man3/gl_history_info.3 I added the new gl_state_of_history(), gl_range_of_history() and gl_size_of_history() functions for querying information about the history list. history.c While testing the new gl_size_of_history() function, I noticed that when the history buffer wrapped, any location nodes of old lines between the most recent line and the end of the buffer weren't being removed. This could result in bogus entries appearing at the start of the history list. Now fixed. 20/10/2001 mcs@astro.caltech.edu libtecla.h getline.c history.c history.h man3/gl_get_line.3 man3/gl_lookup_history.3 I added a function called gl_lookup_history(), that the application can use to lookup lines in the history list. libtecla.h getline.c history.c history.h man3/gl_get_line.3 gl_show_history() now takes a format string argument to control how the line is displayed, and with what information. It also now provides the option of either displaying all history lines or just those of the current history group. getline.c man3/gl_get_line.3 gl_get_line() only archives lines in the history buffer if the newline action was invoked by a newline or carriage return character. 16/10/2001 mcs@astro.caltech.edu history.c history.h getline.c libtecla.h libtecla.map man3/gl_get_line.3 man3/gl_resize_history.3 man3/gl_limit_history.3 man3/gl_clear_history.3 man3/gl_toggle_history.3 I added a number of miscellaneous history configuration functions. You can now resize or delete the history buffer, limit the number of lines that are allowed in the buffer, clear either all history or just the history of the current history group, and temporarily enable and disable the history mechanism. 13/10/2001 mcs@astro.caltech.edu getline.c tputs_fp is now only declared if using termcap or terminfo. getline.c libtecla.map man3/gl_get_line.3 man3/gl_terminal_size.3 I added a public gl_terminal_size() function for updating and querying the current size of the terminal. update_version configure.in libtecla.h A user noted that on systems where the configure script couldn't be used, it was inconvenient to have the version number macros set by the configure script, so they are now specified in libtecla.h. To reduce the likelihood that the various files where the version number now appears might get out of sync, I have written the update_version script, which changes the version number in all of these files to a given value. 01/10/2001 mcs@astro.caltech.edu getline.c history.c history.h man3/gl_get_line.3 I added a max_lines argument to gl_save_history(), to allow people to optionally place a ceiling on the number of history lines saved. Specifying this as -1 sets the ceiling to infinity. 01/10/2001 mcs@astro.caltech.edu configure.in configure Under digital unix, getline wouldn't compile with _POSIX_C_SOURCE set, due to type definitions needed by select being excluded by this flag. Defining the _OSF_SOURCE macro as well on this system, resolved this. 30/09/2001 mcs@astro.caltech.edu getline.c libtecla.h history.c history.h man3/gl_get_line.3 man3/gl_group_history.3 I implemented history streams. History streams effectively allow multiple history lists to be stored in a single history buffer. Lines in the buffer are tagged with the current stream identification number, and lookups only consider lines that are marked with the current stream identifier. getline.c libtecla.h history.c history.h man3/gl_get_line.3 man3/gl_show_history.3 The new gl_show_history function displays the current history to a given stdio output stream. 29/09/2001 mcs@astro.caltech.edu getline.c Previously new_GetLine() installed a persistent signal handler to be sure to catch the SIGWINCH (terminal size change) signal between calls to gl_get_line(). This had the drawback that if multiple GetLine objects were created, only the first GetLine object used after the signal was received, would see the signal and adapt to the new terminal size. Instead of this, a signal handler for sigwinch is only installed while gl_get_line() is running, and just after installing this handler, gl_get_line() checks for terminal size changes that might have occurred while the signal handler wasn't installed. getline.c Dynamically allocated copies of capability strings looked up in the terminfo or termcap databases are now made, so that calls to setupterm() etc for one GetLine object don't get trashed when another GetLine object calls setupterm() etc. It is now safe to allocate and use multiple GetLine objects, albeit only within a single thread. 28/09/2001 mcs@astro.caltech.edu version.c Makefile.rules I added a function for querying the version number of the library. 26/09/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 I added the new gl_watch_fd() function, which allows applications to register callback functions to be invoked when activity is seen on arbitrary file descriptors while gl_get_line() is awaiting keyboard input from the user. keytab.c If a request is received to delete a non-existent binding, which happens to be an ambiguous prefix of other bindings no complaint is now generated about it being ambiguous. 23/09/2001 mcs@astro.caltech.edu getline.c history.c history.h man3/gl_get_line.3 libtecla.map demo.c I added new public functions for saving and restoring the contents of the history list. The demo program now uses these functions to load and save history in ~/.demo_history. 23/09/2001 mcs@astro.caltech.edu getline.c On trying the demo for the first time on a KDE konsole terminal, I discovered that the default M-O binding to repeat history was hiding the arrow keys, which are M-OA etc. I have removed this binding. The M-o (ie the lower case version of this), is still bound. 18/09/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 libtecla.map Automatic reading of ~/.teclarc is now postponed until the first call to gl_get_line(), to give the application the chance to specify alternative configuration sources with the new function gl_configure_getline(). The latter function allows configuration to be done with a string, a specified application-specific file, and/or a specified user-specific file. I also added a read-init-files action function, for re-reading the configuration files, if any. This is by default bound to ^X^R. This is all documented in gl_get_line.3. 08/09/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 It is now possible to bind actions to key-sequences that start with printable characters. Previously keysequences were required to start with meta or control characters. This is documented in gl_get_line.3. getline.c man3/gl_get_line.3 A customized completion function can now arrange for gl_get_line() to return the current input line whenever a successful completion has been made. This is signalled by setting the last character of the optional continuation suffix to a newline character. This is documented in gl_get_line.3. 05/07/2001 Bug reported by Mike MacFaden, fixed by mcs configure.in There was a bug in the configure script that only revealed itself on systems without termcap but not terminfo (eg. NetBSD). I traced the bug back to a lack of sufficient quoting of multi-line m4 macro arguments in configure.in, and have now fixed this and recreated the configure script. 05/07/2001 Bug reported and patched by Mike MacFaden (patch modified by mcs to match original intentions). getline.c getline.c wouldn't compile when termcap was selected as the terminal information database. setupterm() was being passed a non-existent variable, in place of the term[] argument of gl_control_strings(). Also if gl_change_terminal() is called with term==NULL, "ansi" is now substituted. 02/07/2001 Version 1.3.3 released. 27/06/2001 mcs@astro.caltech.edu getline.c expand.c cplmatch.c Added checks to fprintf() statements that write to the terminal. getline.c Move the cursor to the end of the line before suspending, so that the cursor doesn't get left in the middle of the input line. Makefile.in On systems that don't support shared libraries, the distclean target of make deleted libtecla.h. This has now been fixed. getline.c gl_change_terminal() was being called by gl_change_editor(), with the unwanted side effect that raw terminal modes were stored as those to be restored later, if called by an action function. gl_change_terminal() was being called in this case to re-establish terminal-specific key bindings, so I have just split this part of the function out into a separate function for both gl_change_editor() and gl_change_terminal() to call. 12/06/2001 mcs@astro.caltech.edu getline.c Signal handling has been improved. Many more signals are now trapped, and instead of using a simple flag set by a signal handler, race conditions are avoided by blocking signals during most of the gl_get_line() code, and unblocking them via calls to sigsetjmp(), just before attempting to read each new character from the user. The matching use of siglongjmp() in the signal handlers ensures that signals are reblocked correctly before they are handled. In most cases, signals cause gl_get_line() to restore the terminal modes and signal handlers of the calling application, then resend the signal to the application. In the case of SIGINT, SIGHUP, SIGPIPE, and SIGQUIT, if the process still exists after the signals are resent, gl_get_line() immediately returns with appropriate values assigned to errno. If SIGTSTP, SIGTTIN or SIGTTOU signals are received, the process is suspended. If any other signal is received, and the process continues to exist after the signal is resent to the calling application, line input is resumed after the terminal is put back into raw mode, the gl_get_line() signal handling is restored, and the input line redrawn. man/gl_get_line(3) I added a SIGNAL HANDLING section to the gl_get_line() man page, describing the new signal handling features. 21/05/2001 Version 1.3.2 released. 21/05/2001 mcs@astro.caltech.edu getline.c When vi-replace-char was used to replace the character at the end of the line, it left the cursor one character to its right instead of on top of it. Now rememdied. getline.c When undoing, to properly emulate vi, the cursor is now left at the leftmost of the saved and current cursor positions. getline.c man3/gl_get_line.3 Implemented find-parenthesis (%), delete-to-paren (M-d%), vi-change-to-paren (M-c%), copy-to-paren (M-y%). cplfile.c pcache.c In three places I was comparing the last argument of strncmp() to zero instead of the return value of strncmp(). 20/05/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 Implemented and documented the vi-repeat-change action, bound to the period key. This repeats the last action that modified the input line. 19/05/2001 mcs@astro.caltech.edu man3/gl_get_line.3 I documented the new action functions and bindings provided by Tim Eliseo, plus the ring-bell action and the new "nobeep" configuration option. getline.c I modified gl_change_editor() to remove and reinstate the terminal settings as well as the default bindings, since these have editor-specific differences. I also modified it to not abort if a key-sequence can't be bound for some reason. This allows the new vi-mode and emacs-mode bindings to be used safely. getline.c When the line was re-displayed on receipt of a SIGWINCH signal, the result wasn't visible until the next character was typed, since a call to fflush() was needed. gl_redisplay_line() now calls gl_flush_output() to remedy this. 17/05/2001 mcs@astro.catlech.edu getline.c Under Linux, calling fflush(gl->output_fd) hangs if terminal output has been suspended with ^S. With the tecla library taking responsability for reading the stop and start characters this was a problem, because once hung in fflush(), the keyboard input loop wasn't entered, so the user couldn't type the start character to resume output. To remedy this, I now have the terminal process these characters, rather than the library. 12/05/2001 mcs@astro.caltech.edu getline.c The literal-next action is now implemented as a single function which reads the next character itself. Previously it just set a flag which effected the interpretation of the next character read by the input loop. getline.c Added a ring-bell action function. This is currently unbound to any key by default, but it is used internally, and can be used by users that want to disable any of the default key-bindings. 12/05/2001 Tim Eliseo (logged here by mcs) getline.c Don't reset gl->number until after calling an action function. By looking at whether gl->number is <0 or not, action functions can then tell whether the count that they were passed was explicitly specified by the user, as opposed to being defaulted to 1. getline.c In vi, the position at which input mode is entered acts as a barrier to backward motion for the few backward moving actions that are enabled in input mode. Tim added this barrier to getline. getline.c In gl_get_line() after reading an input line, or having the read aborted by a signal, the sig_atomic_t gl_pending_signal was being compared to zero instead of -1 to see if no signals had been received. gl_get_line() will thus have been calling raise(-1), which luckily didn't seem to do anything. Tim also arranged for errno to be set to EINTR when a signal aborts gl_get_line(). getline.c The test in gl_add_char_to_line() for detecting when overwriting a character with a wider character, had a < where it needed a >. Overwriting with a wider character thus overwrote trailing characters. Tim also removed a redundant copy of the character into the line buffer. getline.c gl_cursor_left() and gl->cursor_right() were executing a lot of redundant code, when the existing call to the recently added gl_place_cursor() function, does all that is necessary. getline.c Remove redundant code from backward_kill_line() by re-implimenting in terms of gl_place_cursor() and gl_delete_chars(). getline.c gl_forward_delete_char() now records characters in cut buffer when in vi command mode. getline.c In vi mode gl_backward_delete_char() now only deletes up to the point at which input mode was entered. Also gl_delete_chars() restores from the undo buffer when deleting in vi insert mode. getline.c Added action functions, vi-delete-goto-column, vi-change-to-bol, vi-change-line, emacs-mode, vi-mode, vi-forward-change-find, vi-backward-change-find, vi-forward-change-to, vi-backward-change-to, vi-change-goto-col, forward-delete-find, backward-delete-find, forward-delete-to, backward-delete-to, delete-refind, delete-invert-refind, forward-copy-find, backward-copy-find, forward-copy-to, backward-copy-to copy-goto-column, copy-rest-of-line, copy-to-bol, copy-line, history-re-search-forward, history-re-search-backward. 06/05/2001 Version 1.3.1 released. 03/05/2001 mcs@astro.caltech.edu configure.in Old versions of GNU ld don't accept version scripts. Under Linux I thus added a test to try out ld with the --version-script argument to see if it works. If not, version scripts aren't used. configure.in My test for versions of Solaris earlier than 7 failed when confronted by a three figure version number (2.5.1). Fixed. 30/04/2001 mcs@astro.caltech.edu getline.c In vi mode, history-search-backward and history-search-forward weren't doing anything when invoked at the start of an empty line, whereas they should have acted like up-history and down-history. Makefile.in Makefile.rules When shared libraries are being created, the build procedure now arranges for any alternate library links to be created as well, before linking the demos. Without this the demos always linked to the static libraries (which was perfectly ok, but wasn't a good example). Makefile.in Makefile.rules On systems on which shared libraries were being created, if there were no alternate list of names, make would abort due to a Bourne shell 'for' statement that didn't have any arguments. Currently there are no systems who's shared library configurations would trigger this problem. Makefile.rules The demos now relink to take account of changes to the library. configure.in configure When determining whether the reentrant version of the library should be compiled by default, the configure script now attempts to compile a dummy program that includes all of the appropriate system headers and defines _POSIX_C_SOURCE. This should now be a robust test on systems which use C macros to alias these function names to other internal functions. configure.in Under Solaris 2.6 and earlier, the curses library is in /usr/ccs/lib. Gcc wasn't finding this. In addition to remedying this, I had to remove "-z text" from LINK_SHARED under Solaris to get it to successfully compile the shared library against the static curses library. configure.in Under Linux the -soname directive was being used incorrectly, citing the fully qualified name of the library instead of its major version alias. This will unfortunately mean that binaries linked with the 1.2.3 and 1.2.4 versions of the shared library won't use later versions of the library unless relinked. 30/04/2001 mcs@astro.caltech.edu getline.c In gl_get_input_line(), don't redundantly copy the start_line if start_line == gl->line. 30/04/2001 Version 1.3.0 released. 28/04/2001 mcs@astro.caltech.edu configure.in I removed the --no-undefined directive from the Linux LINK_SHARED command. After recent patches to our RedHat 7.0 systems ld started reporting some internal symbols of libc as being undefined. Using nm on libc indicated that the offending symbols are indeed defined, albeit as "common" symbols, so there appears to be a bug in RedHat's ld. Removing this flag allows the tecla shared library to compile, and programs appear to function fine. man3/gl_get_line.3 The default key-sequence used to invoke the read-from-file action was incorrectly cited as ^Xi instead of ^X^F. 26/04/2001 mcs@astro.caltech.edu getline.c man3/gl_get_line.3 A new vi-style editing mode was added. This involved adding many new action functions, adding support for specifying editing modes in users' ~/.teclarc files, writing a higher level cursor motion function to support the different line-end bounds required in vi command mode, and a few small changes to support the fact that vi has two modes, input mode and command mode with different bindings. When vi editing mode is enabled, any binding that starts with an escape or a meta character, is interpreted as a command-mode binding, and switches the library to vi command mode if not already in that mode. Once in command mode the first character of all keysequences entered until input mode is re-enabled, are quietly coerced to meta characters before being looked up in the key-binding table. So, for example, in the key-binding table, the standard vi command-mode 'w' key, which moves the cursor one word to the right, is represented by M-w. This emulates vi's dual sets of bindings in a natural way without needing large changes to the library, or new binding syntaxes. Since cursor keys normally emit keysequences which start with escape, it also does something sensible when a cursor key is pressed during input mode (unlike true vi, which gets upset). I also added a ^Xg binding for the new list-glob action to both the emacs and vi key-binding tables. This lists the files that match the wild-card expression that precedes it on the command line. The function that reads in ~/.teclarc used to tell new_GetLine() to abort if it encountered anything that it didn't understand in this file. It now just reports an error and continues onto the next line. Makefile.in: When passing LIBS=$(LIBS) to recursive invokations of make, quotes weren't included around the $(LIBS) part. This would cause problems if LIBS ever contained more than one word (with the supplied configure script this doesn't happen currently). I added these quotes. expand.c man3/ef_expand_file.3: I wrote a new public function called ef_list_expansions(), to list the matching filenames returned by ef_expand_file(). I also fixed the example in the man page, which cited exp->file instead of exp->files, and changed the dangerous name 'exp' with 'expn'. keytab.c: Key-binding tables start with 100 elements, and are supposedly incremented in size by 100 elements whenever the a table runs out of space. The realloc arguments to do this were wrong. This would have caused problems if anybody added a lot of personal bindings in their ~/.teclarc file. I only noticed it because the number of key bindings needed by the new vi mode exceeded this number. libtecla.map ef_expand_file() is now reported as having been added in the upcoming 1.3.0 release. 25/03/2001 Markus Gyger (logged here by mcs) Makefile.in: Make symbolic links to alternative shared library names relative instead of absolute. Makefile.rules: The HP-UX libtecla.map.opt file should be made in the compilation directory, to allow the source code directory to be on a readonly filesystem. cplmatch.c demo2.c history.c pcache.c To allow the library to be compiled with a C++ compiler, without generating warnings, a few casts were added where void* return values were being assigned directly to none void* pointer variables. 25/03/2001 mcs@astro.caltech.edu libtecla.map: Added comment header to explain the purpose of the file. Also added cpl_init_FileArgs to the list of exported symbols. This symbol is deprecated, and no longer documented, but for backwards compatibility, it should still be exported. configure: I had forgotten to run autoconf before releasing version 1.2.4, so I have just belatedly done so. This enables Markus' changes to "configure.in" documented previously, (see 17/03/2001). 20/03/2001 John Levon (logged here by mcs) libtecla.h A couple of the function prototypes in libtecla.h have (FILE *) argument declarations, which means that stdio.h needs to be included. The header file should be self contained, so libtecla.h now includes stdio.h. 18/03/2001 Version 1.2.4 released. README html/index.html configure.in Incremented minor version from 3 to 4. 18/03/2001 mcs@astro.caltech.edu getline.c The fix for the end-of-line problem that I released a couple of weeks ago, only worked for the first line, because I was handling this case when the cursor position was equal to the last column, rather than when the cursor position modulo ncolumn was zero. Makefile.in Makefile.rules The demos are now made by default, their rules now being int Makefile.rules instead of Makefile.in. INSTALL I documented how to compile the library in a different directory than the distribution directory. I also documented features designed to facilitate configuring and building the library as part of another package. 17/03/2001 Markus Gyger (logged here by mcs) getline.c Until now cursor motions were done one at a time. Markus has added code to make use the of the terminfo capability that moves the cursor by more than one position at a time. This greatly improves performance when editing near the start of long lines. getline.c To further improve performance, Markus switched from writing one character at a time to the terminal, using the write() system call, to using C buffered output streams. The output buffer is only flushed when necessary. Makefile.rules Makefile.in configure.in Added support for compiling for different architectures in different directories. Simply create another directory and run the configure script located in the original directory. Makefile.in configure.in libtecla.map Under Solaris, Linux and HP-UX, symbols that are to be exported by tecla shared libraries are explicitly specified via symbol map files. Only publicly documented functions are thus visible to applications. configure.in When linking shared libraries under Solaris SPARC, registers that are reserved for applications are marked as off limits to the library, using -xregs=no%appl when compiling with Sun cc, or -mno-app-regs when compiling with gcc. Also removed -z redlocsym for Solaris, which caused problems under some releases of ld. homedir.c (after minor changes by mcs) Under ksh, ~+ expands to the current value of the ksh PWD environment variable, which contains the path of the current working directory, including any symbolic links that were traversed to get there. The special username "+" is now treated equally by tecla, except that it substitutes the return value of getcwd() if PWD either isn't set, or if it points at a different directory than that reported by getcwd(). 08/03/2001 Version 1.2.3 released. 08/03/2001 mcs@astro.caltech.edu getline.c On compiling the library under HP-UX for the first time I encountered and fixed a couple of bugs: 1. On all systems except Solaris, the callback function required by tputs() takes an int argument for the character that is to be printed. Under Solaris it takes a char argument. The callback function was passing this argument, regardless of type, to write(), which wrote the first byte of the argument. This was fine under Solaris and under little-endian systems, because the first byte contained the character to be written, but on big-endian systems, it always wrote the zero byte at the other end of the word. As a result, no control characters were being written to the terminal. 2. While attempting to start a newline after the user hit enter, the library was outputting the control sequence for moving the cursor down, instead of the newline character. On many systems the control sequence for moving the cursor down happends to be a newline character, but under HP-UX it isn't. The result was that no new line was being started under HP-UX. 04/03/2001 mcs@astro.caltech.edu configure.in Makefile.in Makefile.stub configure config.guess config.sub Makefile.rules install-sh PORTING README INSTALL Configuration and compilation of the library is now performed with the help of an autoconf configure script. In addition to relieving the user of the need to edit the Makefile, this also allows automatic compilation of the reentrant version of the library on platforms that can handle it, along with the creation of shared libraries where configured. On systems that aren't known to the configure script, just the static tecla library is compiled. This is currently the case on all systems except Linux, Solaris and HP-UX. In the hope that installers will provide specific conigurations for other systems, the configure.in script is heavily commented, and instructions on how to use are included in a new PORTING file. 24/02/2001 Version 1.2b released. 22/02/2001 mcs@astro.caltech.edu getline.c It turns out that most terminals, but not all, on writing a character in the rightmost column, don't wrap the cursor onto the next line until the next character is output. This library wasn't aware of this and thus if one tried to reposition the cursor from the last column, gl_get_line() thought that it was moving relative to a point on the next line, and thus moved the cursor up a line. The fix was to write one extra character when in the last column to force the cursor onto the next line, then backup the cursor to the start of the new line. getline.c On terminal initialization, the dynamic LINES and COLUMNS environment variables were ignored unless terminfo/termcap didn't return sensible dimensions. In practice, when present they should override the static versions in the terminfo/termcap databases. This is the new behavior. In reality this probably won't have caused many problems, because a SIGWINCH signal which informs of terminal size changes is sent when the terminal is opened, so the dimensions established during initialization quickly get updated on most systems. 18/02/2001 Version 1.2a released. 18/02/2001 mcs@astro.caltech.edu getline.c Three months ago I moved the point at which termios.h was included in getline.c. Unfortunately, I didn't notice that this moved it to after the test for TIOCGWINSZ being defined. This resulted in SIGWINCH signals not being trapped for, and thus terminal size changes went unnoticed. I have now moved the test to after the inclusion of termios.h. 12/02/2001 Markus Gyger (described here by mcs) man3/pca_lookup_file.3 man3/gl_get_line.3 man3/ef_expand_file.3 man3/cpl_complete_word.3 In the 1.2 release of the library, all functions in the library were given man pages. Most of these simply include one of the above 4 man pages, which describe the functions while describing the modules that they are in. Markus added all of these function names to the lists in the "NAME" headers of the respective man pages. Previously only the primary function of each module was named there. 11/02/2001 mcs@astro.caltech.edu getline.c On entering a line that wrapped over two or more terminal, if the user pressed enter when the cursor wasn't on the last of the wrapped lines, the text of the wrapped lines that followed it got mixed up with the next line written by the application, or the next input line. Somehow this slipped through the cracks and wasn't noticed until now. Anyway, it is fixed now. 09/02/2001 Version 1.2 released. 04/02/2001 mcs@astro.caltech.edu pcache.c libtecla.h With all filesystems local, demo2 was very fast to start up, but on a Sun system with one of the target directories being on a remote nfs mounted filesystem, the startup time was many seconds. This was due to the executable selection callback being applied to all files in the path at startup. To avoid this, all files are now included in the cache, and the application specified file-selection callback is only called on files as they are matched. Whether the callback rejected or accepted them is then cached so that the next time an already checked file is looked at, the callback doesn't have to be called. As a result, startup is now fast on all systems, and since usually there are only a few matching file completions at a time, the delay during completion is also usually small. The only exception is if the user tries to complete an empty string, at which point all files have to be checked. Having done this once, however, doing it again is fast. man3/pca_lookup_file.3 I added a man page documenting the new PathCache module. man3/.3 I have added man pages for all of the functions in each of the modules. These 1-line pages use the .so directive to redirect nroff to the man page of the parent module. man Makefile update_html I renamed man to man3 to make it easier to test man page rediction, and updated Makefile and update_html accordingly. I also instructed update_html to ignore 1-line man pages when making html equivalents of the man pages. cplmatch.c In cpl_list_completions() the size_t return value of strlen() was being used as the length argument of a "%*s" printf directive. This ought to be an int, so the return value of strlen() is now cast to int. This would have caused problems on architectures where the size of a size_t is not equal to the size of an int. 02/02/2001 mcs@astro.caltech.edu getline.c Under UNIX, certain terminal bindings are set using the stty command. This, for example, specifies which control key generates a user-interrupt (usually ^C or ^Y). What I hadn't realized was that ASCII NUL is used as the way to specify that one of these bindings is unset. I have now modified the code to skip unset bindings, leaving the corresponding action bound to the built-in default, or a user provided binding. 28/01/2001 mcs@astro.caltech.edu pcache.c libtecla.h A new module was added which supports searching for files in any colon separated list of directories, such as the unix execution PATH environment variable. Files in these directories, after being individually okayed for inclusion via an application provided callback, are cached in a PathCache object. You can then look up the full pathname of a given filename, or you can use the provided completion callback to list possible completions in the path-list. The contents of relative directories, such as ".", obviously can't be cached, so these directories are read on the fly during lookups and completions. The obvious application of this facility is to provide Tab-completion of commands, and thus a callback to place executable files in the cache, is provided. demo2.c This new program demonstrates the new PathCache module. It reads and processes lines of input until the word 'exit' is entered, or C-d is pressed. The default tab-completion callback is replaced with one which at the start of a line, looks up completions of commands in the user's execution path, and when invoked in other parts of the line, reverts to normal filename completion. Whenever a new line is entered, it extracts the first word on the line, looks it up in the user's execution path to see if it corresponds to a known command file, and if so, displays the full pathname of the file, along with the remaining arguments. cplfile.c I added an optional pair of callback function/data members to the new cpl_file_completions() configuration structure. Where provided, this callback is asked on a file-by-file basis, which files should be included in the list of file completions. For example, a callback is provided for listing only completions of executable files. cplmatch.c When listing completions, the length of the type suffix of each completion wasn't being taken into account correctly when computing the column widths. Thus the listing appeared ragged sometimes. This is now fixed. pathutil.c I added a function for prepending a string to a path, and another for testing whether a pathname referred to an executable file. 28/01/2001 mcs@astro.caltech.edu libtecla.h cplmatch.c man/cpl_complete_word.3 The use of a publically defined structure to configure the cpl_file_completions() callback was flawed, so a new approach has been designed, and the old method, albeit still supported, is no longer documented in the man pages. The definition of the CplFileArgs structure in libtecla.h is now accompanied by comments warning people not to modify it, since modifications could break applications linked to shared versions of the tecla library. The new method involves an opaque CplFileConf object, instances of which are returned by a provided constructor function, configured with provided accessor functions, and when no longer needed, deleted with a provided destructor function. This is documented in the cpl_complete_word man page. The cpl_file_completions() callback distinguishes what type of configuration structure it has been sent by virtue of a code placed at the beginning of the CplFileConf argument by its constructor. 04/01/2001 mcs@astro.caltech.edu (Release of version 1.1j) getline.c I added upper-case bindings for the default meta-letter keysequences such as M-b. They thus continue to work when the user has caps-lock on. Makefile I re-implemented the "install" target in terms of new install_lib, install_inc and install_man targets. When distributing the library with other packages, these new targets allows for finer grained control of the installation process. 30/12/2000 mcs@astro.caltech.edu getline.c man/gl_get_line.3 I realized that the recall-history action that I implemented wasn't what Markus had asked me for. What he actually wanted was for down-history to continue going forwards through a previous history recall session if no history recall session had been started while entering the current line. I have thus removed the recall-history action and modified the down-history action function accordingly. 24/12/2000 mcs@astro.caltech.edu getline.c I modified gl_get_line() to allow the previously returned line to be passed in the start_line argument. getline.c man/gl_get_line.3 I added a recall-history action function, bound to M^P. This recalls the last recalled history line, regardless of whether it was from the current or previous line. 13/12/2000 mcs@astro.caltech.edu (Release of version 1.1i) getline.c history.h history.c man/gl_get_line.3 I implemented the equivalent of the ksh Operate action. I have named the tecla equivalent "repeat-history". This causes the line that is to be edited to returned, and arranges for the next most recent history line to be preloaded on the next call to gl_get_line(). Repeated invocations of this action thus result in successive history lines being repeated - hence the name. Implementing the ksh Operate action was suggested by Markus Gyger. In ksh it is bound to ^O, but since ^O is traditionally bound by the default terminal settings, to stop-output, I have bound the tecla equivalent to M-o. 01/12/2000 mcs@astro.caltech.edu (Release of version 1.1h) getline.c keytab.c keytab.h man/gl_get_line.3 I added a digit-argument action, to allow repeat counts for actions to be entered. As in both tcsh and readline, this is bound by default to each of M-0, M-1 through to M-9, the number being appended to the current repeat count. Once one of these has been pressed, the subsequent digits of the repeat count can be typed with or without the meta key pressed. It is also possible to bind digit-argument to other keys, with or without a numeric final keystroke. See man page for details. getline.c man/gl_get_line.3 Markus noted that my choice of M-< for the default binding of read-from-file, could be confusing, since readline binds this to beginning-of-history. I have thus rebound it to ^X^F (ie. like find-file in emacs). getline.c history.c history.h man/gl_get_line.3 I have now implemented equivalents of the readline beginning-of-history and end-of-history actions. These are bound to M-< and M-> respectively. history.c history.h I Moved the definition of the GlHistory type, and its subordinate types from history.h to history.c. There is no good reason for any other module to have access to the innards of this structure. 27/11/2000 mcs@astro.caltech.edu (Release of version 1.1g) getline.c man/gl_get_line.3 I added a "read-from-file" action function and bound it by default to M-<. This causes gl_get_line() to temporarily return input from the file who's name precedes the cursor. 26/11/2000 mcs@astro.caltech.edu getline.c keytab.c keytab.h man/gl_get_line.3 I have reworked some of the keybinding code again. Now, within key binding strings, in addition to the previously existing notation, you can now use M-a to denote meta-a, and C-a to denote control-a. For example, a key binding which triggers when the user presses the meta key, the control key and the letter [ simultaneously, can now be denoted by M-C-[, or M-^[ or \EC-[ or \E^[. I also updated the man page to use M- instead of \E in the list of default bindings, since this looks cleaner. getline.c man/gl_get_line.3 I added a copy-region-as-kill action function and gave it a default binding to M-w. 22/11/2000 mcs@astro.caltech.edu *.c Markus Gyger sent me a copy of a previous version of the library, with const qualifiers added in appropriate places. I have done the same for the latest version. Among other things, this gets rid of the warnings that are generated if one tells the compiler to const qualify literal strings. getline.c getline.h glconf.c I have moved the contents of glconf.c and the declaration of the GetLine structure into getline.c. This is cleaner, since now only functions in getline.c can mess with the innards of GetLine objects. It also clears up some problems with system header inclusion order under Solaris, and also the possibility that this might result in inconsistent system macro definitions, which in turn could cause different declarations of the structure to be seen in different files. hash.c I wrote a wrapper function to go around strcmp(), such that when hash.c is compiled with a C++ compiler, the pointer to the wrapper function is a C++ function pointer. This makes it compatible with comparison function pointer recorded in the hash table. cplmatch.c getline.c libtecla.h Markus noted that the Sun C++ compiler wasn't able to match up the declaration of cpl_complete_word() in libtecla.h, where it is surrounded by a extern "C" {} wrapper, with the definition of this function in cplmatch.c. My suspicion is that the compiler looks not only at the function name, but also at the function arguments to see if two functions match, and that the match_fn() argument, being a fully blown function pointer declaration, got interpetted as that of a C function in one case, and a C++ function in the other, thus preventing a match. To fix this I now define a CplMatchFn typedef in libtecla.h, and use this to declare the match_fn callback. 20/11/2000 (Changes suggested by Markus Gyger to support C++ compilers): expand.c Renamed a variable called "explicit" to "xplicit", to avoid conflicts when compiling with C++ compilers. *.c Added explicit casts when converting from (void *) to other pointer types. This isn't needed in C but it is in C++. getline.c tputs() has a strange declaration under Solaris. I was enabling this declaration when the SPARC feature-test macro was set. Markus changed the test to hinge on the __sun and __SVR4 macros. direader.c glconf.c stringrp.c I had omitted to include string.h in these two files. Markus also suggested some other changes, which are still under discussion. With the just above changes however, the library compiles without complaint using g++. 19/11/2000 mcs@astro.caltech.edu getline.h getline.c keytab.c keytab.h glconf.c man/gl_get_line.3 I added support for backslash escapes (include \e for the keyboard escape key) and literal binary characters to the characters allowed within key sequences of key bindings. getline.h getline.c keytab.c keytab.h glconf.c man/gl_get_line.3 I introduced symbolic names for the arrow keys, and modified the library to use the cursor key sequences reported by terminfo/termcap in addition to the default ANSI ones. Anything bound to the symbolically named arrow keys also gets bound to the default and terminfo/termcap cursor key sequences. Note that under Solaris terminfo/termcap report the properties of hardware X terminals when TERM is xterm instead of the terminal emulator properties, and the cursor keys on these two systems generate different key sequences. This is an example of why extra default sequences are needed. getline.h getline.c keytab.c For some reason I was using \e to represent the escape character. This is supported by gcc, which thus doesn't emit a warning except with the -pedantic flag, but isn't part of standard C. I now use a macro to define escape as \033 in getline.h, and this is now used wherever the escape character is needed. 17/11/2000 mcs@astro.caltech.edu (Release of version 1.1d) getline.c, man/gl_get_line(3), html/gl_get_line.html In tcsh ^D is bound to a function which does different things depending on where the cursor is within the input line. I have implemented its equivalent in the tecla library. When invoked at the end of the line this action function displays possible completions. When invoked on an empty line it causes gl_get_line() to return NULL, thus signalling end of input. When invoked within a line it invokes forward-delete-char, as before. The new action function is called del-char-or-list-or-eof. getline.c, man/gl_get_line(3), html/gl_get_line.html I found that the complete-word and expand-file actions had underscores in their names instead of hyphens. This made them different from all other action functions, so I have changed the underscores to hyphens. homedir.c On SCO UnixWare while getpwuid_r() is available, the associated _SC_GETPW_R_SIZE_MAX macro used by sysconf() to find out how big to make the buffer to pass to this function to cater for any password entry, doesn't exist. I also hadn't catered for the case where sysconf() reports that this limit is indeterminate. I have thus change the code to substitute a default limit of 1024 if either the above macro isn't defined or if sysconf() says that the associated limit is indeterminate. 17/11/2000 mcs@astro.caltech.edu (Release of version 1.1c) getline.c, getline.h, history.c, history.h I have modified the way that the history recall functions operate, to make them better emulate the behavior of tcsh. Previously the history search bindings always searched for the prefix that preceded the cursor, then left the cursor at the same point in the line, so that a following search would search using the same prefix. This isn't how tcsh operates. On finding a matching line, tcsh puts the cursor at the end of the line, but arranges for the followup search to continue with the same prefix, unless the user does any cursor motion or character insertion operations in between, in which case it changes the search prefix to the new set of characters that are before the cursor. There are other complications as well, which I have attempted to emulate. As far as I can tell, the tecla history recall facilities now fully emulate those of tcsh. 16/11/2000 mcs@astro.caltech.edu (Release of version 1.1b) demo.c: One can now quit from the demo by typing exit. keytab.c: The first entry of the table was getting deleted by _kt_clear_bindings() regardless of the source of the binding. This deleted the up-arrow binding. Symptoms noted by gazelle@yin.interaccess.com. getline.h: Depending on which system include files were include before the inclusion of getline.h, SIGWINCH and TIOCGWINSZ might or might not be defined. This resulted in different definitions of the GetLine object in different files, and thus some very strange bugs! I have now added #includes for the necessary system header files in getline.h itself. The symptom was that on creating a ~/.teclarc file, the demo program complained of a NULL argument to kt_set_keybinding() for the first line of the file. 15/11/2000 mcs@astro.caltech.edu (Release of version 1.1a) demo.c: I had neglected to check the return value of new_GetLine() in the demo program. Oops. getline.c libtecla.h: I wrote gl_change_terminal(). This allows one to change to a different terminal or I/O stream, by specifying the stdio streams to use for input and output, along with the type of terminal that they are connected to. getline.c libtecla.h: Renamed GetLine::isterm to GetLine::is_term. Standard C reserves names that start with "is" followed by alphanumeric characters, so this avoids potential clashes in the future. keytab.c keytab.h Each key-sequence can now have different binding functions from different sources, with the user provided binding having the highest precedence, followed by the default binding, followed by any terminal specific binding. This allows gl_change_terminal() to redefine the terminal-specific bindings each time that gl_change_terminal() is called, without overwriting the user specified or default bindings. In the future, it will also allow for reconfiguration of user specified bindings after the call to new_GetLine(). Ie. deleting a user specified binding should reinstate any default or terminal specific binding. man/cpl_complete_word.3 html/cpl_complete_word.html man/ef_expand_file.3 html/ef_expand_file.html man/gl_get_line.3 html/gl_get_line.html I added sections on thread safety to the man pages of the individual modules. man/gl_get_line.3 html/gl_get_line.html I documented the new gl_change_terminal() function. man/gl_get_line.3 html/gl_get_line.html In the description of the ~/.teclarc configuration file, I had omitted the 'bind' command word in the example entry. I have now remedied this. ./libtecla/Makefile0100644000076400007640000000032310142301644012562 0ustar mcsmcsdefault: ./configure $(MAKE) distclean: ./configure --without-man-pages $(MAKE) $@ normal reentrant demos demos_r clean install_lib install_bin install_inc \ install_man install: ./configure $(MAKE) $@ ./libtecla/Makefile.rules0100644000076400007640000001331407573320060013726 0ustar mcsmcsdefault: $(OBJDIR) $(TARGETS) $(DEMOS) $(PROGRAMS) #----------------------------------------------------------------------- # You shouldn't need to change anything in this file. #----------------------------------------------------------------------- # Create the directory in which the object files will be created. $(OBJDIR): mkdir $(OBJDIR) # Construct the compilation command. COMPILE = $(CC) -c $(CFLAGS) -o $@ LIB_OBJECTS = $(OBJDIR)/getline.o $(OBJDIR)/keytab.o $(OBJDIR)/freelist.o \ $(OBJDIR)/strngmem.o $(OBJDIR)/hash.o $(OBJDIR)/history.o \ $(OBJDIR)/direader.o $(OBJDIR)/homedir.o $(OBJDIR)/pathutil.o \ $(OBJDIR)/expand.o $(OBJDIR)/stringrp.o $(OBJDIR)/cplfile.o \ $(OBJDIR)/cplmatch.o $(OBJDIR)/pcache.o $(OBJDIR)/version.o \ $(OBJDIR)/chrqueue.o $(OBJDIR)/ioutil.o $(OBJDIR)/errmsg.o # List the available demonstration programs. DEMO_PROGS = demo$(SUFFIX) demo2$(SUFFIX) demo3$(SUFFIX) # List all of the programs that this makefile can build. PROGS = $(DEMO_PROGS) enhance$(SUFFIX) static: libtecla$(SUFFIX).a libtecla$(SUFFIX).a: $(LIB_OBJECTS) ar -ru $@ $(LIB_OBJECTS); \ $(RANLIB) $@; \ rm -f $(PROGS) shared: libtecla$(SUFFIX)$(SHARED_EXT) libtecla$(SUFFIX)$(SHARED_EXT): $(LIB_OBJECTS) $(srcdir)/libtecla.map \ libtecla.map.opt $(LINK_SHARED) @endings="$(SHARED_ALT)" ; \ for alt in $$endings ; do \ lnk="libtecla$(SUFFIX)$$alt"; \ echo "rm -f $$lnk; $(LN_S) $@ $$lnk"; \ rm -f $$lnk; $(LN_S) $@ $$lnk; \ done; \ rm -f $(PROGS) libtecla.map.opt: $(srcdir)/libtecla.map sed -n 's/^[ ]*\([_a-zA-Z0-9]*\)[ ]*;.*/+e \1/p' $? >$@ demos: $(DEMO_PROGS) demo$(SUFFIX): $(OBJDIR)/demo.o LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \ $(OBJDIR)/demo.o -L. -ltecla$(SUFFIX) $(LIBS) demo2$(SUFFIX): $(OBJDIR)/demo2.o LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \ $(OBJDIR)/demo2.o -L. -ltecla$(SUFFIX) $(LIBS) demo3$(SUFFIX): $(OBJDIR)/demo3.o LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \ $(OBJDIR)/demo3.o -L. -ltecla$(SUFFIX) $(LIBS) enhance$(SUFFIX): $(OBJDIR)/enhance.o LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \ $(OBJDIR)/enhance.o -L. -ltecla$(SUFFIX) $(LIBS) #----------------------------------------------------------------------- # Object file dependencies. #----------------------------------------------------------------------- $(OBJDIR)/getline.o: $(srcdir)/getline.c $(srcdir)/pathutil.h \ $(srcdir)/libtecla.h $(OBJDIR)/keytab.h $(srcdir)/history.h \ $(srcdir)/freelist.h $(srcdir)/stringrp.h $(srcdir)/getline.h \ $(srcdir)/ioutil.h $(srcdir)/chrqueue.h $(srcdir)/cplmatch.h \ $(srcdir)/expand.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/getline.c $(OBJDIR)/keytab.o: $(srcdir)/keytab.c $(OBJDIR)/keytab.h \ $(srcdir)/strngmem.h $(srcdir)/getline.h $(srcdir)/errmsg.h \ $(srcdir)/hash.h $(COMPILE) $(srcdir)/keytab.c $(OBJDIR)/strngmem.o: $(srcdir)/strngmem.c $(srcdir)/strngmem.h \ $(srcdir)/freelist.h $(COMPILE) $(srcdir)/strngmem.c $(OBJDIR)/freelist.o: $(srcdir)/freelist.c $(srcdir)/freelist.h $(COMPILE) $(srcdir)/freelist.c $(OBJDIR)/hash.o: $(srcdir)/hash.c $(srcdir)/hash.h $(srcdir)/strngmem.h \ $(srcdir)/freelist.h $(COMPILE) $(srcdir)/hash.c $(OBJDIR)/history.o: $(srcdir)/history.c $(srcdir)/ioutil.h \ $(srcdir)/history.h $(srcdir)/freelist.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/history.c $(OBJDIR)/expand.o: $(srcdir)/expand.c $(srcdir)/freelist.h \ $(srcdir)/direader.h $(srcdir)/pathutil.h $(srcdir)/homedir.h \ $(srcdir)/stringrp.h $(srcdir)/libtecla.h $(srcdir)/ioutil.h \ $(srcdir)/expand.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/expand.c $(OBJDIR)/direader.o: $(srcdir)/direader.c $(srcdir)/direader.h \ $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/direader.c $(OBJDIR)/homedir.o: $(srcdir)/homedir.c $(srcdir)/pathutil.h \ $(srcdir)/homedir.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/homedir.c $(OBJDIR)/pathutil.o: $(srcdir)/pathutil.c $(srcdir)/pathutil.h $(COMPILE) $(srcdir)/pathutil.c $(OBJDIR)/stringrp.o: $(srcdir)/stringrp.c $(srcdir)/freelist.h \ $(srcdir)/stringrp.h $(COMPILE) $(srcdir)/stringrp.c $(OBJDIR)/cplfile.o: $(srcdir)/cplfile.c $(srcdir)/libtecla.h \ $(srcdir)/direader.h $(srcdir)/homedir.h $(srcdir)/pathutil.h \ $(srcdir)/cplfile.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/cplfile.c $(OBJDIR)/cplmatch.o: $(srcdir)/cplmatch.c $(srcdir)/libtecla.h \ $(srcdir)/ioutil.h $(srcdir)/stringrp.h $(srcdir)/pathutil.h \ $(srcdir)/cplfile.h $(srcdir)/cplmatch.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/cplmatch.c $(OBJDIR)/pcache.o: $(srcdir)/pcache.c $(srcdir)/libtecla.h \ $(srcdir)/pathutil.h $(srcdir)/homedir.h $(srcdir)/freelist.h \ $(srcdir)/direader.h $(srcdir)/stringrp.h $(errmsg.h) $(COMPILE) $(srcdir)/pcache.c $(OBJDIR)/demo.o: $(srcdir)/demo.c $(srcdir)/libtecla.h $(COMPILE) $(srcdir)/demo.c $(OBJDIR)/demo2.o: $(srcdir)/demo2.c $(srcdir)/libtecla.h $(COMPILE) $(srcdir)/demo2.c $(OBJDIR)/demo3.o: $(srcdir)/demo3.c $(srcdir)/libtecla.h $(COMPILE) $(srcdir)/demo3.c $(OBJDIR)/version.o: $(srcdir)/version.c $(srcdir)/libtecla.h $(COMPILE) $(srcdir)/version.c $(OBJDIR)/enhance.o: $(srcdir)/enhance.c $(srcdir)/libtecla.h $(COMPILE) $(srcdir)/enhance.c $(OBJDIR)/chrqueue.o: $(srcdir)/chrqueue.c $(srcdir)/ioutil.h \ $(srcdir)/chrqueue.h $(srcdir)/freelist.h $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/chrqueue.c $(OBJDIR)/ioutil.o: $(srcdir)/ioutil.c $(srcdir)/ioutil.h $(COMPILE) $(srcdir)/ioutil.c $(OBJDIR)/errmsg.o: $(srcdir)/errmsg.c $(srcdir)/errmsg.h $(COMPILE) $(srcdir)/errmsg.c #----------------------------------------------------------------------- # Include file dependencies. #----------------------------------------------------------------------- $(OBJDIR)/keytab.h: $(srcdir)/keytab.h $(srcdir)/libtecla.h cp $(srcdir)/keytab.h $@ ./libtecla/configure0100755000076400007640000044472710142301412013046 0ustar mcsmcs#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.57. # # Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002 # Free Software Foundation, Inc. # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be Bourne compatible if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then set -o posix fi # Support unset when possible. if (FOO=FOO; unset FOO) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # Work around bugs in pre-3.0 UWIN ksh. $as_unset ENV MAIL MAILPATH PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -n "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1; then as_expr=expr else as_expr=false fi if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)$' \| \ . : '\(.\)' 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; } /^X\/\(\/\/\)$/{ s//\1/; q; } /^X\/\(\/\).*/{ s//\1/; q; } s/.*/./; q'` # PATH needs CR, and LINENO needs CR and PATH. # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi as_lineno_1=$LINENO as_lineno_2=$LINENO as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null` test "x$as_lineno_1" != "x$as_lineno_2" && test "x$as_lineno_3" = "x$as_lineno_2" || { # Find who we are. Look in the path if we contain no path at all # relative or not. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2 { (exit 1); exit 1; }; } fi case $CONFIG_SHELL in '') as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for as_base in sh bash ksh sh5; do case $as_dir in /*) if ("$as_dir/$as_base" -c ' as_lineno_1=$LINENO as_lineno_2=$LINENO as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null` test "x$as_lineno_1" != "x$as_lineno_2" && test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; } $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; } CONFIG_SHELL=$as_dir/$as_base export CONFIG_SHELL exec "$CONFIG_SHELL" "$0" ${1+"$@"} fi;; esac done done ;; esac # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line before each line; the second 'sed' does the real # work. The second script uses 'N' to pair each line-number line # with the numbered line, and appends trailing '-' during # substitution so that $LINENO is not a special case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-) sed '=' <$as_myself | sed ' N s,$,-, : loop s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3, t loop s,-$,, s,^['$as_cr_digits']*\n,, ' >$as_me.lineno && chmod +x $as_me.lineno || { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensible to this). . ./$as_me.lineno # Exit status is that of the last command. exit } case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in *c*,-n*) ECHO_N= ECHO_C=' ' ECHO_T=' ' ;; *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; *) ECHO_N= ECHO_C='\c' ECHO_T= ;; esac if expr a : '\(a\)' >/dev/null 2>&1; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then # We could just check for DJGPP; but this test a) works b) is more generic # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04). if test -f conf$$.exe; then # Don't use ln at all; we don't have any links as_ln_s='cp -p' else as_ln_s='ln -s' fi elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.file if mkdir -p . 2>/dev/null; then as_mkdir_p=: else as_mkdir_p=false fi as_executable_p="test -f" # Sed expression to map a string onto a valid CPP name. as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" # Sed expression to map a string onto a valid variable name. as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g" # IFS # We need space, tab and new line, in precisely that order. as_nl=' ' IFS=" $as_nl" # CDPATH. $as_unset CDPATH # Name of the host. # hostname on some systems (SVR3.2, Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` exec 6>&1 # # Initializations. # ac_default_prefix=/usr/local ac_config_libobj_dir=. cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= SHELL=${CONFIG_SHELL-/bin/sh} # Maximum number of lines to put in a shell here document. # This variable seems obsolete. It should probably be removed, and # only ac_max_sed_lines should be used. : ${ac_max_here_lines=38} # Identity of this package. PACKAGE_NAME= PACKAGE_TARNAME= PACKAGE_VERSION= PACKAGE_STRING= PACKAGE_BUGREPORT= ac_unique_file="getline.c" # Factoring default headers for most tests. ac_includes_default="\ #include #if HAVE_SYS_TYPES_H # include #endif #if HAVE_SYS_STAT_H # include #endif #if STDC_HEADERS # include # include #else # if HAVE_STDLIB_H # include # endif #endif #if HAVE_STRING_H # if !STDC_HEADERS && HAVE_MEMORY_H # include # endif # include #endif #if HAVE_STRINGS_H # include #endif #if HAVE_INTTYPES_H # include #else # if HAVE_STDINT_H # include # endif #endif #if HAVE_UNISTD_H # include #endif" ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS MAJOR_VER MINOR_VER MICRO_VER CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT SET_MAKE LN_S AWK RANLIB ac_ct_RANLIB LD ac_ct_LD build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os CPP EGREP TARGETS SHARED_EXT SHARED_ALT SHARED_CFLAGS LINK_SHARED DEFS_R LIBR_MANDIR LIBR_MANEXT FUNC_MANDIR FUNC_MANEXT PROG_MANDIR PROG_MANEXT MISC_MANDIR MISC_MANEXT FILE_MANDIR FILE_MANEXT TARGET_LIBS MAKE_MAN_PAGES LIBOBJS LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. ac_init_help= ac_init_version=false # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datadir='${prefix}/share' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' libdir='${exec_prefix}/lib' includedir='${prefix}/include' oldincludedir='/usr/include' infodir='${prefix}/info' mandir='${prefix}/man' ac_prev= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval "$ac_prev=\$ac_option" ac_prev= continue fi ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'` # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_option in -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad | --data | --dat | --da) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ | --da=*) datadir=$ac_optarg ;; -disable-* | --disable-*) ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/-/_/g'` eval "enable_$ac_feature=no" ;; -enable-* | --enable-*) ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/-/_/g'` case $ac_option in *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;; *) ac_optarg=yes ;; esac eval "enable_$ac_feature='$ac_optarg'" ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst \ | --locals | --local | --loca | --loc | --lo) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* \ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package| sed 's/-/_/g'` case $ac_option in *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;; *) ac_optarg=yes ;; esac eval "with_$ac_package='$ac_optarg'" ;; -without-* | --without-*) ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package | sed 's/-/_/g'` eval "with_$ac_package=no" ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) { echo "$as_me: error: unrecognized option: $ac_option Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; } ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid variable name: $ac_envvar" >&2 { (exit 1); exit 1; }; } ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` eval "$ac_envvar='$ac_optarg'" export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` { echo "$as_me: error: missing argument to $ac_option" >&2 { (exit 1); exit 1; }; } fi # Be sure to have absolute paths. for ac_var in exec_prefix prefix do eval ac_val=$`echo $ac_var` case $ac_val in [\\/$]* | ?:[\\/]* | NONE | '' ) ;; *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 { (exit 1); exit 1; }; };; esac done # Be sure to have absolute paths. for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \ localstatedir libdir includedir oldincludedir infodir mandir do eval ac_val=$`echo $ac_var` case $ac_val in [\\/$]* | ?:[\\/]* ) ;; *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 { (exit 1); exit 1; }; };; esac done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used." >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then its parent. ac_confdir=`(dirname "$0") 2>/dev/null || $as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$0" : 'X\(//\)[^/]' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| \ . : '\(.\)' 2>/dev/null || echo X"$0" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; } /^X\(\/\/\)[^/].*/{ s//\1/; q; } /^X\(\/\/\)$/{ s//\1/; q; } /^X\(\/\).*/{ s//\1/; q; } s/.*/./; q'` srcdir=$ac_confdir if test ! -r $srcdir/$ac_unique_file; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r $srcdir/$ac_unique_file; then if test "$ac_srcdir_defaulted" = yes; then { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2 { (exit 1); exit 1; }; } else { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2 { (exit 1); exit 1; }; } fi fi (cd $srcdir && test -r ./$ac_unique_file) 2>/dev/null || { echo "$as_me: error: sources are in $srcdir, but \`cd $srcdir' does not work" >&2 { (exit 1); exit 1; }; } srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'` ac_env_build_alias_set=${build_alias+set} ac_env_build_alias_value=$build_alias ac_cv_env_build_alias_set=${build_alias+set} ac_cv_env_build_alias_value=$build_alias ac_env_host_alias_set=${host_alias+set} ac_env_host_alias_value=$host_alias ac_cv_env_host_alias_set=${host_alias+set} ac_cv_env_host_alias_value=$host_alias ac_env_target_alias_set=${target_alias+set} ac_env_target_alias_value=$target_alias ac_cv_env_target_alias_set=${target_alias+set} ac_cv_env_target_alias_value=$target_alias ac_env_CC_set=${CC+set} ac_env_CC_value=$CC ac_cv_env_CC_set=${CC+set} ac_cv_env_CC_value=$CC ac_env_CFLAGS_set=${CFLAGS+set} ac_env_CFLAGS_value=$CFLAGS ac_cv_env_CFLAGS_set=${CFLAGS+set} ac_cv_env_CFLAGS_value=$CFLAGS ac_env_LDFLAGS_set=${LDFLAGS+set} ac_env_LDFLAGS_value=$LDFLAGS ac_cv_env_LDFLAGS_set=${LDFLAGS+set} ac_cv_env_LDFLAGS_value=$LDFLAGS ac_env_CPPFLAGS_set=${CPPFLAGS+set} ac_env_CPPFLAGS_value=$CPPFLAGS ac_cv_env_CPPFLAGS_set=${CPPFLAGS+set} ac_cv_env_CPPFLAGS_value=$CPPFLAGS ac_env_CPP_set=${CPP+set} ac_env_CPP_value=$CPP ac_cv_env_CPP_set=${CPP+set} ac_cv_env_CPP_value=$CPP # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] _ACEOF cat <<_ACEOF Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --datadir=DIR read-only architecture-independent data [PREFIX/share] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --infodir=DIR info documentation [PREFIX/info] --mandir=DIR man documentation [PREFIX/man] _ACEOF cat <<\_ACEOF System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] --target=TARGET configure for building compilers for TARGET [HOST] _ACEOF fi if test -n "$ac_init_help"; then cat <<\_ACEOF Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-file-actions Should users of gl_get_line() have access to the filesystem (default=yes) --with-file-system Does the target have a filesystem (default=yes) --with-man-pages Are man pages desired (default=yes) Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory CPPFLAGS C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. _ACEOF fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. ac_popdir=`pwd` for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d $ac_dir || continue ac_builddir=. if test "$ac_dir" != .; then ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A "../" for each directory in $ac_dir_suffix. ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'` else ac_dir_suffix= ac_top_builddir= fi case $srcdir in .) # No --srcdir option. We are building in place. ac_srcdir=. if test -z "$ac_top_builddir"; then ac_top_srcdir=. else ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'` fi ;; [\\/]* | ?:[\\/]* ) # Absolute path. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ;; *) # Relative path. ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_builddir$srcdir ;; esac # Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be # absolute. ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd` ac_abs_top_builddir=`cd "$ac_dir" && cd ${ac_top_builddir}. && pwd` ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd` ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd` cd $ac_dir # Check for guested configure; otherwise get Cygnus style configure. if test -f $ac_srcdir/configure.gnu; then echo $SHELL $ac_srcdir/configure.gnu --help=recursive elif test -f $ac_srcdir/configure; then echo $SHELL $ac_srcdir/configure --help=recursive elif test -f $ac_srcdir/configure.ac || test -f $ac_srcdir/configure.in; then echo $ac_configure --help else echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi cd $ac_popdir done fi test -n "$ac_init_help" && exit 0 if $ac_init_version; then cat <<\_ACEOF Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit 0 fi exec 5>config.log cat >&5 <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was generated by GNU Autoconf 2.57. Invocation command line was $ $0 $@ _ACEOF { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` hostinfo = `(hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. echo "PATH: $as_dir" done } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_sep= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*) ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;; 2) ac_configure_args1="$ac_configure_args1 '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'" # Get rid of the leading space. ac_sep=" " ;; esac done done $as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; } $as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; } # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Be sure not to use single quotes in there, as some shells, # such as our DU 5.0 friend, will then `close' the trap. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo cat <<\_ASBOX ## ---------------- ## ## Cache variables. ## ## ---------------- ## _ASBOX echo # The following way of writing the cache mishandles newlines in values, { (set) 2>&1 | case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in *ac_space=\ *) sed -n \ "s/'"'"'/'"'"'\\\\'"'"''"'"'/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p" ;; *) sed -n \ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p" ;; esac; } echo cat <<\_ASBOX ## ----------------- ## ## Output variables. ## ## ----------------- ## _ASBOX echo for ac_var in $ac_subst_vars do eval ac_val=$`echo $ac_var` echo "$ac_var='"'"'$ac_val'"'"'" done | sort echo if test -n "$ac_subst_files"; then cat <<\_ASBOX ## ------------- ## ## Output files. ## ## ------------- ## _ASBOX echo for ac_var in $ac_subst_files do eval ac_val=$`echo $ac_var` echo "$ac_var='"'"'$ac_val'"'"'" done | sort echo fi if test -s confdefs.h; then cat <<\_ASBOX ## ----------- ## ## confdefs.h. ## ## ----------- ## _ASBOX echo sed "/^$/d" confdefs.h | sort echo fi test "$ac_signal" != 0 && echo "$as_me: caught signal $ac_signal" echo "$as_me: exit $exit_status" } >&5 rm -f core core.* *.core && rm -rf conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -rf conftest* confdefs.h # AIX cpp loses on an empty file, so make sure it contains at least a newline. echo >confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer explicitly selected file to automatically selected ones. if test -z "$CONFIG_SITE"; then if test "x$prefix" != xNONE; then CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" else CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi fi for ac_site_file in $CONFIG_SITE; do if test -r "$ac_site_file"; then { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5 echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special # files actually), so we avoid doing that. if test -f "$cache_file"; then { echo "$as_me:$LINENO: loading cache $cache_file" >&5 echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . $cache_file;; *) . ./$cache_file;; esac fi else { echo "$as_me:$LINENO: creating cache $cache_file" >&5 echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in `(set) 2>&1 | sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val="\$ac_cv_env_${ac_var}_value" eval ac_new_val="\$ac_env_${ac_var}_value" case $ac_old_set,$ac_new_set in set,) { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5 echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5 echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} { echo "$as_me:$LINENO: former value: $ac_old_val" >&5 echo "$as_me: former value: $ac_old_val" >&2;} { echo "$as_me:$LINENO: current value: $ac_new_val" >&5 echo "$as_me: current value: $ac_new_val" >&2;} ac_cache_corrupted=: fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*) ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) ac_configure_args="$ac_configure_args '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5 echo "$as_me: error: changes in the environment can compromise the build" >&2;} { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5 echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;} { (exit 1); exit 1; }; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu MAJOR_VER="1" MINOR_VER="6" MICRO_VER="1" CFLAGS="$CFLAGS" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then echo "$as_me:$LINENO: result: $CC" >&5 echo "${ECHO_T}$CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_ac_ct_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then echo "$as_me:$LINENO: result: $ac_ct_CC" >&5 echo "${ECHO_T}$ac_ct_CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi CC=$ac_ct_CC else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then echo "$as_me:$LINENO: result: $CC" >&5 echo "${ECHO_T}$CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_ac_ct_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="cc" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then echo "$as_me:$LINENO: result: $ac_ct_CC" >&5 echo "${ECHO_T}$ac_ct_CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi CC=$ac_ct_CC else CC="$ac_cv_prog_CC" fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then echo "$as_me:$LINENO: result: $CC" >&5 echo "${ECHO_T}$CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then echo "$as_me:$LINENO: result: $CC" >&5 echo "${ECHO_T}$CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_ac_ct_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then echo "$as_me:$LINENO: result: $ac_ct_CC" >&5 echo "${ECHO_T}$ac_ct_CC" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi test -n "$ac_ct_CC" && break done CC=$ac_ct_CC fi fi test -z "$CC" && { { echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH See \`config.log' for more details." >&5 echo "$as_me: error: no acceptable C compiler found in \$PATH See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } # Provide some information about the compiler. echo "$as_me:$LINENO:" \ "checking for C compiler version" >&5 ac_compiler=`set X $ac_compile; echo $2` { (eval echo "$as_me:$LINENO: \"$ac_compiler --version &5\"") >&5 (eval $ac_compiler --version &5) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (eval echo "$as_me:$LINENO: \"$ac_compiler -v &5\"") >&5 (eval $ac_compiler -v &5) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (eval echo "$as_me:$LINENO: \"$ac_compiler -V &5\"") >&5 (eval $ac_compiler -V &5) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. echo "$as_me:$LINENO: checking for C compiler default output" >&5 echo $ECHO_N "checking for C compiler default output... $ECHO_C" >&6 ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` if { (eval echo "$as_me:$LINENO: \"$ac_link_default\"") >&5 (eval $ac_link_default) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # Find the output, starting from the most likely. This scheme is # not robust to junk in `.', hence go to wildcards (a.*) only as a last # resort. # Be careful to initialize this variable, since it used to be cached. # Otherwise an old cache value of `no' led to `EXEEXT = no' in a Makefile. ac_cv_exeext= # b.out is created by i960 compilers. for ac_file in a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj ) ;; conftest.$ac_ext ) # This is the source file. ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` # FIXME: I believe we export ac_cv_exeext for Libtool, # but it would be cool to find out if it's true. Does anybody # maintain Libtool? --akim. export ac_cv_exeext break;; * ) break;; esac done else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: C compiler cannot create executables See \`config.log' for more details." >&5 echo "$as_me: error: C compiler cannot create executables See \`config.log' for more details." >&2;} { (exit 77); exit 77; }; } fi ac_exeext=$ac_cv_exeext echo "$as_me:$LINENO: result: $ac_file" >&5 echo "${ECHO_T}$ac_file" >&6 # Check the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. echo "$as_me:$LINENO: checking whether the C compiler works" >&5 echo $ECHO_N "checking whether the C compiler works... $ECHO_C" >&6 # FIXME: These cross compiler hacks should be removed for Autoconf 3.0 # If not cross compiling, check that we can run a simple program. if test "$cross_compiling" != yes; then if { ac_try='./$ac_file' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { echo "$as_me:$LINENO: error: cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&5 echo "$as_me: error: cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi fi fi echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6 rm -f a.out a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save # Check the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. echo "$as_me:$LINENO: checking whether we are cross compiling" >&5 echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6 echo "$as_me:$LINENO: result: $cross_compiling" >&5 echo "${ECHO_T}$cross_compiling" >&6 echo "$as_me:$LINENO: checking for suffix of executables" >&5 echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6 if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` export ac_cv_exeext break;; * ) break;; esac done else { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest$ac_cv_exeext echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5 echo "${ECHO_T}$ac_cv_exeext" >&6 rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT echo "$as_me:$LINENO: checking for suffix of object files" >&5 echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6 if test "${ac_cv_objext+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then for ac_file in `(ls conftest.o conftest.obj; ls conftest.*) 2>/dev/null`; do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: $ac_cv_objext" >&5 echo "${ECHO_T}$ac_cv_objext" >&6 OBJEXT=$ac_cv_objext ac_objext=$OBJEXT echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5 echo $ECHO_N "checking whether we are using the GNU C compiler... $ECHO_C" >&6 if test "${ac_cv_c_compiler_gnu+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_compiler_gnu=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_compiler_gnu=no fi rm -f conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5 echo "${ECHO_T}$ac_cv_c_compiler_gnu" >&6 GCC=`test $ac_compiler_gnu = yes && echo yes` ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS CFLAGS="-g" echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5 echo $ECHO_N "checking whether $CC accepts -g... $ECHO_C" >&6 if test "${ac_cv_prog_cc_g+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_prog_cc_g=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_prog_cc_g=no fi rm -f conftest.$ac_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5 echo "${ECHO_T}$ac_cv_prog_cc_g" >&6 if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi echo "$as_me:$LINENO: checking for $CC option to accept ANSI C" >&5 echo $ECHO_N "checking for $CC option to accept ANSI C... $ECHO_C" >&6 if test "${ac_cv_prog_cc_stdc+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_prog_cc_stdc=no ac_save_CC=$CC cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF # Don't try gcc -ansi; that turns off useful extensions and # breaks some systems' header files. # AIX -qlanglvl=ansi # Ultrix and OSF/1 -std1 # HP-UX 10.20 and later -Ae # HP-UX older versions -Aa -D_HPUX_SOURCE # SVR4 -Xc -D__EXTENSIONS__ for ac_arg in "" -qlanglvl=ansi -std1 -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_prog_cc_stdc=$ac_arg break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f conftest.$ac_objext done rm -f conftest.$ac_ext conftest.$ac_objext CC=$ac_save_CC fi case "x$ac_cv_prog_cc_stdc" in x|xno) echo "$as_me:$LINENO: result: none needed" >&5 echo "${ECHO_T}none needed" >&6 ;; *) echo "$as_me:$LINENO: result: $ac_cv_prog_cc_stdc" >&5 echo "${ECHO_T}$ac_cv_prog_cc_stdc" >&6 CC="$CC $ac_cv_prog_cc_stdc" ;; esac # Some people use a C++ compiler to compile C. Since we use `exit', # in C++ we need to declare it. In case someone uses the same compiler # for both compiling C and C++ we need to have the C++ compiler decide # the declaration of exit, since it's the most demanding environment. cat >conftest.$ac_ext <<_ACEOF #ifndef __cplusplus choke me #endif _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then for ac_declaration in \ ''\ '#include ' \ 'extern "C" void std::exit (int) throw (); using std::exit;' \ 'extern "C" void std::exit (int); using std::exit;' \ 'extern "C" void exit (int) throw ();' \ 'extern "C" void exit (int);' \ 'void exit (int);' do cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include $ac_declaration int main () { exit (42); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 continue fi rm -f conftest.$ac_objext conftest.$ac_ext cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_declaration int main () { exit (42); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f conftest.$ac_objext conftest.$ac_ext done rm -f conftest* if test -n "$ac_declaration"; then echo '#ifdef __cplusplus' >>confdefs.h echo $ac_declaration >>confdefs.h echo '#endif' >>confdefs.h fi else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f conftest.$ac_objext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu echo "$as_me:$LINENO: checking whether ${MAKE-make} sets \$(MAKE)" >&5 echo $ECHO_N "checking whether ${MAKE-make} sets \$(MAKE)... $ECHO_C" >&6 set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y,./+-,__p_,'` if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.make <<\_ACEOF all: @echo 'ac_maketemp="$(MAKE)"' _ACEOF # GNU make sometimes prints "make[1]: Entering...", which would confuse us. eval `${MAKE-make} -f conftest.make 2>/dev/null | grep temp=` if test -n "$ac_maketemp"; then eval ac_cv_prog_make_${ac_make}_set=yes else eval ac_cv_prog_make_${ac_make}_set=no fi rm -f conftest.make fi if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6 SET_MAKE= else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 SET_MAKE="MAKE=${MAKE-make}" fi echo "$as_me:$LINENO: checking whether ln -s works" >&5 echo $ECHO_N "checking whether ln -s works... $ECHO_C" >&6 LN_S=$as_ln_s if test "$LN_S" = "ln -s"; then echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6 else echo "$as_me:$LINENO: result: no, using $LN_S" >&5 echo "${ECHO_T}no, using $LN_S" >&6 fi for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_AWK+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then echo "$as_me:$LINENO: result: $AWK" >&5 echo "${ECHO_T}$AWK" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi test -n "$AWK" && break done if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_RANLIB+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then echo "$as_me:$LINENO: result: $RANLIB" >&5 echo "${ECHO_T}$RANLIB" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_ac_ct_RANLIB+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then echo "$as_me:$LINENO: result: $ac_ct_RANLIB" >&5 echo "${ECHO_T}$ac_ct_RANLIB" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi RANLIB=$ac_ct_RANLIB else RANLIB="$ac_cv_prog_RANLIB" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ld", so it can be a program name with args. set dummy ${ac_tool_prefix}ld; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_LD+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$LD"; then ac_cv_prog_LD="$LD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_LD="${ac_tool_prefix}ld" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi LD=$ac_cv_prog_LD if test -n "$LD"; then echo "$as_me:$LINENO: result: $LD" >&5 echo "${ECHO_T}$LD" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi fi if test -z "$ac_cv_prog_LD"; then ac_ct_LD=$LD # Extract the first word of "ld", so it can be a program name with args. set dummy ld; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 if test "${ac_cv_prog_ac_ct_LD+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_LD"; then ac_cv_prog_ac_ct_LD="$ac_ct_LD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_LD="ld" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done fi fi ac_ct_LD=$ac_cv_prog_ac_ct_LD if test -n "$ac_ct_LD"; then echo "$as_me:$LINENO: result: $ac_ct_LD" >&5 echo "${ECHO_T}$ac_ct_LD" >&6 else echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6 fi LD=$ac_ct_LD else LD="$ac_cv_prog_LD" fi ac_aux_dir= for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do if test -f $ac_dir/install-sh; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f $ac_dir/install.sh; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f $ac_dir/shtool; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&5 echo "$as_me: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&2;} { (exit 1); exit 1; }; } fi ac_config_guess="$SHELL $ac_aux_dir/config.guess" ac_config_sub="$SHELL $ac_aux_dir/config.sub" ac_configure="$SHELL $ac_aux_dir/configure" # This should be Cygnus configure. # Make sure we can run config.sub. $ac_config_sub sun4 >/dev/null 2>&1 || { { echo "$as_me:$LINENO: error: cannot run $ac_config_sub" >&5 echo "$as_me: error: cannot run $ac_config_sub" >&2;} { (exit 1); exit 1; }; } echo "$as_me:$LINENO: checking build system type" >&5 echo $ECHO_N "checking build system type... $ECHO_C" >&6 if test "${ac_cv_build+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_build_alias=$build_alias test -z "$ac_cv_build_alias" && ac_cv_build_alias=`$ac_config_guess` test -z "$ac_cv_build_alias" && { { echo "$as_me:$LINENO: error: cannot guess build type; you must specify one" >&5 echo "$as_me: error: cannot guess build type; you must specify one" >&2;} { (exit 1); exit 1; }; } ac_cv_build=`$ac_config_sub $ac_cv_build_alias` || { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_build_alias failed" >&5 echo "$as_me: error: $ac_config_sub $ac_cv_build_alias failed" >&2;} { (exit 1); exit 1; }; } fi echo "$as_me:$LINENO: result: $ac_cv_build" >&5 echo "${ECHO_T}$ac_cv_build" >&6 build=$ac_cv_build build_cpu=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` build_vendor=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` build_os=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` echo "$as_me:$LINENO: checking host system type" >&5 echo $ECHO_N "checking host system type... $ECHO_C" >&6 if test "${ac_cv_host+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_host_alias=$host_alias test -z "$ac_cv_host_alias" && ac_cv_host_alias=$ac_cv_build_alias ac_cv_host=`$ac_config_sub $ac_cv_host_alias` || { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_host_alias failed" >&5 echo "$as_me: error: $ac_config_sub $ac_cv_host_alias failed" >&2;} { (exit 1); exit 1; }; } fi echo "$as_me:$LINENO: result: $ac_cv_host" >&5 echo "${ECHO_T}$ac_cv_host" >&6 host=$ac_cv_host host_cpu=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` echo "$as_me:$LINENO: checking target system type" >&5 echo $ECHO_N "checking target system type... $ECHO_C" >&6 if test "${ac_cv_target+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_target_alias=$target_alias test "x$ac_cv_target_alias" = "x" && ac_cv_target_alias=$ac_cv_host_alias ac_cv_target=`$ac_config_sub $ac_cv_target_alias` || { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_target_alias failed" >&5 echo "$as_me: error: $ac_config_sub $ac_cv_target_alias failed" >&2;} { (exit 1); exit 1; }; } fi echo "$as_me:$LINENO: result: $ac_cv_target" >&5 echo "${ECHO_T}$ac_cv_target" >&6 target=$ac_cv_target target_cpu=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` target_vendor=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` target_os=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # The aliases save the names the user supplied, while $host etc. # will get canonicalized. test -n "$target_alias" && test "$program_prefix$program_suffix$program_transform_name" = \ NONENONEs,x,x, && program_prefix=${target_alias}- case $target_os in solaris2.[0-6]|solaris2.[0-6].*) LIBS="$LIBS -L/usr/ccs/lib" ;; esac if test "$GCC"_ = "yes"_; then touch foo.c fix=`$CC -E -Wp,-v foo.c 2>&1 | $AWK ' /^#include <...> search starts here:/ {in_list=1;ndir=0} / *\// && in_list {path[ndir++] = $1} /^End of search list/ {in_list=0} END { if(path[0] ~ /\/usr\/local\/include/) { for(dir=1; dir&5 echo $ECHO_N "checking how to run the C preprocessor... $ECHO_C" >&6 # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if test "${ac_cv_prog_CPP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # Double quotes because CPP needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether non-existent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi echo "$as_me:$LINENO: result: $CPP" >&5 echo "${ECHO_T}$CPP" >&6 ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether non-existent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { echo "$as_me:$LINENO: error: C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details." >&5 echo "$as_me: error: C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu echo "$as_me:$LINENO: checking for egrep" >&5 echo $ECHO_N "checking for egrep... $ECHO_C" >&6 if test "${ac_cv_prog_egrep+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if echo a | (grep -E '(a|b)') >/dev/null 2>&1 then ac_cv_prog_egrep='grep -E' else ac_cv_prog_egrep='egrep' fi fi echo "$as_me:$LINENO: result: $ac_cv_prog_egrep" >&5 echo "${ECHO_T}$ac_cv_prog_egrep" >&6 EGREP=$ac_cv_prog_egrep echo "$as_me:$LINENO: checking for ANSI C header files" >&5 echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6 if test "${ac_cv_header_stdc+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_header_stdc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_header_stdc=no fi rm -f conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); exit (0); } _ACEOF rm -f conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then : else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_header_stdc=no fi rm -f core core.* *.core gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi fi echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 echo "${ECHO_T}$ac_cv_header_stdc" >&6 if test $ac_cv_header_stdc = yes; then cat >>confdefs.h <<\_ACEOF #define STDC_HEADERS 1 _ACEOF fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f conftest.$ac_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done echo "$as_me:$LINENO: checking for tigetstr in -lcurses" >&5 echo $ECHO_N "checking for tigetstr in -lcurses... $ECHO_C" >&6 if test "${ac_cv_lib_curses_tigetstr+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcurses $LIBS" cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any gcc2 internal prototype to avoid an error. */ #ifdef __cplusplus extern "C" #endif /* We use char because int might match the return type of a gcc2 builtin and then its argument prototype would still apply. */ char tigetstr (); int main () { tigetstr (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_lib_curses_tigetstr=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_lib_curses_tigetstr=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi echo "$as_me:$LINENO: result: $ac_cv_lib_curses_tigetstr" >&5 echo "${ECHO_T}$ac_cv_lib_curses_tigetstr" >&6 if test $ac_cv_lib_curses_tigetstr = yes; then cat >>confdefs.h <<\_ACEOF #define USE_TERMINFO 1 _ACEOF LIBS="$LIBS -lcurses" else echo "$as_me:$LINENO: checking for tigetstr in -lncurses" >&5 echo $ECHO_N "checking for tigetstr in -lncurses... $ECHO_C" >&6 if test "${ac_cv_lib_ncurses_tigetstr+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lncurses $LIBS" cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any gcc2 internal prototype to avoid an error. */ #ifdef __cplusplus extern "C" #endif /* We use char because int might match the return type of a gcc2 builtin and then its argument prototype would still apply. */ char tigetstr (); int main () { tigetstr (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_lib_ncurses_tigetstr=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_lib_ncurses_tigetstr=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi echo "$as_me:$LINENO: result: $ac_cv_lib_ncurses_tigetstr" >&5 echo "${ECHO_T}$ac_cv_lib_ncurses_tigetstr" >&6 if test $ac_cv_lib_ncurses_tigetstr = yes; then cat >>confdefs.h <<\_ACEOF #define USE_TERMINFO 1 _ACEOF LIBS="$LIBS -lncurses" else echo "$as_me:$LINENO: checking for tgetstr in -lcurses" >&5 echo $ECHO_N "checking for tgetstr in -lcurses... $ECHO_C" >&6 if test "${ac_cv_lib_curses_tgetstr+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcurses $LIBS" cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any gcc2 internal prototype to avoid an error. */ #ifdef __cplusplus extern "C" #endif /* We use char because int might match the return type of a gcc2 builtin and then its argument prototype would still apply. */ char tgetstr (); int main () { tgetstr (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_lib_curses_tgetstr=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_lib_curses_tgetstr=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi echo "$as_me:$LINENO: result: $ac_cv_lib_curses_tgetstr" >&5 echo "${ECHO_T}$ac_cv_lib_curses_tgetstr" >&6 if test $ac_cv_lib_curses_tgetstr = yes; then cat >>confdefs.h <<\_ACEOF #define USE_TERMCAP 1 _ACEOF LIBS="$LIBS -lcurses" if test "${ac_cv_header_termcap_h+set}" = set; then echo "$as_me:$LINENO: checking for termcap.h" >&5 echo $ECHO_N "checking for termcap.h... $ECHO_C" >&6 if test "${ac_cv_header_termcap_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi echo "$as_me:$LINENO: result: $ac_cv_header_termcap_h" >&5 echo "${ECHO_T}$ac_cv_header_termcap_h" >&6 else # Is the header compilable? echo "$as_me:$LINENO: checking termcap.h usability" >&5 echo $ECHO_N "checking termcap.h usability... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f conftest.$ac_objext conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6 # Is the header present? echo "$as_me:$LINENO: checking termcap.h presence" >&5 echo $ECHO_N "checking termcap.h presence... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6 # So? What about this header? case $ac_header_compiler:$ac_header_preproc in yes:no ) { echo "$as_me:$LINENO: WARNING: termcap.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: termcap.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: termcap.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: termcap.h: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; no:yes ) { echo "$as_me:$LINENO: WARNING: termcap.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: termcap.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: termcap.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: termcap.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: termcap.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: termcap.h: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac echo "$as_me:$LINENO: checking for termcap.h" >&5 echo $ECHO_N "checking for termcap.h... $ECHO_C" >&6 if test "${ac_cv_header_termcap_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_termcap_h=$ac_header_preproc fi echo "$as_me:$LINENO: result: $ac_cv_header_termcap_h" >&5 echo "${ECHO_T}$ac_cv_header_termcap_h" >&6 fi if test $ac_cv_header_termcap_h = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_TERMCAP_H 1 _ACEOF fi fi fi fi for ac_header in curses.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if eval "test \"\${$as_ac_Header+set}\" = set"; then echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 else # Is the header compilable? echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f conftest.$ac_objext conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6 # Is the header present? echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6 # So? What about this header? case $ac_header_compiler:$ac_header_preproc in yes:no ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; no:yes ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=$ac_header_preproc" fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF for ac_header in term.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f conftest.$ac_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done else for ac_header in ncurses/curses.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if eval "test \"\${$as_ac_Header+set}\" = set"; then echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 else # Is the header compilable? echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f conftest.$ac_objext conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6 # Is the header present? echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6 # So? What about this header? case $ac_header_compiler:$ac_header_preproc in yes:no ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; no:yes ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=$ac_header_preproc" fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF for ac_header in ncurses/term.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 if eval "test \"\${$as_ac_Header+set}\" = set"; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f conftest.$ac_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done fi done fi done TARGETS="normal reentrant" echo "$as_me:$LINENO: checking for reentrant functions" >&5 echo $ECHO_N "checking for reentrant functions... $ECHO_C" >&6 if test "${tecla_cv_reentrant+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else KEPT_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=199506L" cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include #include int main () { (void) readdir_r(NULL, NULL, NULL); (void) getpwuid_r(geteuid(), NULL, NULL, 0, NULL); (void) getpwnam_r(NULL, NULL, NULL, 0, NULL); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then tecla_cv_reentrant=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 tecla_cv_reentrant=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext CFLAGS="$KEPT_CFLAGS" fi echo "$as_me:$LINENO: result: $tecla_cv_reentrant" >&5 echo "${ECHO_T}$tecla_cv_reentrant" >&6 if test $tecla_cv_reentrant = no; then TARGETS="normal" fi if test "${ac_cv_header_sys_select_h+set}" = set; then echo "$as_me:$LINENO: checking for sys/select.h" >&5 echo $ECHO_N "checking for sys/select.h... $ECHO_C" >&6 if test "${ac_cv_header_sys_select_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi echo "$as_me:$LINENO: result: $ac_cv_header_sys_select_h" >&5 echo "${ECHO_T}$ac_cv_header_sys_select_h" >&6 else # Is the header compilable? echo "$as_me:$LINENO: checking sys/select.h usability" >&5 echo $ECHO_N "checking sys/select.h usability... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest.$ac_objext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f conftest.$ac_objext conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6 # Is the header present? echo "$as_me:$LINENO: checking sys/select.h presence" >&5 echo $ECHO_N "checking sys/select.h presence... $ECHO_C" >&6 cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null; then if test -s conftest.err; then ac_cpp_err=$ac_c_preproc_warn_flag else ac_cpp_err= fi else ac_cpp_err=yes fi if test -z "$ac_cpp_err"; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6 # So? What about this header? case $ac_header_compiler:$ac_header_preproc in yes:no ) { echo "$as_me:$LINENO: WARNING: sys/select.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: sys/select.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: sys/select.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: sys/select.h: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; no:yes ) { echo "$as_me:$LINENO: WARNING: sys/select.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: sys/select.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: sys/select.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: sys/select.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: sys/select.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: sys/select.h: proceeding with the preprocessor's result" >&2;} ( cat <<\_ASBOX ## ------------------------------------ ## ## Report this to bug-autoconf@gnu.org. ## ## ------------------------------------ ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac echo "$as_me:$LINENO: checking for sys/select.h" >&5 echo $ECHO_N "checking for sys/select.h... $ECHO_C" >&6 if test "${ac_cv_header_sys_select_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_sys_select_h=$ac_header_preproc fi echo "$as_me:$LINENO: result: $ac_cv_header_sys_select_h" >&5 echo "${ECHO_T}$ac_cv_header_sys_select_h" >&6 fi if test $ac_cv_header_sys_select_h = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_SYS_SELECT_H 1 _ACEOF fi echo "$as_me:$LINENO: checking for select system call" >&5 echo $ECHO_N "checking for select system call... $ECHO_C" >&6 if test "${tecla_cv_select+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif int main () { fd_set fds; int nready; FD_ZERO(&fds); FD_SET(1, &fds); nready = select(2, &fds, &fds, &fds, NULL); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then tecla_cv_select=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 tecla_cv_select=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext fi echo "$as_me:$LINENO: result: $tecla_cv_select" >&5 echo "${ECHO_T}$tecla_cv_select" >&6 if test $tecla_cv_select = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_SELECT 1 _ACEOF fi echo "$as_me:$LINENO: checking for SysV pseudo-terminals" >&5 echo $ECHO_N "checking for SysV pseudo-terminals... $ECHO_C" >&6 if test "${tecla_cv_sysv_pty+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { char *name = ptsname(0); int i1 = grantpt(0); int i2 = unlockpt(0); int i3 = ioctl(0, I_PUSH, "ptem"); return 0; ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 (eval $ac_link) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='test -s conftest$ac_exeext' { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 (eval $ac_try) 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then tecla_cv_sysv_pty=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 tecla_cv_sysv_pty=no fi rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext fi echo "$as_me:$LINENO: result: $tecla_cv_sysv_pty" >&5 echo "${ECHO_T}$tecla_cv_sysv_pty" >&6 if test $tecla_cv_sysv_pty = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_SYSV_PTY 1 _ACEOF fi SHARED_EXT="" SHARED_ALT="" SHARED_CFLAGS="" LINK_SHARED="" DEFS_R="-D_POSIX_C_SOURCE=199506L -DPREFER_REENTRANT" LIBR_MANDIR="man3" LIBR_MANEXT="3" FUNC_MANDIR="man3" FUNC_MANEXT="3" PROG_MANDIR="man1" PROG_MANEXT="1" MISC_MANDIR="man7" MISC_MANEXT="7" FILE_MANDIR="man5" FILE_MANEXT="5" # Check whether --with-file-actions or --without-file-actions was given. if test "${with_file_actions+set}" = set; then withval="$with_file_actions" cat >>confdefs.h <<\_ACEOF #define HIDE_FILE_SYSTEM 1 _ACEOF fi; # Check whether --with-file-system or --without-file-system was given. if test "${with_file_system+set}" = set; then withval="$with_file_system" cat >>confdefs.h <<\_ACEOF #define WITHOUT_FILE_SYSTEM 1 _ACEOF fi; case $target in *solaris*) cat >>confdefs.h <<\_ACEOF #define __EXTENSIONS__ 1 _ACEOF SHARED_EXT=".so.${MAJOR_VER}" SHARED_ALT=".so" LINK_SHARED="$LD"' -G -M $$(srcdir)/libtecla.map -o $$@ -h $$(@F) -z defs -i $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-Kpic" case $CC in */cc|cc) SHARED_CFLAGS="$SHARED_CFLAGS -xstrconst" ;; esac case $target_cpu in sparc) SHARED_CFLAGS="$SHARED_CFLAGS -xregs=no%appl" esac case $target_os in solaris2.[89]*|solaris2.1[0-9]*) LIBR_MANEXT=3lib FUNC_MANEXT=3tecla LIBR_MANDIR=man$LIBR_MANEXT FUNC_MANDIR=man$FUNC_MANEXT esac MISC_MANDIR="man5" MISC_MANEXT="5" FILE_MANDIR="man4" FILE_MANEXT="4" ;; *linux*) SHARED_EXT=".so.${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}" SHARED_ALT=".so .so.${MAJOR_VER}" echo "$as_me:$LINENO: checking for --version-script in GNU ld" >&5 echo $ECHO_N "checking for --version-script in GNU ld... $ECHO_C" >&6 if test "${tecla_cv_gnu_ld_script+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if (echo 'void dummy(void) {return;}' > dummy.c; $CC -c -fpic dummy.c; \ $LD -o dummy.so dummy.o -shared --version-script=$srcdir/libtecla.map) 1>&2 2>/dev/null; then tecla_cv_gnu_ld_script=yes else tecla_cv_gnu_ld_script=no fi rm -f dummy.c dummy.o dummy.so fi echo "$as_me:$LINENO: result: $tecla_cv_gnu_ld_script" >&5 echo "${ECHO_T}$tecla_cv_gnu_ld_script" >&6 if test $tecla_cv_gnu_ld_script = yes; then VERSION_OPT='--version-script=$$(srcdir)/libtecla.map' else VERSION_OPT='' fi LINK_SHARED="$LD"' -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-fpic" CFLAGS="-D_SVID_SOURCE -D_BSD_SOURCE $CFLAGS" ;; *hpux*) SHARED_EXT=".${MAJOR_VER}" SHARED_ALT=".sl" LINK_SHARED="$LD"' -b +h $$(@F) +k +vshlibunsats -o $$@ -c libtecla.map.opt $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="+z" MISC_MANEXT=5 FILE_MANEXT=4 MISC_MANDIR=man$MISC_MANEXT FILE_MANDIR=man$FILE_MANEXT ;; *darwin*) SHARED_EXT=".${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}.dylib" SHARED_ALT=".dylib .${MAJOR_VER}.dylib" LINK_SHARED='$(CC) -o $$@ -dynamiclib -flat_namespace -undefined suppress -compatibility_version '${MAJOR_VER}.${MINOR_VER}' -current_version '${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}' -install_name '${libdir}'/$$@ $$(LIB_OBJECTS)' SHARED_CFLAGS="" ;; *dec-osf*) cat >>confdefs.h <<\_ACEOF #define _OSF_SOURCE 1 _ACEOF ;; *freebsd*) SHARED_EXT=".so.${MAJOR_VER}" SHARED_ALT=".so" VERSION_OPT='--version-script=$$(srcdir)/libtecla.map' LINK_SHARED='ld -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-fpic" ;; mips-sgi-irix*) DEFS_R="$DEFS_R -D_XOPEN_SOURCE=500" if test "$RANLIB"_ = "_"; then RANLIB=":" fi ;; esac if test "$GCC"_ = "yes"_ && test "$LINK_SHARED"_ != "_" ; then SHARED_CFLAGS="-fpic" case $target in sparc-*-solaris*) SHARED_CFLAGS="$SHARED_CFLAGS -mno-app-regs" ;; *darwin*) SHARED_CFLAGS="" ;; esac LINK_SHARED="$LINK_SHARED `gcc -print-libgcc-file-name`" fi if test "$LINK_SHARED"_ != "_"; then TARGET_LIBS="static shared" else TARGET_LIBS="static" LINK_SHARED="@:" fi # Check whether --with-man-pages or --without-man-pages was given. if test "${with_man_pages+set}" = set; then withval="$with_man_pages" MAKE_MAN_PAGES="$withval" else MAKE_MAN_PAGES="yes" fi; OUTPUT_FILES="Makefile" rm -rf man/man* if test "$MAKE_MAN_PAGES"_ = "yes"_; then for area in libr func misc prog file; do for page in man/$area/*.in; do OUTPUT_FILES="$OUTPUT_FILES `echo $page | sed 's/\.in$//'`" done done fi ac_config_files="$ac_config_files $OUTPUT_FILES" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, don't put newlines in cache variables' values. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. { (set) 2>&1 | case `(ac_space=' '; set | grep ac_space) 2>&1` in *ac_space=\ *) # `set' does not quote correctly, so add quotes (double-quote # substitution turns \\\\ into \\, and sed turns \\ into \). sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n \ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p" ;; esac; } | sed ' t clear : clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ : end' >>confcache if diff $cache_file confcache >/dev/null 2>&1; then :; else if test -w $cache_file; then test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file" cat confcache >$cache_file else echo "not updating unwritable cache $cache_file" fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # VPATH may cause trouble with some makes, so we remove $(srcdir), # ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=/{ s/:*\$(srcdir):*/:/; s/:*\${srcdir}:*/:/; s/:*@srcdir@:*/:/; s/^\([^=]*=[ ]*\):*/\1/; s/:*$//; s/^[^=]*=[ ]*$//; }' fi # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then we branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. cat >confdef2opt.sed <<\_ACEOF t clear : clear s,^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\),-D\1=\2,g t quote s,^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\),-D\1=\2,g t quote d : quote s,[ `~#$^&*(){}\\|;'"<>?],\\&,g s,\[,\\&,g s,\],\\&,g s,\$,$$,g p _ACEOF # We use echo to avoid assuming a particular line-breaking character. # The extra dot is to prevent the shell from consuming trailing # line-breaks from the sub-command output. A line-break within # single-quotes doesn't work because, if this script is created in a # platform that uses two characters for line-breaks (e.g., DOS), tr # would break. ac_LF_and_DOT=`echo; echo .` DEFS=`sed -n -f confdef2opt.sed confdefs.h | tr "$ac_LF_and_DOT" ' .'` rm -f confdef2opt.sed ac_libobjs= ac_ltlibobjs= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_i=`echo "$ac_i" | sed 's/\$U\././;s/\.o$//;s/\.obj$//'` # 2. Add them. ac_libobjs="$ac_libobjs $ac_i\$U.$ac_objext" ac_ltlibobjs="$ac_ltlibobjs $ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : ${CONFIG_STATUS=./config.status} ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5 echo "$as_me: creating $CONFIG_STATUS" >&6;} cat >$CONFIG_STATUS <<_ACEOF #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be Bourne compatible if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then set -o posix fi # Support unset when possible. if (FOO=FOO; unset FOO) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # Work around bugs in pre-3.0 UWIN ksh. $as_unset ENV MAIL MAILPATH PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -n "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1; then as_expr=expr else as_expr=false fi if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)$' \| \ . : '\(.\)' 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; } /^X\/\(\/\/\)$/{ s//\1/; q; } /^X\/\(\/\).*/{ s//\1/; q; } s/.*/./; q'` # PATH needs CR, and LINENO needs CR and PATH. # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi as_lineno_1=$LINENO as_lineno_2=$LINENO as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null` test "x$as_lineno_1" != "x$as_lineno_2" && test "x$as_lineno_3" = "x$as_lineno_2" || { # Find who we are. Look in the path if we contain no path at all # relative or not. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5 echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;} { (exit 1); exit 1; }; } fi case $CONFIG_SHELL in '') as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for as_base in sh bash ksh sh5; do case $as_dir in /*) if ("$as_dir/$as_base" -c ' as_lineno_1=$LINENO as_lineno_2=$LINENO as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null` test "x$as_lineno_1" != "x$as_lineno_2" && test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; } $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; } CONFIG_SHELL=$as_dir/$as_base export CONFIG_SHELL exec "$CONFIG_SHELL" "$0" ${1+"$@"} fi;; esac done done ;; esac # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line before each line; the second 'sed' does the real # work. The second script uses 'N' to pair each line-number line # with the numbered line, and appends trailing '-' during # substitution so that $LINENO is not a special case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-) sed '=' <$as_myself | sed ' N s,$,-, : loop s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3, t loop s,-$,, s,^['$as_cr_digits']*\n,, ' >$as_me.lineno && chmod +x $as_me.lineno || { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5 echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;} { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensible to this). . ./$as_me.lineno # Exit status is that of the last command. exit } case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in *c*,-n*) ECHO_N= ECHO_C=' ' ECHO_T=' ' ;; *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; *) ECHO_N= ECHO_C='\c' ECHO_T= ;; esac if expr a : '\(a\)' >/dev/null 2>&1; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then # We could just check for DJGPP; but this test a) works b) is more generic # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04). if test -f conf$$.exe; then # Don't use ln at all; we don't have any links as_ln_s='cp -p' else as_ln_s='ln -s' fi elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.file if mkdir -p . 2>/dev/null; then as_mkdir_p=: else as_mkdir_p=false fi as_executable_p="test -f" # Sed expression to map a string onto a valid CPP name. as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" # Sed expression to map a string onto a valid variable name. as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g" # IFS # We need space, tab and new line, in precisely that order. as_nl=' ' IFS=" $as_nl" # CDPATH. $as_unset CDPATH exec 6>&1 # Open the log real soon, to keep \$[0] and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. Logging --version etc. is OK. exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX } >&5 cat >&5 <<_CSEOF This file was extended by $as_me, which was generated by GNU Autoconf 2.57. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ _CSEOF echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5 echo >&5 _ACEOF # Files that config.status was made for. if test -n "$ac_config_files"; then echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS fi if test -n "$ac_config_headers"; then echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS fi if test -n "$ac_config_links"; then echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS fi if test -n "$ac_config_commands"; then echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS fi cat >>$CONFIG_STATUS <<\_ACEOF ac_cs_usage="\ \`$as_me' instantiates files from templates according to the current configuration. Usage: $0 [OPTIONS] [FILE]... -h, --help print this help, then exit -V, --version print version number, then exit -q, --quiet do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_cs_version="\\ config.status configured by $0, generated by GNU Autoconf 2.57, with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\" Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." srcdir=$srcdir _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # If no file are specified by the user, then we need to provide default # value. By we need to know if files were specified by the user. ac_need_defaults=: while test $# != 0 do case $1 in --*=*) ac_option=`expr "x$1" : 'x\([^=]*\)='` ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'` ac_shift=: ;; -*) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; *) # This is not an option, so the user has probably given explicit # arguments. ac_option=$1 ac_need_defaults=false;; esac case $ac_option in # Handling of the options. _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --vers* | -V ) echo "$ac_cs_version"; exit 0 ;; --he | --h) # Conflict between --help and --header { { echo "$as_me:$LINENO: error: ambiguous option: $1 Try \`$0 --help' for more information." >&5 echo "$as_me: error: ambiguous option: $1 Try \`$0 --help' for more information." >&2;} { (exit 1); exit 1; }; };; --help | --hel | -h ) echo "$ac_cs_usage"; exit 0 ;; --debug | --d* | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift CONFIG_FILES="$CONFIG_FILES $ac_optarg" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg" ac_need_defaults=false;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1 Try \`$0 --help' for more information." >&5 echo "$as_me: error: unrecognized option: $1 Try \`$0 --help' for more information." >&2;} { (exit 1); exit 1; }; } ;; *) ac_config_targets="$ac_config_targets $1" ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF if \$ac_cs_recheck; then echo "running $SHELL $0 " $ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6 exec $SHELL $0 $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF for ac_config_target in $ac_config_targets do case "$ac_config_target" in # Handling of arguments. "$OUTPUT_FILES" ) CONFIG_FILES="$CONFIG_FILES $OUTPUT_FILES" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} { (exit 1); exit 1; }; };; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason to put it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Create a temporary directory, and hook for its removal unless debugging. $debug || { trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0 trap '{ (exit 1); exit 1; }' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d -q "./confstatXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" } || { tmp=./confstat$$-$RANDOM (umask 077 && mkdir $tmp) } || { echo "$me: cannot create a temporary directory in ." >&2 { (exit 1); exit 1; } } _ACEOF cat >>$CONFIG_STATUS <<_ACEOF # # CONFIG_FILES section. # # No need to generate the scripts if there are no CONFIG_FILES. # This happens for instance when ./config.status config.h if test -n "\$CONFIG_FILES"; then # Protect against being on the right side of a sed subst in config.status. sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g; s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF s,@SHELL@,$SHELL,;t t s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t s,@exec_prefix@,$exec_prefix,;t t s,@prefix@,$prefix,;t t s,@program_transform_name@,$program_transform_name,;t t s,@bindir@,$bindir,;t t s,@sbindir@,$sbindir,;t t s,@libexecdir@,$libexecdir,;t t s,@datadir@,$datadir,;t t s,@sysconfdir@,$sysconfdir,;t t s,@sharedstatedir@,$sharedstatedir,;t t s,@localstatedir@,$localstatedir,;t t s,@libdir@,$libdir,;t t s,@includedir@,$includedir,;t t s,@oldincludedir@,$oldincludedir,;t t s,@infodir@,$infodir,;t t s,@mandir@,$mandir,;t t s,@build_alias@,$build_alias,;t t s,@host_alias@,$host_alias,;t t s,@target_alias@,$target_alias,;t t s,@DEFS@,$DEFS,;t t s,@ECHO_C@,$ECHO_C,;t t s,@ECHO_N@,$ECHO_N,;t t s,@ECHO_T@,$ECHO_T,;t t s,@LIBS@,$LIBS,;t t s,@MAJOR_VER@,$MAJOR_VER,;t t s,@MINOR_VER@,$MINOR_VER,;t t s,@MICRO_VER@,$MICRO_VER,;t t s,@CC@,$CC,;t t s,@CFLAGS@,$CFLAGS,;t t s,@LDFLAGS@,$LDFLAGS,;t t s,@CPPFLAGS@,$CPPFLAGS,;t t s,@ac_ct_CC@,$ac_ct_CC,;t t s,@EXEEXT@,$EXEEXT,;t t s,@OBJEXT@,$OBJEXT,;t t s,@SET_MAKE@,$SET_MAKE,;t t s,@LN_S@,$LN_S,;t t s,@AWK@,$AWK,;t t s,@RANLIB@,$RANLIB,;t t s,@ac_ct_RANLIB@,$ac_ct_RANLIB,;t t s,@LD@,$LD,;t t s,@ac_ct_LD@,$ac_ct_LD,;t t s,@build@,$build,;t t s,@build_cpu@,$build_cpu,;t t s,@build_vendor@,$build_vendor,;t t s,@build_os@,$build_os,;t t s,@host@,$host,;t t s,@host_cpu@,$host_cpu,;t t s,@host_vendor@,$host_vendor,;t t s,@host_os@,$host_os,;t t s,@target@,$target,;t t s,@target_cpu@,$target_cpu,;t t s,@target_vendor@,$target_vendor,;t t s,@target_os@,$target_os,;t t s,@CPP@,$CPP,;t t s,@EGREP@,$EGREP,;t t s,@TARGETS@,$TARGETS,;t t s,@SHARED_EXT@,$SHARED_EXT,;t t s,@SHARED_ALT@,$SHARED_ALT,;t t s,@SHARED_CFLAGS@,$SHARED_CFLAGS,;t t s,@LINK_SHARED@,$LINK_SHARED,;t t s,@DEFS_R@,$DEFS_R,;t t s,@LIBR_MANDIR@,$LIBR_MANDIR,;t t s,@LIBR_MANEXT@,$LIBR_MANEXT,;t t s,@FUNC_MANDIR@,$FUNC_MANDIR,;t t s,@FUNC_MANEXT@,$FUNC_MANEXT,;t t s,@PROG_MANDIR@,$PROG_MANDIR,;t t s,@PROG_MANEXT@,$PROG_MANEXT,;t t s,@MISC_MANDIR@,$MISC_MANDIR,;t t s,@MISC_MANEXT@,$MISC_MANEXT,;t t s,@FILE_MANDIR@,$FILE_MANDIR,;t t s,@FILE_MANEXT@,$FILE_MANEXT,;t t s,@TARGET_LIBS@,$TARGET_LIBS,;t t s,@MAKE_MAN_PAGES@,$MAKE_MAN_PAGES,;t t s,@LIBOBJS@,$LIBOBJS,;t t s,@LTLIBOBJS@,$LTLIBOBJS,;t t CEOF _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # Split the substitutions into bite-sized pieces for seds with # small command number limits, like on Digital OSF/1 and HP-UX. ac_max_sed_lines=48 ac_sed_frag=1 # Number of current file. ac_beg=1 # First line for current file. ac_end=$ac_max_sed_lines # Line after last line for current file. ac_more_lines=: ac_sed_cmds= while $ac_more_lines; do if test $ac_beg -gt 1; then sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag else sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag fi if test ! -s $tmp/subs.frag; then ac_more_lines=false else # The purpose of the label and of the branching condition is to # speed up the sed processing (if there are no `@' at all, there # is no need to browse any of the substitutions). # These are the two extra sed commands mentioned above. (echo ':t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed if test -z "$ac_sed_cmds"; then ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed" else ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed" fi ac_sed_frag=`expr $ac_sed_frag + 1` ac_beg=$ac_end ac_end=`expr $ac_end + $ac_max_sed_lines` fi done if test -z "$ac_sed_cmds"; then ac_sed_cmds=cat fi fi # test -n "$CONFIG_FILES" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". case $ac_file in - | *:- | *:-:* ) # input from stdin cat >$tmp/stdin ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'` ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;; *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'` ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;; * ) ac_file_in=$ac_file.in ;; esac # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories. ac_dir=`(dirname "$ac_file") 2>/dev/null || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| \ . : '\(.\)' 2>/dev/null || echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; } /^X\(\/\/\)[^/].*/{ s//\1/; q; } /^X\(\/\/\)$/{ s//\1/; q; } /^X\(\/\).*/{ s//\1/; q; } s/.*/./; q'` { if $as_mkdir_p; then mkdir -p "$ac_dir" else as_dir="$ac_dir" as_dirs= while test ! -d "$as_dir"; do as_dirs="$as_dir $as_dirs" as_dir=`(dirname "$as_dir") 2>/dev/null || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| \ . : '\(.\)' 2>/dev/null || echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; } /^X\(\/\/\)[^/].*/{ s//\1/; q; } /^X\(\/\/\)$/{ s//\1/; q; } /^X\(\/\).*/{ s//\1/; q; } s/.*/./; q'` done test ! -n "$as_dirs" || mkdir $as_dirs fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5 echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;} { (exit 1); exit 1; }; }; } ac_builddir=. if test "$ac_dir" != .; then ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A "../" for each directory in $ac_dir_suffix. ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'` else ac_dir_suffix= ac_top_builddir= fi case $srcdir in .) # No --srcdir option. We are building in place. ac_srcdir=. if test -z "$ac_top_builddir"; then ac_top_srcdir=. else ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'` fi ;; [\\/]* | ?:[\\/]* ) # Absolute path. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ;; *) # Relative path. ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_builddir$srcdir ;; esac # Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be # absolute. ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd` ac_abs_top_builddir=`cd "$ac_dir" && cd ${ac_top_builddir}. && pwd` ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd` ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd` if test x"$ac_file" != x-; then { echo "$as_me:$LINENO: creating $ac_file" >&5 echo "$as_me: creating $ac_file" >&6;} rm -f "$ac_file" fi # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ if test x"$ac_file" = x-; then configure_input= else configure_input="$ac_file. " fi configure_input=$configure_input"Generated from `echo $ac_file_in | sed 's,.*/,,'` by configure." # First look for the input files in the build tree, otherwise in the # src tree. ac_file_inputs=`IFS=: for f in $ac_file_in; do case $f in -) echo $tmp/stdin ;; [\\/$]*) # Absolute (can't be DOS-style, as IFS=:) test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5 echo "$as_me: error: cannot find input file: $f" >&2;} { (exit 1); exit 1; }; } echo $f;; *) # Relative if test -f "$f"; then # Build tree echo $f elif test -f "$srcdir/$f"; then # Source tree echo $srcdir/$f else # /dev/null tree { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5 echo "$as_me: error: cannot find input file: $f" >&2;} { (exit 1); exit 1; }; } fi;; esac done` || { (exit 1); exit 1; } _ACEOF cat >>$CONFIG_STATUS <<_ACEOF sed "$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s,@configure_input@,$configure_input,;t t s,@srcdir@,$ac_srcdir,;t t s,@abs_srcdir@,$ac_abs_srcdir,;t t s,@top_srcdir@,$ac_top_srcdir,;t t s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t s,@builddir@,$ac_builddir,;t t s,@abs_builddir@,$ac_abs_builddir,;t t s,@top_builddir@,$ac_top_builddir,;t t s,@abs_top_builddir@,$ac_abs_top_builddir,;t t " $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out rm -f $tmp/stdin if test x"$ac_file" != x-; then mv $tmp/out $ac_file else cat $tmp/out rm -f $tmp/out fi done _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF { (exit 0); exit 0; } _ACEOF chmod +x $CONFIG_STATUS ac_clean_files=$ac_clean_files_save # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || { (exit 1); exit 1; } fi ./libtecla/RELEASE.NOTES0100644000076400007640000007020310141247361013024 0ustar mcsmcsThis file lists major changes which accompany each new release. Version 1.6.1: This is primarily a minor bug-fix release. One added feature is the ability to call gl_normal_io() from callbacks registered by gl_watch_fd() and gl_inactivity_timeout(). This allows these callbacks to cleanly suspend line editing before either reading from the terminal, or writing to the terminal; and then subsequently causes the input line to be automatically redisplayed, and line-editing to be resumed by gl_get_line(), as soon as the callback returns. Another minor change is that if the terminal type specified in the TERM environment variable is set to "dumb", gl_get_line() now treats the terminal as though it were a non-interactive stream, rather than treating it as a VT100-compatible terminal. This means that it doesn't either prompt for input, or perform any command-line editing, even when it really is interacting with a terminal. This is aimed at the rare situation where a third-pary program that connects to libtecla through an embedded pseudo-terminal, needs to be forced to behave as though it weren't talking to a terminal, in order that it be useable in non-interactive scripts. Note that in the previous release, the optional configuration function, gl_tty_signals(), was incorrectly swapping the suspend and terminal signal handlers before installing them. A configuration problem that prevented select() from being used under MacOS X, has been fixed. Although not documented in the man page, it was meant to be possible to take the input line that one call to gl_get_line() returned, and ask the next call to gl_get_line() to present it back to the user for re-editing, simply by passing the pointer returned by one call to gl_get_line() as the start_line argument of the next call to gl_get_line(). This feature unfortunately stopped working in 1.6.0, so this release restores it, and officially documents it in the man page documentation of gl_get_line(). In the previous version of the library, calling gl_terminal_size() on a system without SIGWINCH support, would crash the application. This has been fixed. Libtecla now apparently compiles cleanly under IRIX. Version 1.6.0: This release is primarily a bug-fix release. However there are also four new functions, so the minor version number has been incremented to reflect this. Two of the new functions are gl_automatic_history() and gl_append_history(). The former of these functions allows the application to tell gl_get_line() not to automatically archive entered lines in the history list. The second of these functions allows the application to explicitly append a line to the history list. Thus together, these two functions allow the calling application to take over control of what is placed in the history list. The third new function is gl_query_char(), which prompts the user for a single character reply, which the user can then type without having to hit return to enter it. Unless echoing is disabled, the character that is entered is then displayed after the prompt, and a newline is started. Finally, the 4th new function is gl_read_char(), which also reads a single character from the user, but doesn't prompt the user, write anything to the terminal, or disturb any partially entered input line. It is thus safe to call this function not only from between calls to gl_get_line(), but also from application callback functions, even if gl_normal_io() hasn't been called. When using the history-search-backwards or history-search-forwards actions, if the search prefix that the user typed, contains any of the *,? or [ globbing characters, it is now treated as a glob pattern to be matched against historical lines, instead of a simple prefix. I have added a --without-file-system option to the configure script. This is intended for use in embedded systems that either don't have filesystems, or where the file-system code in libtecla is seen as unwanted bloat. See the INSTALL document for details. Similarly, I also added a --without-file-actions option to the configure script. This allows the application author/installer to prevent users of gl_get_line() from accessing the filesystem with the builtin actions of gl_get_line(). It does this by removing a number of action functions, such as expand-filename, and list-glob, and by changing the default behavior of other actions, such as complete-word and list-or-eof, to show no completions. Now to the bugs that have been fixed. Version 1.5.0 had a lot of big internal changes, so there are a number of bugs that needed to be fixed. There was a bug which caused a crash if gl_load_history() was called multiple times. There was another bug which caused a prompt not to be displayed on the next line after switching from reading input from a file to reading from the terminal. Also, in tecla configuration files, backslash escaped characters within key-binding key-sequences weren't being escaped. Thus ^\\ got interpretted as a control-\ followed by a \ character instead of as a control-\. There was a bug in the history recall mechanism which caused the search prefix to be forgotten in certain complicated usage scenarios. There was a minor memory leak in the gl_configure_getline() function. Finally, if gl_get_line() was aborted by a signal, or any other abnormal event, the value of errno which originally indicated what had happened, got zeroed by the code that restored the terminal to a usable state. Thus the application couldn't figure out what had caused the error, apart from by looking at gl_return_status(). All of these bugs have been fixed. In the Makefile, there were a number of places where install-sh was invoked without a path prefix. This has now been remedied. A fully functional workaround for a bug in Solaris' terminal I/O code has also been implemented. This bug, which only manifested itself in libtecla's uncommonly used non-blocking server I/O mode, caused characters entered while in normal I/O mode, between calls to gl_get_line() to be invisible to the next call to gl_get_line(), until the user typed at least one more key after raw terminal mode was restored. The Gnu autoconf config.guess and config.sub scripts have been updated to their latest versions. Apparently the old versions that I was previously using were too old to know about certain BSD ports. Version 1.5.0: This release includes several major new features for those using gl_get_line(), shared library support in Darwin, better cross compilation support, and various minor bug fixes. The biggest new feature is the option of a non-blocking I/O mode, in which gl_get_line() can safely be called from an application's external event-loop to incrementally read input lines from the user. This feature is documented in the gl_io_mode(3) man page. In addition, there is now support for the definition of additional word-completion action functions, which can then be bound to different keys. See the documentation of the gl_completion_action() function in the gl_get_line(3) man page. Externally defined action functions can also be defined, although presently they don't have write access to the input line, so they are restricted to operations that display information text to the terminal, or modify the environment of the calling application in some way. See the documentation of the gl_register_action() function in the gl_get_line(3) man page. Some of the non-blocking I/O support functions can also be used for improved signal handling in the normal blocking mode. In particular, the gl_list_signals() and gl_catch_blocked() functions make it easier to write reliable signal handling around gl_get_line(). The new "RELIABLE SIGNAL HANDLING" section of the gl_get_line(3) man page is intended as an introduction to this subject. Programs can now clear the terminal between calls to gl_get_line(), by calling the new gl_erase_terminal() function. The gl_display_text() function, now used in the demos to display introductory banners, is provided for formatting text according to the width of the terminal. It is now possible to install inactivity timeout callbacks in gl_get_line(), using the new gl_inactivity_timeout() function. The new gl_set_term_size() function allows the application to explicitly set the terminal size, for cases, such as when one is using a terminal at the end of a serial lineq, where the terminal driver doesn't send the process a SIGWINCH when the terminal size changes. The new gl_bind_keyseq() function provides a convenient alternative to gl_configure_getline(), for binding or unbinding one key-sequence at a time. gl_get_line()s signal handling, file-descriptor event-handling, inactivity-timeout handling and server-mode non-blocking I/O features now not only work when input is coming from a terminal, but now also work when input is coming from non-interactive streams, such as files and pipes. The history implementation has been re-written to make it more efficient and easier to modify. The biggest user-level change is that when recalling history lines using a search prefix, the same line is no longer returned more than once in a row. Previously this duplicate elimination only worked when one was recalling a line without specifying a search prefix, and this was naively performed by preventing neighboring duplicates from existing in the history list, rather than by skipping duplicates at search time. In previous versions of the library, when gl_get_line() and its associated public functions detected invalid arguments, or couldn't allocate memory, etc, error messages were written to stderr. This isn't appropriate for library functions, so instead of writing such messages to stderr, these messages are now recorded in buffers within the affected GetLine object. The latest error message can then subsequently be queried by calling gl_error_message(). The use of errno has also been expanded, and a new function called gl_return_status() has been provided to expand on the cause of the last return from gl_get_line(). User level usage and configuration information has now been split out of the gl_get_line(3) man page into a separate tecla(7) man page. The enhance(3) man page has also been renamed to enhance(1). When expanding "~/", gl_get_line() now checks for, and returns the value of the HOME environment variable, if it exists, in preference to looking up the directory of the current user in the password file. When the terminal was resized to a narrower width, previous versions of gl_get_line() would redraw the line higher up the terminal. This bug has been fixed. A bug in history recall has also been fixed, in which an error message was being generated if one attempted to recall a line while the cursor was at the end of the longest possible input line. A more serious bug, in which callbacks registered by gl_watch_fd() weren't being called for write-events, has also been fixed. Finally, a few minor fixes have been made to improve support under QNX and Mac OS X. Beware that in this release, much of the underlying code has undergone some radical re-work, so although backwards compatibility of all documented features has been preserved, there may be some lingering bugs that could break existing programs. So, if you plan to use this version in production code, please test it as far as possible within your application before releasing it to your clients, and as always, please report any unexpected behavior. Version 1.4.1: This is a maintenance release. It includes minor changes to support Mac OS X (Darwin), the QNX real-time operating system, and Cygwin under Windows. It also fixes an oversight that was preventing the tab key from inserting tab characters when users unbound the complete-word action from it. Version 1.4.0: The contents of the history list can now be saved and restored with the new gl_save_history() and gl_load_history() functions. Event handlers can now be registered to watch for and respond to I/O on arbitrary file descriptors while gl_get_line() is waiting for terminal input from the user. See the gl_get_line(3) man page for details on gl_watch_fd(). As an optional alternative to getting configuration information only from ~/.teclarc, the new gl_configure_getline() function allows configuration commands to be taken from any of, a string, a specified application-specific file, and/or a specified user-specific file. See the gl_get_line(3) man page for details. The version number of the library can now be queried using the libtecla_version() function. See the libtecla(3) man page. The new gl_group_history() function allows applications to group different types of input line in the history buffer, and arrange for only members of the appropriate group to be recalled on a given call to gl_get_line(). See the gl_get_line(3) man page. The new gl_show_history() function displays the current history list to a given stdio output stream. See the gl_get_line(3) man page. new_GetLine() now allows you to specify a history buffer size of zero, thus requesting that no history buffer be allocated. You can subsequently resize or delete the history buffer at any time, by calling gl_resize_history(), limit the number of lines that are allowed in the buffer by calling gl_limit_history(), clear either all history lines from the history list, or just the history lines that are associated with the current history group, by calling gl_clear_history, and toggle the history mechanism on and off by calling gl_toggle_history(). The new gl_terminal_size() function can be used to query the current terminal size. It can also be used to supply a default terminal size on systems where no mechanism is available for looking up the size. The contents and configuration of the history list can now be obtained by the calling application, by calling the new gl_lookup_history(), gl_state_of_history(), gl_range_of_history() and gl_size_of_history() functions. See the gl_get_line(3) man page. Echoing of the input line as it is typed, can now be turned on and off via the new gl_echo_mode() function. While echoing is disabled, newly entered input lines are omitted from the history list. See the gl_get_line(3) man page. While the default remains to display the prompt string literally, the new gl_prompt_style() function can be used to enable text attribute formatting directives in prompt strings, such as underlining, bold font, and highlighting directives. Signal handling in gl_get_line() is now customizable. The default signal handling behavior remains essentially the same, except that the SIGTSTP, SIGTTIN and SIGTTOU are now forwarded to the corresponding signal handler of the calling program, instead of causing a SIGSTOP to be sent to the application. It is now possible to remove signals from the list that are trapped by gl_get_line(), as well as add new signals to this list. The signal and terminal environments in which the signal handler of the calling program is invoked, and what gl_get_line() does after the signal handler returns, is now customizable on a per signal basis. You can now also query the last signal that was caught by gl_get_line(). This is useful when gl_get_line() aborts with errno=EINTR, and you need to know which signal caused it to abort. Key-sequences bound to action functions can now start with printable characters. Previously only keysequences starting with control or meta characters were permitted. gl_get_line() is now 8-bit clean. If the calling program has correctly called setlocale(LC_CTYPE,""), then the user can select an alternate locale by setting the standard LC_CTYPE, LC_ALL, or LANG environment variables, and international characters can then be entered directly, either by using a non-US keyboard, or by using a compose key on a standard US keyboard. Note that in locales in which meta characters become printable, meta characters no longer match M-c bindings, which then have to be entered using their escape-c equivalents. Fortunately most modern terminal emulators either output the escape-c version by default when the meta key is used, or can be configured to do so (see the gl_get_line(3) man page), so in most cases you can continue to use the meta key. Completion callback functions can now tell gl_get_line() to return the input line immediately after a successful tab completion, simply by setting the last character of the optional continuation suffix to a newline character (ie. in the call to cpl_add_completion()). It is now safe to create and use multiple GetLine objects, albeit still only from a single thread. In conjunction with the new gl_configure_getline() function, this optionally allows multiple GetLine objects with different bindings to be used to implement different input modes. The edit-mode configuration command now accepts the argument, none. This tells gl_get_line() to revert to using just the native line editing facilities provided by the terminal driver. This could be used if the termcap or terminfo entry of the host terminal were badly corrupted. Application callback functions invoked by gl_get_line() can now change the displayed prompt using the gl_replace_prompt() function. Their is now an optional program distributed with the library. This is a beta release of a program which adds tecla command-line editing to virtually any third party application without the application needing to be linked to the library. See the enhance(3) man page for further details. Although built and installed by default, the INSTALL document explains how to prevent this. The INSTALL document now explains how you can stop the demo programs from being built and installed. NetBSD/termcap fixes. Mike MacFaden reported two problems that he saw when compiling libtecla under NetBSD. Both cases were related to the use of termcap. Most systems use terminfo, so this problem has gone unnoticed until now, and won't have affected the grand majority of users. The configure script had a bug which prevented the check for CPP working properly, and getline.c wouldn't compile due to an undeclared variable when USE_TERMCAP was defined. Both problems have now been fixed. Note that if you successfully compiled version 1.3.3, this problem didn't affect you. An unfortunate and undocumented binding of the key-sequence M-O was shadowing the arrow-key bindings on systems that use ^[OA etc. I have removed this binding (the documented lower case M-o binding remains bound). Under the KDE konsole terminal this was causing the arrow keys to do something other than expected. There was a bug in the history list code which could result in strange entries appearing at the start of the history list once enough history lines had been added to the list to cause the circular history buffer to wrap. This is now fixed. Version 1.3.3: Signal handling has been re-written, and documentation of its behaviour has been added to the gl_get_line(3) man page. In addition to eliminating race conditions, and appropriately setting errno for those signals that abort gl_get_line(), many more signals are now intercepted, making it less likely that the terminal will be left in raw mode by a signal that isn't trapped by gl_get_line(). A bug was also fixed that was leaving the terminal in raw mode if the editing mode was changed interactively between vi and emacs. This was only noticeable when running programs from old shells that don't reset terminal modes. Version 1.3.2: Tim Eliseo contributed a number of improvements to vi mode, including a fuller set of vi key-bindings, implementation of the vi constraint that the cursor can't backup past the point at which input mode was entered, and restoration of overwritten characters when backspacing in overwrite mode. There are also now new bindings to allow users to toggle between vi and emacs modes interactively. The terminal bell is now used in some circumstances, such as when an unrecognized key sequence is entered. This can be turned off by the new nobeep option in the tecla configuration file. Unrelated to the above, a problem under Linux which prevented ^Q from being used to resume terminal output after the user had pressed ^S, has been fixed. Version 1.3.1: In vi mode a bug was preventing the history-search-backward and history-search-forward actions from doing anything when invoked on empty lines. On empty lines they now act like up-history and down-history respectively, as in emacs mode. When creating shared libraries under Linux, the -soname directive was being used incorrectly. The result is that Linux binaries linked with the 1.2.3, 1.2.4 and 1.3.0 versions of the tecla shared libraries, will refuse to see other versions of the shared library until relinked with version 1.3.1 or higher. The configure script can now handle the fact that under Solaris-2.6 and earlier, the only curses library is a static one that hides in /usr/ccs/lib. Under Linux it now also caters for old versions of GNU ld which don't accept version scripts. The demos are now linked against the shared version of the library if possible. Previously they were always linked with the static version. Version 1.3.0: The major change in this release is the addition of an optional vi command-line editing mode in gl_get_line(), along with lots of new action functions to support its bindings. To enable this, first create a ~/.teclarc file if you don't already have one, then add the following line to it. edit-mode vi The default vi bindings, which are designed to mimic those of the vi editor as closely as possible, are described in the gl_get_line(3) man page. A new convenience function called ef_list_expansions() has been added for listing filename expansions. See the ef_list_expansions(3) man page for details. This is used in a new list-glob binding, bound to ^Xg in emacs mode, and ^G in vi input mode. A bug has been fixed in the key-binding table expansion code. This bug would have caused problems to anybody who defined more than about 18 personalized key-bindings in their ~/.teclarc file. Version 1.2.4: Buffered I/O is now used for writing to terminals, and where supported, cursor motion is done with move-n-positions terminfo capabilities instead of doing lots of move-1-position requests. This greatly improves how the library feels over slow links. You can now optionally compile different architectures in different directories, without having to make multiple copies of the distribution. This is documented in the INSTALL file. The ksh ~+ directive is now supported. Thanks to Markus Gyger for the above improvements. Documentation has been added to the INSTALL file describing features designed to facilitate configuration and installation of the library as part of larger packages. These features are intended to remove the need to modify the tecla distribution's configuration and build procedures when embedding the libtecla distribution in other package distributions. A previous fix to stop the cursor from warping when the last character of the input line was in the last column of the terminal, was only being used for the first terminal line of the input line. It is now used for all subsequent lines as well, as originally intended. Version 1.2.3: The installation procedure has been better automated with the addition of an autoconf configure script. This means that installers can now compile and install the library by typing: ./configure make make install On all systems this makes at least the normal static version of the tecla library. It also makes the reentrant version if reentrant POSIX functions are detected. Under Solaris, Linux and HP-UX the configuration script arranges for shared libraries to be compiled in addition to the static libraries. It is hoped that installers will return information about how to compile shared libraries on other systems, for inclusion in future releases, and to this end, a new PORTING guide has been provided. The versioning number scheme has been changed. This release would have been 1.2c, but instead will be refered to as 1.2.3. The versioning scheme, based on conventions used by Sun Microsystems, is described in configure.in. The library was also tested under HP-UX, and this revealed two serious bugs, both of which have now been fixed. The first bug prevented the library from writing control codes to terminals on big-endian machines, with the exception of those running under Solaris. This was due to an int variable being used where a char was needed. The second bug had the symptom that on systems that don't use the newline character as the control code for moving the cursor down a line, a newline wasn't started when the user hit enter. Version 1.2b: Two more minor bug fixes: Many terminals don't wrap the cursor to the next line when a character is written to the rightmost terminal column. Instead, they delay starting a new line until one more character is written, at which point they move the cursor two positions. gl_get_line() wasn't aware of this, so cursor repositionings just after writing the last character of a column, caused it to erroneously go up a line. This has now been remedied, using a method that should work regardless of whether a terminal exhibits this behavior or not. Some systems dynamically record the current terminal dimensions in environment variables called LINES and COLUMNS. On such systems, during the initial terminal setup, these values should override the static values read from the terminal information databases, and now do. Previously they were only used if the dimensions returned by terminfo/termcap looked bogus. Version 1.2a: This minor release fixes the following two bugs: The initial terminal size and subsequent changes thereto, weren't being noticed by gl_get_line(). This was because the test for the existence of TIOCWINSZ was erroneously placed before the inclusion of termios.h. One of the results was that on input lines that spanned more than one terminal line, the cursor occasionally jumped unexpectedly to the previous terminal line. On entering a line that wrapped over multiple terminal lines, gl_get_line() simply output a carriage-return line-feed at the point at which the user pressed return. Thus if one typed in such a line, then moved back onto one of the earlier terminal lines before hitting return, the cursor was left on a line containing part of the line that had just been entered. This didn't do any harm, but it looked a mess. Version 1.2: A new facility for looking up and completing filenames in UNIX-style paths has now been added (eg. you can search for, or complete commands using the UNIX PATH environment variable). See the pca_lookup_file(3) man page. The already existing filename completion callback can now be made selective in what types of files it lists. See the cpl_complete_word(3) man page. Due to its potential to break applications when changed, the use of the publically defined CplFileArgs structure to configure the cpl_file_completions() callback is now deprecated. The definition of this structure has been frozen, and its documentation has been removed from the man pages. It will remain supported, but if you have used it, you are recommended to switch to the new method, which involves a new opaque configuration object, allocated via a provided constructor function, configured via accessor functions, and eventually deleted with a provided destructor function. The cpl_file_completions() callback distinguishes which structure type it has been sent by virtue of a code placed at the start of the new structure by the constructor. It is assumed that no existing applications set the boolean 'escaped' member of the CplFileArgs structure to 4568. The new method is documented in the cpl_complete_word(3) man page. Version 1.1j This was the initial public release on freshmeat.org. ./libtecla/README0100644000076400007640000000516710050336672012024 0ustar mcsmcsThis is version 1.6.1 of the tecla command-line editing library. For the current official release, please direct your browser to: http://www.astro.caltech.edu/~mcs/tecla/index.html The tecla library provides UNIX and LINUX programs with interactive command line editing facilities, similar to those of the unix tcsh shell. In addition to simple command-line editing, it supports recall of previously entered command lines, TAB completion of file names, and in-line wild-card expansion of filenames. The internal functions which perform file-name completion and wild-card expansion are also available externally for optional use by programs, along with a module for tab-completion and lookup of filenames in a list of directories. Note that special care has been taken to allow the use of this library in threaded programs. The option to enable this is discussed in the Makefile, and specific discussions of thread safety are presented in the included man pages. For instructions on how to compile and install the library, please see the INSTALL file, which should be in the same directory as this file. Copyright and Disclaimer ------------------------ Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. ./libtecla/cplfile.c0100644000076400007640000007026710027466660012736 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM /* * Standard includes. */ #include #include #include #include #include #include /* * Local includes. */ #include "libtecla.h" #include "direader.h" #include "homedir.h" #include "pathutil.h" #include "cplfile.h" #include "errmsg.h" /* * Set the maximum length allowed for usernames. * names. */ #define USR_LEN 100 /* * Set the maximum length allowed for environment variable names. */ #define ENV_LEN 100 /* * The resources needed to complete a filename are maintained in objects * of the following type. */ struct CompleteFile { ErrMsg *err; /* The error reporting buffer */ DirReader *dr; /* A directory reader */ HomeDir *home; /* A home directory expander */ PathName *path; /* The buffer in which to accumulate the path */ PathName *buff; /* A pathname work buffer */ char usrnam[USR_LEN+1]; /* The buffer used when reading the names of */ /* users. */ char envnam[ENV_LEN+1]; /* The buffer used when reading the names of */ /* environment variables. */ }; static int cf_expand_home_dir(CompleteFile *cf, const char *user); static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl, const char *prefix, const char *line, int word_start, int word_end, int escaped); static HOME_DIR_FN(cf_homedir_callback); static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl, const char *line, int word_start, int word_end, int escaped, CplCheckFn *check_fn, void *check_data); static char *cf_read_name(CompleteFile *cf, const char *type, const char *string, int slen, char *nambuf, int nammax); static int cf_prepare_suffix(CompleteFile *cf, const char *suffix, int add_escapes); /* * A stack based object of the following type is used to pass data to the * cf_homedir_callback() function. */ typedef struct { CompleteFile *cf; /* The file-completion resource object */ WordCompletion *cpl; /* The string-completion rsource object */ size_t prefix_len; /* The length of the prefix being completed */ const char *line; /* The line from which the prefix was extracted */ int word_start; /* The index in line[] of the start of the username */ int word_end; /* The index in line[] following the end of the prefix */ int escaped; /* If true, add escapes to the completion suffixes */ } CfHomeArgs; /*....................................................................... * Create a new file-completion object. * * Output: * return CompleteFile * The new object, or NULL on error. */ CompleteFile *_new_CompleteFile(void) { CompleteFile *cf; /* The object to be returned */ /* * Allocate the container. */ cf = (CompleteFile *) malloc(sizeof(CompleteFile)); if(!cf) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_CompleteFile(). */ cf->err = NULL; cf->dr = NULL; cf->home = NULL; cf->path = NULL; cf->buff = NULL; cf->usrnam[0] = '\0'; cf->envnam[0] = '\0'; /* * Allocate a place to record error messages. */ cf->err = _new_ErrMsg(); if(!cf->err) return _del_CompleteFile(cf); /* * Create the object that is used for reading directories. */ cf->dr = _new_DirReader(); if(!cf->dr) return _del_CompleteFile(cf); /* * Create the object that is used to lookup home directories. */ cf->home = _new_HomeDir(); if(!cf->home) return _del_CompleteFile(cf); /* * Create the buffer in which the completed pathname is accumulated. */ cf->path = _new_PathName(); if(!cf->path) return _del_CompleteFile(cf); /* * Create a pathname work buffer. */ cf->buff = _new_PathName(); if(!cf->buff) return _del_CompleteFile(cf); return cf; } /*....................................................................... * Delete a file-completion object. * * Input: * cf CompleteFile * The object to be deleted. * Output: * return CompleteFile * The deleted object (always NULL). */ CompleteFile *_del_CompleteFile(CompleteFile *cf) { if(cf) { cf->err = _del_ErrMsg(cf->err); cf->dr = _del_DirReader(cf->dr); cf->home = _del_HomeDir(cf->home); cf->path = _del_PathName(cf->path); cf->buff = _del_PathName(cf->buff); free(cf); }; return NULL; } /*....................................................................... * Look up the possible completions of the incomplete filename that * lies between specified indexes of a given command-line string. * * Input: * cpl WordCompletion * The object in which to record the completions. * cf CompleteFile * The filename-completion resource object. * line const char * The string containing the incomplete filename. * word_start int The index of the first character in line[] * of the incomplete filename. * word_end int The index of the character in line[] that * follows the last character of the incomplete * filename. * escaped int If true, backslashes in line[] are * interpreted as escaping the characters * that follow them, and any spaces, tabs, * backslashes, or wildcard characters in the * returned suffixes will be similarly escaped. * If false, backslashes will be interpreted as * literal parts of the file name, and no * backslashes will be added to the returned * suffixes. * check_fn CplCheckFn * If not zero, this argument specifies a * function to call to ask whether a given * file should be included in the list * of completions. * check_data void * Anonymous data to be passed to check_fn(). * Output: * return int 0 - OK. * 1 - Error. A description of the error can be * acquired by calling _cf_last_error(cf). */ int _cf_complete_file(WordCompletion *cpl, CompleteFile *cf, const char *line, int word_start, int word_end, int escaped, CplCheckFn *check_fn, void *check_data) { const char *lptr; /* A pointer into line[] */ int nleft; /* The number of characters still to be processed */ /* in line[]. */ /* * Check the arguments. */ if(!cpl || !cf || !line || word_end < word_start) { if(cf) { _err_record_msg(cf->err, "_cf_complete_file: Invalid arguments", END_ERR_MSG); }; return 1; }; /* * Clear the buffer in which the filename will be constructed. */ _pn_clear_path(cf->path); /* * How many characters are to be processed? */ nleft = word_end - word_start; /* * Get a pointer to the start of the incomplete filename. */ lptr = line + word_start; /* * If the first character is a tilde, then perform home-directory * interpolation. */ if(nleft > 0 && *lptr == '~') { int slen; if(!cf_read_name(cf, "User", ++lptr, --nleft, cf->usrnam, USR_LEN)) return 1; /* * Advance over the username in the input line. */ slen = strlen(cf->usrnam); lptr += slen; nleft -= slen; /* * If we haven't hit the end of the input string then we have a complete * username to translate to the corresponding home directory. */ if(nleft > 0) { if(cf_expand_home_dir(cf, cf->usrnam)) return 1; /* * ~user and ~ are usually followed by a directory separator to * separate them from the file contained in the home directory. * If the home directory is the root directory, then we don't want * to follow the home directory by a directory separator, so we should * skip over it so that it doesn't get copied into the filename. */ if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; }; /* * If we have reached the end of the input string, then the username * may be incomplete, and we should attempt to complete it. */ } else { /* * Look up the possible completions of the username. */ return cf_complete_username(cf, cpl, cf->usrnam, line, word_start+1, word_end, escaped); }; }; /* * Copy the rest of the path, stopping to expand $envvar expressions * where encountered. */ while(nleft > 0) { int seglen; /* The length of the next segment to be copied */ /* * Find the length of the next segment to be copied, stopping if an * unescaped '$' is seen, or the end of the path is reached. */ for(seglen=0; seglen < nleft; seglen++) { int c = lptr[seglen]; if(escaped && c == '\\') seglen++; else if(c == '$') break; /* * We will be completing the last component of the file name, * so whenever a directory separator is seen, assume that it * might be the start of the last component, and mark the character * that follows it as the start of the name that is to be completed. */ if(nleft >= FS_DIR_SEP_LEN && strncmp(lptr + seglen, FS_DIR_SEP, FS_DIR_SEP_LEN)==0) { word_start = (lptr + seglen) - line + FS_DIR_SEP_LEN; }; }; /* * We have reached either the end of the filename or the start of * $environment_variable expression. Record the newly checked * segment of the filename in the output filename, removing * backslash-escapes where needed. */ if(_pn_append_to_path(cf->path, lptr, seglen, escaped) == NULL) { _err_record_msg(cf->err, "Insufficient memory to complete filename", END_ERR_MSG); return 1; }; lptr += seglen; nleft -= seglen; /* * If the above loop finished before we hit the end of the filename, * then this was because an unescaped $ was seen. In this case, interpolate * the value of the environment variable that follows it into the output * filename. */ if(nleft > 0) { char *value; /* The value of the environment variable */ int vlen; /* The length of the value string */ int nlen; /* The length of the environment variable name */ /* * Read the name of the environment variable. */ if(!cf_read_name(cf, "Environment", ++lptr, --nleft, cf->envnam, ENV_LEN)) return 1; /* * Advance over the environment variable name in the input line. */ nlen = strlen(cf->envnam); lptr += nlen; nleft -= nlen; /* * Get the value of the environment variable. */ value = getenv(cf->envnam); if(!value) { _err_record_msg(cf->err, "Unknown environment variable: ", cf->envnam, END_ERR_MSG); return 1; }; vlen = strlen(value); /* * If we are at the start of the filename and the first character of the * environment variable value is a '~', attempt home-directory * interpolation. */ if(cf->path->name[0] == '\0' && value[0] == '~') { if(!cf_read_name(cf, "User", value+1, vlen-1, cf->usrnam, USR_LEN) || cf_expand_home_dir(cf, cf->usrnam)) return 1; /* * If the home directory is the root directory, and the ~usrname expression * was followed by a directory separator, prevent the directory separator * from being appended to the root directory by skipping it in the * input line. */ if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; }; } else { /* * Append the value of the environment variable to the output path. */ if(_pn_append_to_path(cf->path, value, strlen(value), escaped)==NULL) { _err_record_msg(cf->err, "Insufficient memory to complete filename", END_ERR_MSG); return 1; }; /* * Prevent extra directory separators from being added. */ if(nleft >= FS_DIR_SEP_LEN && strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; } else if(vlen > FS_DIR_SEP_LEN && strcmp(value + vlen - FS_DIR_SEP_LEN, FS_DIR_SEP)==0) { cf->path->name[vlen-FS_DIR_SEP_LEN] = '\0'; }; }; /* * If adding the environment variable didn't form a valid directory, * we can't complete the line, since there is no way to separate append * a partial filename to an environment variable reference without * that appended part of the name being seen later as part of the * environment variable name. Thus if the currently constructed path * isn't a directory, quite now with no completions having been * registered. */ if(!_pu_path_is_dir(cf->path->name)) return 0; /* * For the reasons given above, if we have reached the end of the filename * with the expansion of an environment variable, the only allowed * completion involves the addition of a directory separator. */ if(nleft == 0) { if(cpl_add_completion(cpl, line, lptr-line, word_end, FS_DIR_SEP, "", "")) { _err_record_msg(cf->err, cpl_last_error(cpl), END_ERR_MSG); return 1; }; return 0; }; }; }; /* * Complete the filename if possible. */ return cf_complete_entry(cf, cpl, line, word_start, word_end, escaped, check_fn, check_data); } /*....................................................................... * Return a description of the last path-completion error that occurred. * * Input: * cf CompleteFile * The path-completion resource object. * Output: * return const char * The description of the last error. */ const char *_cf_last_error(CompleteFile *cf) { return cf ? _err_get_msg(cf->err) : "NULL CompleteFile argument"; } /*....................................................................... * Lookup the home directory of the specified user, or the current user * if no name is specified, appending it to output pathname. * * Input: * cf CompleteFile * The pathname completion resource object. * user const char * The username to lookup, or "" to lookup the * current user. * Output: * return int 0 - OK. * 1 - Error. */ static int cf_expand_home_dir(CompleteFile *cf, const char *user) { /* * Attempt to lookup the home directory. */ const char *home_dir = _hd_lookup_home_dir(cf->home, user); /* * Failed? */ if(!home_dir) { _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG); return 1; }; /* * Append the home directory to the pathname string. */ if(_pn_append_to_path(cf->path, home_dir, -1, 0) == NULL) { _err_record_msg(cf->err, "Insufficient memory for home directory expansion", END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Lookup and report all completions of a given username prefix. * * Input: * cf CompleteFile * The filename-completion resource object. * cpl WordCompletion * The object in which to record the completions. * prefix const char * The prefix of the usernames to lookup. * line const char * The command-line in which the username appears. * word_start int The index within line[] of the start of the * username that is being completed. * word_end int The index within line[] of the character which * follows the incomplete username. * escaped int True if the completions need to have special * characters escaped. * Output: * return int 0 - OK. * 1 - Error. */ static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl, const char *prefix, const char *line, int word_start, int word_end, int escaped) { /* * Set up a container of anonymous arguments to be sent to the * username-lookup iterator. */ CfHomeArgs args; args.cf = cf; args.cpl = cpl; args.prefix_len = strlen(prefix); args.line = line; args.word_start = word_start; args.word_end = word_end; args.escaped = escaped; /* * Iterate through the list of users, recording those which start * with the specified prefix. */ if(_hd_scan_user_home_dirs(cf->home, prefix, &args, cf_homedir_callback)) { _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * The user/home-directory scanner callback function (see homedir.h) * used by cf_complete_username(). */ static HOME_DIR_FN(cf_homedir_callback) { /* * Get the file-completion resources from the anonymous data argument. */ CfHomeArgs *args = (CfHomeArgs *) data; WordCompletion *cpl = args->cpl; CompleteFile *cf = args->cf; /* * Copy the username into the pathname work buffer, adding backslash * escapes where needed. */ if(cf_prepare_suffix(cf, usrnam+args->prefix_len, args->escaped)) { strncpy(errmsg, _err_get_msg(cf->err), maxerr); errmsg[maxerr] = '\0'; return 1; }; /* * Report the completion suffix that was copied above. */ if(cpl_add_completion(cpl, args->line, args->word_start, args->word_end, cf->buff->name, FS_DIR_SEP, FS_DIR_SEP)) { strncpy(errmsg, cpl_last_error(cpl), maxerr); errmsg[maxerr] = '\0'; return 1; }; return 0; } /*....................................................................... * Report possible completions of the filename in cf->path->name[]. * * Input: * cf CompleteFile * The file-completion resource object. * cpl WordCompletion * The object in which to record the completions. * line const char * The input line, as received by the callback * function. * word_start int The index within line[] of the start of the * last component of the filename that is being * completed. * word_end int The index within line[] of the character which * follows the incomplete filename. * escaped int If true, escape special characters in the * completion suffixes. * check_fn CplCheckFn * If not zero, this argument specifies a * function to call to ask whether a given * file should be included in the list * of completions. * check_data void * Anonymous data to be passed to check_fn(). * Output: * return int 0 - OK. * 1 - Error. */ static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl, const char *line, int word_start, int word_end, int escaped, CplCheckFn *check_fn, void *check_data) { const char *dirpath; /* The name of the parent directory */ int start; /* The index of the start of the last filename */ /* component in the transcribed filename. */ const char *prefix; /* The filename prefix to be completed */ int prefix_len; /* The length of the filename prefix */ const char *file_name; /* The lastest filename being compared */ int waserr = 0; /* True after errors */ int terminated=0; /* True if the directory part had to be terminated */ /* * Get the pathname string and its current length. */ char *pathname = cf->path->name; int pathlen = strlen(pathname); /* * Locate the start of the final component of the pathname. */ for(start=pathlen - 1; start >= 0 && strncmp(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; start--) ; /* * Is the parent directory the root directory? */ if(start==0 || (start < 0 && strncmp(pathname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0)) { dirpath = FS_ROOT_DIR; start += FS_ROOT_DIR_LEN; /* * If we found a directory separator then the part which precedes the * last component is the name of the directory to be opened. */ } else if(start > 0) { /* * The _dr_open_dir() function requires the directory name to be '\0' * terminated, so temporarily do this by overwriting the first character * of the directory separator. */ pathname[start] = '\0'; dirpath = pathname; terminated = 1; /* * We reached the start of the pathname before finding a directory * separator, so arrange to open the current working directory. */ } else { start = 0; dirpath = FS_PWD; }; /* * Attempt to open the directory. */ if(_dr_open_dir(cf->dr, dirpath, NULL)) { _err_record_msg(cf->err, "Can't open directory: ", dirpath, END_ERR_MSG); return 1; }; /* * If removed above, restore the directory separator and skip over it * to the start of the filename. */ if(terminated) { memcpy(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN); start += FS_DIR_SEP_LEN; }; /* * Get the filename prefix and its length. */ prefix = pathname + start; prefix_len = strlen(prefix); /* * Traverse the directory, looking for files who's prefixes match the * last component of the pathname. */ while((file_name = _dr_next_file(cf->dr)) != NULL && !waserr) { int name_len = strlen(file_name); /* * Is the latest filename a possible completion of the filename prefix? */ if(name_len >= prefix_len && strncmp(prefix, file_name, prefix_len)==0) { /* * When listing all files in a directory, don't list files that start * with '.'. This is how hidden files are denoted in UNIX. */ if(prefix_len > 0 || file_name[0] != '.') { /* * Copy the completion suffix into the work pathname cf->buff->name, * adding backslash escapes if needed. */ if(cf_prepare_suffix(cf, file_name + prefix_len, escaped)) { waserr = 1; } else { /* * We want directories to be displayed with directory suffixes, * and other fully completed filenames to be followed by spaces. * To check the type of the file, append the current suffix * to the path being completed, check the filetype, then restore * the path to its original form. */ const char *cont_suffix = ""; /* The suffix to add if fully */ /* completed. */ const char *type_suffix = ""; /* The suffix to add when listing */ if(_pn_append_to_path(cf->path, file_name + prefix_len, -1, escaped) == NULL) { _err_record_msg(cf->err, "Insufficient memory to complete filename.", END_ERR_MSG); return 1; }; /* * Specify suffixes according to the file type. */ if(_pu_path_is_dir(cf->path->name)) { cont_suffix = FS_DIR_SEP; type_suffix = FS_DIR_SEP; } else if(!check_fn || check_fn(check_data, cf->path->name)) { cont_suffix = " "; } else { cf->path->name[pathlen] = '\0'; continue; }; /* * Remove the temporarily added suffix. */ cf->path->name[pathlen] = '\0'; /* * Record the latest completion. */ if(cpl_add_completion(cpl, line, word_start, word_end, cf->buff->name, type_suffix, cont_suffix)) waserr = 1; }; }; }; }; /* * Close the directory. */ _dr_close_dir(cf->dr); return waserr; } /*....................................................................... * Read a username or environment variable name, stopping when a directory * separator is seen, when the end of the string is reached, or the * output buffer overflows. * * Input: * cf CompleteFile * The file-completion resource object. * type char * The capitalized name of the type of name being read. * string char * The string who's prefix contains the name. * slen int The number of characters in string[]. * nambuf char * The output name buffer. * nammax int The longest string that will fit in nambuf[], excluding * the '\0' terminator. * Output: * return char * A pointer to nambuf on success. On error NULL is * returned and a description of the error is recorded * in cf->err. */ static char *cf_read_name(CompleteFile *cf, const char *type, const char *string, int slen, char *nambuf, int nammax) { int namlen; /* The number of characters in nambuf[] */ const char *sptr; /* A pointer into string[] */ /* * Work out the max number of characters that should be copied. */ int nmax = nammax < slen ? nammax : slen; /* * Get the environment variable name that follows the dollar. */ for(sptr=string,namlen=0; namlen < nmax && (slen-namlen < FS_DIR_SEP_LEN || strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0); namlen++) { nambuf[namlen] = *sptr++; }; /* * Did the name overflow the buffer? */ if(namlen >= nammax) { _err_record_msg(cf->err, type, " name too long", END_ERR_MSG); return NULL; }; /* * Terminate the string. */ nambuf[namlen] = '\0'; return nambuf; } /*....................................................................... * Using the work buffer cf->buff, make a suitably escaped copy of a * given completion suffix, ready to be passed to cpl_add_completion(). * * Input: * cf CompleteFile * The file-completion resource object. * suffix char * The suffix to be copied. * add_escapes int If true, escape special characters. * Output: * return int 0 - OK. * 1 - Error. */ static int cf_prepare_suffix(CompleteFile *cf, const char *suffix, int add_escapes) { const char *sptr; /* A pointer into suffix[] */ int nbsl; /* The number of backslashes to add to the suffix */ int i; /* * How long is the suffix? */ int suffix_len = strlen(suffix); /* * Clear the work buffer. */ _pn_clear_path(cf->buff); /* * Count the number of backslashes that will have to be added to * escape spaces, tabs, backslashes and wildcard characters. */ nbsl = 0; if(add_escapes) { for(sptr = suffix; *sptr; sptr++) { switch(*sptr) { case ' ': case '\t': case '\\': case '*': case '?': case '[': nbsl++; break; }; }; }; /* * Arrange for the output path buffer to have sufficient room for the * both the suffix and any backslashes that have to be inserted. */ if(_pn_resize_path(cf->buff, suffix_len + nbsl) == NULL) { _err_record_msg(cf->err, "Insufficient memory to complete filename", END_ERR_MSG); return 1; }; /* * If the suffix doesn't need any escapes, copy it directly into the * work buffer. */ if(nbsl==0) { strcpy(cf->buff->name, suffix); } else { /* * Make a copy with special characters escaped? */ if(nbsl > 0) { const char *src = suffix; char *dst = cf->buff->name; for(i=0; i #include #include #include /* * Local includes. */ #include "libtecla.h" #include "ioutil.h" #include "stringrp.h" #include "pathutil.h" #include "cplfile.h" #include "cplmatch.h" #include "errmsg.h" /* * Specify the number of strings to allocate when the string free-list * is exhausted. This also sets the number of elements to expand the * matches[] array by whenever it is found to be too small. */ #define STR_BLK_FACT 100 /* * Set the default number of spaces place between columns when listing * a set of completions. */ #define CPL_COL_SEP 2 /* * Completion matches are recorded in containers of the following * type. */ struct WordCompletion { ErrMsg *err; /* The error reporting buffer */ StringGroup *sg; /* Memory for a group of strings */ int matches_dim; /* The allocated size of result.matches[] */ CplMatches result; /* Completions to be returned to the caller */ #ifndef WITHOUT_FILE_SYSTEM CompleteFile *cf; /* The resources used for filename completion */ #endif }; static void cpl_sort_matches(WordCompletion *cpl); static void cpl_zap_duplicates(WordCompletion *cpl); static void cpl_clear_completions(WordCompletion *cpl); static int cpl_cmp_matches(const void *v1, const void *v2); static int cpl_cmp_suffixes(const void *v1, const void *v2); /* * The new_CplFileConf() constructor sets the integer first member of * the returned object to the following magic number. On seeing this, * cpl_file_completions() knows when it is passed a valid CplFileConf * object. */ #define CFC_ID_CODE 4568 #ifndef WITHOUT_FILE_SYSTEM /* * A pointer to a structure of the following type can be passed to * the builtin file-completion callback function to modify its behavior. */ struct CplFileConf { int id; /* new_CplFileConf() sets this to CFC_ID_CODE */ int escaped; /* If none-zero, backslashes in the input line are */ /* interpreted as escaping special characters and */ /* spaces, and any special characters and spaces in */ /* the listed completions will also be escaped with */ /* added backslashes. This is the default behaviour. */ /* If zero, backslashes are interpreted as being */ /* literal parts of the filename, and none are added */ /* to the completion suffixes. */ int file_start; /* The index in the input line of the first character */ /* of the filename. If you specify -1 here, */ /* cpl_file_completions() identifies the */ /* the start of the filename by looking backwards for */ /* an unescaped space, or the beginning of the line. */ CplCheckFn *chk_fn; /* If not zero, this argument specifies a */ /* function to call to ask whether a given */ /* file should be included in the list */ /* of completions. */ void *chk_data; /* Anonymous data to be passed to check_fn(). */ }; static void cpl_init_FileConf(CplFileConf *cfc); /* * When file-system access is being excluded, define a dummy structure * to satisfy the typedef in libtecla.h. */ #else struct CplFileConf {int dummy;}; #endif /* * Encapsulate the formatting information needed to layout a * multi-column listing of completions. */ typedef struct { int term_width; /* The width of the terminal (characters) */ int column_width; /* The number of characters within in each column. */ int ncol; /* The number of columns needed */ int nline; /* The number of lines needed */ } CplListFormat; /* * Given the current terminal width, and a list of completions, determine * how to best use the terminal width to display a multi-column listing * of completions. */ static void cpl_plan_listing(CplMatches *result, int term_width, CplListFormat *fmt); /* * Display a given line of a multi-column list of completions. */ static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data); /*....................................................................... * Create a new string-completion object. * * Output: * return WordCompletion * The new object, or NULL on error. */ WordCompletion *new_WordCompletion(void) { WordCompletion *cpl; /* The object to be returned */ /* * Allocate the container. */ cpl = (WordCompletion *) malloc(sizeof(WordCompletion)); if(!cpl) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_WordCompletion(). */ cpl->err = NULL; cpl->sg = NULL; cpl->matches_dim = 0; cpl->result.suffix = NULL; cpl->result.cont_suffix = NULL; cpl->result.matches = NULL; cpl->result.nmatch = 0; #ifndef WITHOUT_FILE_SYSTEM cpl->cf = NULL; #endif /* * Allocate a place to record error messages. */ cpl->err = _new_ErrMsg(); if(!cpl->err) return del_WordCompletion(cpl); /* * Allocate an object that allows a group of strings to be allocated * efficiently by placing many of them in contiguous string segments. */ #ifdef WITHOUT_FILE_SYSTEM cpl->sg = _new_StringGroup(MAX_PATHLEN_FALLBACK); #else cpl->sg = _new_StringGroup(_pu_pathname_dim()); #endif if(!cpl->sg) return del_WordCompletion(cpl); /* * Allocate an array for matching completions. This will be extended later * if needed. */ cpl->matches_dim = STR_BLK_FACT; cpl->result.matches = (CplMatch *) malloc(sizeof(cpl->result.matches[0]) * cpl->matches_dim); if(!cpl->result.matches) { errno = ENOMEM; return del_WordCompletion(cpl); }; /* * Allocate a filename-completion resource object. */ #ifndef WITHOUT_FILE_SYSTEM cpl->cf = _new_CompleteFile(); if(!cpl->cf) return del_WordCompletion(cpl); #endif return cpl; } /*....................................................................... * Delete a string-completion object. * * Input: * cpl WordCompletion * The object to be deleted. * Output: * return WordCompletion * The deleted object (always NULL). */ WordCompletion *del_WordCompletion(WordCompletion *cpl) { if(cpl) { cpl->err = _del_ErrMsg(cpl->err); cpl->sg = _del_StringGroup(cpl->sg); if(cpl->result.matches) { free(cpl->result.matches); cpl->result.matches = NULL; #ifndef WITHOUT_FILE_SYSTEM cpl->cf = _del_CompleteFile(cpl->cf); #endif }; free(cpl); }; return NULL; } /*....................................................................... * This function is designed to be called by CplMatchFn callback * functions. It adds one possible completion of the token that is being * completed to an array of completions. If the completion needs any * special quoting to be valid when displayed in the input line, this * quoting must be included in the string. * * Input: * cpl WordCompletion * The argument of the same name that was passed * to the calling CplMatchFn callback function. * line const char * The input line, as received by the callback * function. * word_start int The index within line[] of the start of the * word that is being completed. * word_end int The index within line[] of the character which * follows the incomplete word, as received by the * calling callback function. * suffix const char * The appropriately quoted string that could * be appended to the incomplete token to complete * it. A copy of this string will be allocated * internally. * type_suffix const char * When listing multiple completions, gl_get_line() * appends this string to the completion to indicate * its type to the user. If not pertinent pass "". * Otherwise pass a literal or static string. * cont_suffix const char * If this turns out to be the only completion, * gl_get_line() will append this string as * a continuation. For example, the builtin * file-completion callback registers a directory * separator here for directory matches, and a * space otherwise. If the match were a function * name you might want to append an open * parenthesis, etc.. If not relevant pass "". * Otherwise pass a literal or static string. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_add_completion(WordCompletion *cpl, const char *line, int word_start, int word_end, const char *suffix, const char *type_suffix, const char *cont_suffix) { CplMatch *match; /* The container of the new match */ char *string; /* A newly allocated copy of the completion string */ /* * Check the arguments. */ if(!cpl) return 1; if(!suffix) return 0; if(!type_suffix) type_suffix = ""; if(!cont_suffix) cont_suffix = ""; /* * Do we need to extend the array of matches[]? */ if(cpl->result.nmatch+1 > cpl->matches_dim) { int needed = cpl->matches_dim + STR_BLK_FACT; CplMatch *matches = (CplMatch *) realloc(cpl->result.matches, sizeof(cpl->result.matches[0]) * needed); if(!matches) { _err_record_msg(cpl->err, "Insufficient memory to extend array of matches.", END_ERR_MSG); return 1; }; cpl->result.matches = matches; cpl->matches_dim = needed; }; /* * Allocate memory to store the combined completion prefix and the * new suffix. */ string = _sg_alloc_string(cpl->sg, word_end-word_start + strlen(suffix)); if(!string) { _err_record_msg(cpl->err, "Insufficient memory to extend array of matches.", END_ERR_MSG); return 1; }; /* * Compose the string. */ strncpy(string, line + word_start, word_end - word_start); strcpy(string + word_end - word_start, suffix); /* * Record the new match. */ match = cpl->result.matches + cpl->result.nmatch++; match->completion = string; match->suffix = string + word_end - word_start; match->type_suffix = type_suffix; /* * Record the continuation suffix. */ cpl->result.cont_suffix = cont_suffix; return 0; } /*....................................................................... * Sort the array of matches. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_sort_matches(WordCompletion *cpl) { qsort(cpl->result.matches, cpl->result.nmatch, sizeof(cpl->result.matches[0]), cpl_cmp_matches); } /*....................................................................... * This is a qsort() comparison function used to sort matches. * * Input: * v1, v2 void * Pointers to the two matches to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int cpl_cmp_matches(const void *v1, const void *v2) { const CplMatch *m1 = (const CplMatch *) v1; const CplMatch *m2 = (const CplMatch *) v2; return strcmp(m1->completion, m2->completion); } /*....................................................................... * Sort the array of matches in order of their suffixes. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_sort_suffixes(WordCompletion *cpl) { qsort(cpl->result.matches, cpl->result.nmatch, sizeof(cpl->result.matches[0]), cpl_cmp_suffixes); } /*....................................................................... * This is a qsort() comparison function used to sort matches in order of * their suffixes. * * Input: * v1, v2 void * Pointers to the two matches to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int cpl_cmp_suffixes(const void *v1, const void *v2) { const CplMatch *m1 = (const CplMatch *) v1; const CplMatch *m2 = (const CplMatch *) v2; return strcmp(m1->suffix, m2->suffix); } /*....................................................................... * Find the common prefix of all of the matching completion matches, * and record a pointer to it in cpl->result.suffix. Note that this has * the side effect of sorting the matches into suffix order. * * Input: * cpl WordCompletion * The completion resource object. * Output: * return int 0 - OK. * 1 - Error. */ static int cpl_common_suffix(WordCompletion *cpl) { CplMatches *result; /* The result container */ const char *first, *last; /* The first and last matching suffixes */ int length; /* The length of the common suffix */ /* * Get the container of the array of matching files. */ result = &cpl->result; /* * No matching completions? */ if(result->nmatch < 1) return 0; /* * Sort th matches into suffix order. */ cpl_sort_suffixes(cpl); /* * Given that the array of matches is sorted, the first and last * suffixes are those that differ most in their prefixes, so the common * prefix of these strings is the longest common prefix of all of the * suffixes. */ first = result->matches[0].suffix; last = result->matches[result->nmatch - 1].suffix; /* * Find the point at which the first and last matching strings * first difffer. */ while(*first && *first == *last) { first++; last++; }; /* * How long is the common suffix? */ length = first - result->matches[0].suffix; /* * Allocate memory to record the common suffix. */ result->suffix = _sg_alloc_string(cpl->sg, length); if(!result->suffix) { _err_record_msg(cpl->err, "Insufficient memory to record common completion suffix.", END_ERR_MSG); return 1; }; /* * Record the common suffix. */ strncpy(result->suffix, result->matches[0].suffix, length); result->suffix[length] = '\0'; return 0; } /*....................................................................... * Discard the contents of the array of possible completion matches. * * Input: * cpl WordCompletion * The word-completion resource object. */ static void cpl_clear_completions(WordCompletion *cpl) { /* * Discard all of the strings. */ _clr_StringGroup(cpl->sg); /* * Record the fact that the array is now empty. */ cpl->result.nmatch = 0; cpl->result.suffix = NULL; cpl->result.cont_suffix = ""; /* * Also clear the error message. */ _err_clear_msg(cpl->err); return; } /*....................................................................... * Given an input line and the point at which it completion is to be * attempted, return an array of possible completions. * * Input: * cpl WordCompletion * The completion resource object. * line char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * data void * Anonymous 'data' to be passed to match_fn(). * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * record completion matches. * Output: * return CplMatches * The container of the array of possible * completions. The returned pointer refers * to a container owned by the parent WordCompletion * object, and its contents thus potentially * change on every call to cpl_matches(). * On error, NULL is returned, and a description * of the error can be acquired by calling * cpl_last_error(cpl). */ CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line, int word_end, void *data, CplMatchFn *match_fn) { int line_len; /* The total length of the input line */ /* * How long is the input line? */ line_len = strlen(line); /* * Check the arguments. */ if(!cpl || !line || !match_fn || word_end < 0 || word_end > line_len) { if(cpl) { _err_record_msg(cpl->err, "cpl_complete_word: Invalid arguments.", END_ERR_MSG); }; return NULL; }; /* * Clear the return container. */ cpl_clear_completions(cpl); /* * Have the matching function record possible completion matches in * cpl->result.matches. */ if(match_fn(cpl, data, line, word_end)) { if(_err_get_msg(cpl->err)[0] == '\0') _err_record_msg(cpl->err, "Error completing word.", END_ERR_MSG); return NULL; }; /* * Record a copy of the common initial part of all of the prefixes * in cpl->result.common. */ if(cpl_common_suffix(cpl)) return NULL; /* * Sort the matches into lexicographic order. */ cpl_sort_matches(cpl); /* * Discard any duplicate matches. */ cpl_zap_duplicates(cpl); /* * If there is more than one match, discard the continuation suffix. */ if(cpl->result.nmatch > 1) cpl->result.cont_suffix = ""; /* * Return the array of matches. */ return &cpl->result; } /*....................................................................... * Recall the return value of the last call to cpl_complete_word(). * * Input: * cpl WordCompletion * The completion resource object. * Output: * return CplMatches * The container of the array of possible * completions, as returned by the last call to * cpl_complete_word(). The returned pointer refers * to a container owned by the parent WordCompletion * object, and its contents thus potentially * change on every call to cpl_complete_word(). * On error, either in the execution of this * function, or in the last call to * cpl_complete_word(), NULL is returned, and a * description of the error can be acquired by * calling cpl_last_error(cpl). */ CplMatches *cpl_recall_matches(WordCompletion *cpl) { return (!cpl || *_err_get_msg(cpl->err)!='\0') ? NULL : &cpl->result; } /*....................................................................... * Print out an array of matching completions. * * Input: * result CplMatches * The container of the sorted array of * completions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_list_completions(CplMatches *result, FILE *fp, int term_width) { return _cpl_output_completions(result, _io_write_stdio, fp, term_width); } /*....................................................................... * Print an array of matching completions via a callback function. * * Input: * result CplMatches * The container of the sorted array of * completions. * write_fn GlWriteFn * The function to call to write the completions, * or 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int _cpl_output_completions(CplMatches *result, GlWriteFn *write_fn, void *data, int term_width) { CplListFormat fmt; /* List formatting information */ int lnum; /* The sequential number of the line to print next */ /* * Not enough space to list anything? */ if(term_width < 1) return 0; /* * Do we have a callback to write via, and any completions to be listed? */ if(write_fn && result && result->nmatch>0) { /* * Work out how to arrange the listing into fixed sized columns. */ cpl_plan_listing(result, term_width, &fmt); /* * Print the listing via the specified callback. */ for(lnum=0; lnum < fmt.nline; lnum++) { if(cpl_format_line(result, &fmt, lnum, write_fn, data)) return 1; }; }; return 0; } /*....................................................................... * Return a description of the string-completion error that occurred. * * Input: * cpl WordCompletion * The string-completion resource object. * Output: * return const char * The description of the last error. */ const char *cpl_last_error(WordCompletion *cpl) { return cpl ? _err_get_msg(cpl->err) : "NULL WordCompletion argument"; } /*....................................................................... * When an error occurs while performing a completion, you registerf a * terse description of the error by calling cpl_record_error(). This * message will then be returned on the next call to cpl_last_error(). * * Input: * cpl WordCompletion * The string-completion resource object that was * originally passed to the callback. * errmsg const char * The description of the error. */ void cpl_record_error(WordCompletion *cpl, const char *errmsg) { if(cpl && errmsg) _err_record_msg(cpl->err, errmsg, END_ERR_MSG); } /*....................................................................... * This is the builtin completion callback function which performs file * completion. * * Input: * cpl WordCompletion * An opaque pointer to the object that will * contain the matches. This should be filled * via zero or more calls to cpl_add_completion(). * data void * Either NULL to request the default * file-completion behavior, or a pointer to a * CplFileConf structure, whose members specify * a different behavior. * line char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * Output * return int 0 - OK. * 1 - Error. */ CPL_MATCH_FN(cpl_file_completions) { #ifdef WITHOUT_FILE_SYSTEM return 0; #else const char *start_path; /* The pointer to the start of the pathname */ /* in line[]. */ CplFileConf *conf; /* The new-style configuration object. */ /* * The following configuration object will be used if the caller didn't * provide one. */ CplFileConf default_conf; /* * This function can be called externally, so check its arguments. */ if(!cpl) return 1; if(!line || word_end < 0) { _err_record_msg(cpl->err, "cpl_file_completions: Invalid arguments.", END_ERR_MSG); return 1; }; /* * The 'data' argument is either a CplFileConf pointer, identifiable * by having an integer id code as its first member, or the deprecated * CplFileArgs pointer, or can be NULL to request the default * configuration. */ if(data && *(int *)data == CFC_ID_CODE) { conf = (CplFileConf *) data; } else { /* * Select the defaults. */ conf = &default_conf; cpl_init_FileConf(&default_conf); /* * If we have been passed an instance of the deprecated CplFileArgs * structure, copy its configuration parameters over the defaults. */ if(data) { CplFileArgs *args = (CplFileArgs *) data; conf->escaped = args->escaped; conf->file_start = args->file_start; }; }; /* * Get the start of the filename. If not specified by the caller * identify it by searching backwards in the input line for an * unescaped space or the start of the line. */ if(conf->file_start < 0) { start_path = _pu_start_of_path(line, word_end); if(!start_path) { _err_record_msg(cpl->err, "Unable to find the start of the filename.", END_ERR_MSG); return 1; }; } else { start_path = line + conf->file_start; }; /* * Perform the completion. */ if(_cf_complete_file(cpl, cpl->cf, line, start_path - line, word_end, conf->escaped, conf->chk_fn, conf->chk_data)) { cpl_record_error(cpl, _cf_last_error(cpl->cf)); return 1; }; return 0; #endif } /*....................................................................... * Initialize a CplFileArgs structure with default configuration * parameters. Note that the CplFileArgs configuration type is * deprecated. The opaque CplFileConf object should be used in future * applications. * * Input: * cfa CplFileArgs * The configuration object of the * cpl_file_completions() callback. */ void cpl_init_FileArgs(CplFileArgs *cfa) { if(cfa) { cfa->escaped = 1; cfa->file_start = -1; }; } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * Initialize a CplFileConf structure with default configuration * parameters. * * Input: * cfc CplFileConf * The configuration object of the * cpl_file_completions() callback. */ static void cpl_init_FileConf(CplFileConf *cfc) { if(cfc) { cfc->id = CFC_ID_CODE; cfc->escaped = 1; cfc->file_start = -1; cfc->chk_fn = 0; cfc->chk_data = NULL; }; } #endif /*....................................................................... * Create a new CplFileConf object and initialize it with defaults. * * Output: * return CplFileConf * The new object, or NULL on error. */ CplFileConf *new_CplFileConf(void) { #ifdef WITHOUT_FILE_SYSTEM errno = EINVAL; return NULL; #else CplFileConf *cfc; /* The object to be returned */ /* * Allocate the container. */ cfc = (CplFileConf *)malloc(sizeof(CplFileConf)); if(!cfc) return NULL; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_CplFileConf(). */ cpl_init_FileConf(cfc); return cfc; #endif } /*....................................................................... * Delete a CplFileConf object. * * Input: * cfc CplFileConf * The object to be deleted. * Output: * return CplFileConf * The deleted object (always NULL). */ CplFileConf *del_CplFileConf(CplFileConf *cfc) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) { /* * Delete the container. */ free(cfc); }; #endif return NULL; } /*....................................................................... * If backslashes in the filename should be treated as literal * characters, call the following function with literal=1. Otherwise * the default is to treat them as escape characters, used for escaping * spaces etc.. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * literal int Pass non-zero here to enable literal interpretation * of backslashes. Pass 0 to turn off literal * interpretation. */ void cfc_literal_escapes(CplFileConf *cfc, int literal) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) cfc->escaped = !literal; #endif } /*....................................................................... * Call this function if you know where the index at which the * filename prefix starts in the input line. Otherwise by default, * or if you specify start_index to be -1, the filename is taken * to start after the first unescaped space preceding the cursor, * or the start of the line, which ever comes first. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * start_index int The index of the start of the filename in * the input line, or -1 to select the default. */ void cfc_file_start(CplFileConf *cfc, int start_index) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) cfc->file_start = start_index; #endif } /*....................................................................... * If you only want certain types of files to be included in the * list of completions, you use the following function to specify a * callback function which will be called to ask whether a given file * should be included. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * chk_fn CplCheckFn * Zero to disable filtering, or a pointer to a * function that returns 1 if a given file should * be included in the list of completions. * chk_data void * Anonymous data to be passed to chk_fn() * every time that it is called. */ void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) { cfc->chk_fn = chk_fn; cfc->chk_data = chk_data; }; #endif } /*....................................................................... * The following CplCheckFn callback returns non-zero if the specified * filename is that of an executable. */ CPL_CHECK_FN(cpl_check_exe) { #ifdef WITHOUT_FILE_SYSTEM return 0; #else return _pu_path_is_exe(pathname); #endif } /*....................................................................... * Remove duplicates from a sorted array of matches. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_zap_duplicates(WordCompletion *cpl) { CplMatch *matches; /* The array of matches */ int nmatch; /* The number of elements in matches[] */ const char *completion; /* The completion string of the last unique match */ const char *type_suffix; /* The type of the last unique match */ int src; /* The index of the match being considered */ int dst; /* The index at which to record the next */ /* unique match. */ /* * Get the array of matches and the number of matches that it * contains. */ matches = cpl->result.matches; nmatch = cpl->result.nmatch; /* * No matches? */ if(nmatch < 1) return; /* * Initialize the comparison strings with the first match. */ completion = matches[0].completion; type_suffix = matches[0].type_suffix; /* * Go through the array of matches, copying each new unrecorded * match at the head of the array, while discarding duplicates. */ for(src=dst=1; srccompletion) != 0 || strcmp(type_suffix, match->type_suffix) != 0) { if(src != dst) matches[dst] = *match; dst++; completion = match->completion; type_suffix = match->type_suffix; }; }; /* * Record the number of unique matches that remain. */ cpl->result.nmatch = dst; return; } /*....................................................................... * Work out how to arrange a given array of completions into a listing * of one or more fixed size columns. * * Input: * result CplMatches * The set of completions to be listed. * term_width int The width of the terminal. A lower limit of * zero is quietly enforced. * Input/Output: * fmt CplListFormat * The formatting information will be assigned * to the members of *fmt. */ static void cpl_plan_listing(CplMatches *result, int term_width, CplListFormat *fmt) { int maxlen; /* The length of the longest matching string */ int i; /* * Ensure that term_width >= 0. */ if(term_width < 0) term_width = 0; /* * Start by assuming the worst case, that either nothing will fit * on the screen, or that there are no matches to be listed. */ fmt->term_width = term_width; fmt->column_width = 0; fmt->nline = fmt->ncol = 0; /* * Work out the maximum length of the matching strings. */ maxlen = 0; for(i=0; inmatch; i++) { CplMatch *match = result->matches + i; int len = strlen(match->completion) + strlen(match->type_suffix); if(len > maxlen) maxlen = len; }; /* * Nothing to list? */ if(maxlen == 0) return; /* * Split the available terminal width into columns of * maxlen + CPL_COL_SEP characters. */ fmt->column_width = maxlen; fmt->ncol = fmt->term_width / (fmt->column_width + CPL_COL_SEP); /* * If the column width is greater than the terminal width, zero columns * will have been selected. Set a lower limit of one column. Leave it * up to the caller how to deal with completions who's widths exceed * the available terminal width. */ if(fmt->ncol < 1) fmt->ncol = 1; /* * How many lines of output will be needed? */ fmt->nline = (result->nmatch + fmt->ncol - 1) / fmt->ncol; return; } /*....................................................................... * Render one line of a multi-column listing of completions, using a * callback function to pass the output to an arbitrary destination. * * Input: * result CplMatches * The container of the sorted array of * completions. * fmt CplListFormat * Formatting information. * lnum int The index of the line to print, starting * from 0, and incrementing until the return * value indicates that there is nothing more * to be printed. * write_fn GlWriteFn * The function to call to write the line, or * 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * Output: * return int 0 - Line printed ok. * 1 - Nothing to print. */ static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data) { int col; /* The index of the list column being output */ /* * If the line index is out of bounds, there is nothing to be written. */ if(lnum < 0 || lnum >= fmt->nline) return 1; /* * If no output function has been provided, return as though the * line had been printed. */ if(!write_fn) return 0; /* * Print the matches in 'ncol' columns, sorted in line order within each * column. */ for(col=0; col < fmt->ncol; col++) { int m = col*fmt->nline + lnum; /* * Is there another match to be written? Note that in general * the last line of a listing will have fewer filled columns * than the initial lines. */ if(m < result->nmatch) { CplMatch *match = result->matches + m; /* * How long are the completion and type-suffix strings? */ int clen = strlen(match->completion); int tlen = strlen(match->type_suffix); /* * Write the completion string. */ if(write_fn(data, match->completion, clen) != clen) return 1; /* * Write the type suffix, if any. */ if(tlen > 0 && write_fn(data, match->type_suffix, tlen) != tlen) return 1; /* * If another column follows the current one, pad to its start with spaces. */ if(col+1 < fmt->ncol) { /* * The following constant string of spaces is used to pad the output. */ static const char spaces[] = " "; static const int nspace = sizeof(spaces) - 1; /* * Pad to the next column, using as few sub-strings of the spaces[] * array as possible. */ int npad = fmt->column_width + CPL_COL_SEP - clen - tlen; while(npad>0) { int n = npad > nspace ? nspace : npad; if(write_fn(data, spaces + nspace - n, n) != n) return 1; npad -= n; }; }; }; }; /* * Start a new line. */ { char s[] = "\r\n"; int n = strlen(s); if(write_fn(data, s, n) != n) return 1; }; return 0; } ./libtecla/demo.c0100644000076400007640000001255710063650054012232 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include #include #include #include "libtecla.h" /* The function which displays the introductory text of the demo */ static void show_demo_introduction(GetLine *gl); /*....................................................................... * This program demonstrates how to use gl_get_line() as a line editor to * to enable users to enter input. It takes no arguments. */ int main(int argc, char *argv[]) { char *line; /* A line of input */ GetLine *gl; /* The line editor */ int major,minor,micro; /* The version number of the library */ /* * Create the line editor, specifying a max line length of 500 bytes, * and 10000 bytes to allocate to storage of historical input lines. */ gl = new_GetLine(500, 5000); if(!gl) return 1; /* * If the user has the LC_CTYPE or LC_ALL environment variables set, * enable display of characters corresponding to the specified locale. */ (void) setlocale(LC_CTYPE, ""); /* * Lookup and display the version number of the library. */ libtecla_version(&major, &minor, µ); printf("\n Welcome to the main demo program of libtecla version %d.%d.%d\n", major, minor, micro); /* * Display an introductory banner. */ show_demo_introduction(gl); /* * Load history. */ #ifndef WITHOUT_FILE_SYSTEM (void) gl_load_history(gl, "~/.demo_history", "#"); #endif /* * Read lines of input from the user and print them to stdout. */ do { /* * Get a new line from the user. */ line = gl_get_line(gl, "$ ", NULL, 0); if(!line) break; /* * Display what was entered. */ if(printf("You entered: %s", line) < 0 || fflush(stdout)) break; /* * If the user types "exit", quit the program. */ if(strcmp(line, "exit\n")==0) break; else if(strcmp(line, "history\n")==0) gl_show_history(gl, stdout, "%N %T %H\n", 0, -1); else if(strcmp(line, "size\n")==0) { GlTerminalSize size = gl_terminal_size(gl, 80, 24); printf("Terminal size = %d columns x %d lines.\n", size.ncolumn, size.nline); } else if(strcmp(line, "clear\n")==0) { if(gl_erase_terminal(gl)) return 1; }; } while(1); /* * Save historical command lines. */ #ifndef WITHOUT_FILE_SYSTEM (void) gl_save_history(gl, "~/.demo_history", "#", -1); #endif /* * Clean up. */ gl = del_GetLine(gl); return 0; } /*....................................................................... * Display introductory text to the user, formatted according to the * current terminal width and enclosed in a box of asterixes. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void show_demo_introduction(GetLine *gl) { int start; /* The column in which gl_display_text() left the cursor */ int i; /* * Break the indtroductory text into an array of strings, so as to * avoid overflowing any compiler string limits. */ const char *doc[] = { "This program is a simple shell with which you can experiment with the ", "line editing and tab completion facilities provided by the gl_get_line() ", "function. The file demo.c also serves as a fully commented example ", "of how to use gl_get_line().\n" }; /* * Form the top line of the documentation box by filling the area of * the line between a " *" prefix and a "* " suffix with asterixes. */ printf("\n"); gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); /* * Justify the documentation text within margins of asterixes. */ for(start=0,i=0; i= 0; i++) start = gl_display_text(gl, 0, " * ", " * ", ' ', 80, start,doc[i]); /* * Draw the bottom line of the documentation box. */ gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); printf("\n"); } ./libtecla/demo2.c0100644000076400007640000003306010027466660012314 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include #include #include #include "libtecla.h" /* * If the library is being built with file-system access excluded, this * demo program won't have anything to demonstrate. */ #ifdef WITHOUT_FILE_SYSTEM int main(int argc, char *argv[]) { fprintf(stderr, "\n" " This program normally demonstrates tecla's path-lookup\n" " facility. However libtecla has been installed with\n" " file-system facilities explicitly excluded, so there is\n" " nothing to demonstrate.\n\n"); return 1; } #else /* * Encapsulate the resources needed by this demo. */ typedef struct { GetLine *gl; /* The line editor */ PathCache *pc; /* A cache of executables in the user's path */ PcaPathConf *ppc; /* The configuration argument of pca_path_completions() */ } DemoRes; /* * The following functions allocate and free instances of the above * structure. */ static DemoRes *new_DemoRes(void); static DemoRes *del_DemoRes(DemoRes *res); /* * Search backwards for the start of a pathname. */ static char *start_of_path(const char *string, int back_from); /* * Find the array indexes of the first character of the first * space-delimited word in the specified string, and of the character * that follows it. */ static int get_word_limits(const char *string, int *wa, int *wb); /* * This is the demonstration completion callback function (defined below). */ static CPL_MATCH_FN(demo_cpl_fn); /* The function which displays the introductory text of the demo */ static void show_demo_introduction(GetLine *gl); /*....................................................................... * This demo takes no arguments. It reads lines of input until the * word 'exit' is entered, or C-d is pressed. It replaces the default * tab-completion callback with one which when invoked at the start of * a line, looks up completions of commands in the user's execution * path, and when invoked in other parts of the line, reverts to * normal filename completion. Whenever a new line is entered, it * extracts the first word on the line, looks it up in the user's * execution path to see if it corresponds to a known executable file, * and if so, displays the full pathname of the file, along with the * remaining arguments. */ int main(int argc, char *argv[]) { char *line; /* A line of input */ DemoRes *res; /* The resources of the demo */ int wa,wb; /* The delimiting indexes of a word in line[] */ int major,minor,micro; /* The version number of the library */ /* * Allocate the resources needed by this demo. */ res = new_DemoRes(); if(!res) return 1; /* * If the user has the LC_CTYPE or LC_ALL environment variables set, * enable display of characters corresponding to the specified locale. */ (void) setlocale(LC_CTYPE, ""); /* * Lookup and display the version number of the library. */ libtecla_version(&major, &minor, µ); printf("\n Welcome to the path-search demo of libtecla version %d.%d.%d\n", major, minor, micro); /* * Display some introductory text, left-justifying it within the current * width of the terminal and enclosing it in a box of asterixes. */ show_demo_introduction(res->gl); /* * Read lines of input from the user and print them to stdout. */ do { /* * Get a new line from the user. */ line = gl_get_line(res->gl, "$ ", NULL, 0); if(!line) break; /* * Work out the extent of the first word in the input line, and * try to identify this as a command in the path, displaying the * full pathname of the match if found. */ if(get_word_limits(line, &wa, &wb) == 0) { char *cmd = pca_lookup_file(res->pc, line + wa, wb-wa, 0); if(cmd) { printf("Command=%s\n", cmd); printf("Arguments=%s", line+wb); } else { printf("Command not found\n"); }; }; /* * If the user types "exit", quit the program. */ if(strcmp(line, "exit\n")==0) break; } while(1); /* * Clean up. */ res = del_DemoRes(res); return 0; } /*....................................................................... * This completion callback searches for completions of executables in * the user's path when invoked on a word at the start of the path, and * performs normal filename completion elsewhere. */ static CPL_MATCH_FN(demo_cpl_fn) { /* * Get the resource object that was passed to gl_customize_completion(). */ DemoRes *res = (DemoRes *) data; /* * Find the start of the filename prefix to be completed, searching * backwards for the first unescaped space, or the start of the line. */ char *start = start_of_path(line, word_end); /* * Skip spaces preceding the start of the prefix. */ while(start > line && isspace((int)(unsigned char) start[-1])) start--; /* * If the filename prefix is at the start of the line, attempt * to complete the filename as a command in the path. Otherwise * perform normal filename completion. */ return (start == line) ? pca_path_completions(cpl, res->ppc, line, word_end) : cpl_file_completions(cpl, NULL, line, word_end); } /*....................................................................... * Search backwards for the potential start of a filename. This * looks backwards from the specified index in a given string, * stopping at the first unescaped space or the start of the line. * * Input: * string const char * The string to search backwards in. * back_from int The index of the first character in string[] * that follows the pathname. * Output: * return char * The pointer to the first character of * the potential pathname, or NULL on error. */ static char *start_of_path(const char *string, int back_from) { int i, j; /* * Search backwards from the specified index. */ for(i=back_from-1; i>=0; i--) { int c = string[i]; /* * Stop on unescaped spaces. */ if(isspace((int)(unsigned char)c)) { /* * The space can't be escaped if we are at the start of the line. */ if(i==0) break; /* * Find the extent of the escape characters which precedes the space. */ for(j=i-1; j>=0 && string[j]=='\\'; j--) ; /* * If there isn't an odd number of escape characters before the space, * then the space isn't escaped. */ if((i - 1 - j) % 2 == 0) break; }; }; return (char *)string + i + 1; } /*....................................................................... * Create a new DemoRes object containing the resources needed by the * demo. * * Output: * return DemoRes * The new object, or NULL on error. */ static DemoRes *new_DemoRes(void) { DemoRes *res; /* The object to be returned */ /* * Allocate the container. */ res = (DemoRes *)malloc(sizeof(DemoRes)); if(!res) { fprintf(stderr, "new_DemoRes: Insufficient memory.\n"); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_DemoRes(). */ res->gl = NULL; res->pc = NULL; res->ppc = NULL; /* * Create the line editor, specifying a max line length of 500 bytes, * and 10000 bytes to allocate to storage of historical input lines. */ res->gl = new_GetLine(500, 10000); if(!res->gl) return del_DemoRes(res); /* * Enable text attribute formatting directives in prompt strings. */ gl_prompt_style(res->gl, GL_FORMAT_PROMPT); /* * Allocate a cache of the executable files found in the user's path. */ res->pc = new_PathCache(); if(!res->pc) return del_DemoRes(res); /* * Populate the cache with the contents of the user's path. */ if(pca_scan_path(res->pc, getenv("PATH"))) return del_DemoRes(res); /* * Arrange for susequent calls to pca_lookup_file() and pca_path_completions() * to only report files that are executable by the user. */ pca_set_check_fn(res->pc, cpl_check_exe, NULL); /* * Allocate a configuration object for use with pca_path_completions(). */ res->ppc = new_PcaPathConf(res->pc); if(!res->ppc) return del_DemoRes(res); /* * Replace the builtin filename completion callback with one which * searches for completions of executables in the user's path when * invoked on a word at the start of the path, and completes files * elsewhere. */ if(gl_customize_completion(res->gl, res, demo_cpl_fn)) return del_DemoRes(res); return res; } /*....................................................................... * Delete a DemoRes object. * * Input: * res DemoRes * The object to be deleted. * Output: * return DemoRes * The deleted object (always NULL). */ static DemoRes *del_DemoRes(DemoRes *res) { if(res) { res->gl = del_GetLine(res->gl); res->pc = del_PathCache(res->pc); res->ppc = del_PcaPathConf(res->ppc); free(res); }; return NULL; } /*....................................................................... * Return the limits of the word at the start of a given string, ignoring * leading white-space, and interpretting the first unescaped space, tab or * the end of the line, as the end of the word. * * Input: * string const char * The string to tokenize. * Input/Output: * wa,wb int * The indexes of the first character of the word, * and the character which follows the last * character of the word, will be assigned to * *wa and *wb, respectively. * Output: * return int 0 - A word was found. * 1 - No word was found before the end of the * string. */ static int get_word_limits(const char *string, int *wa, int *wb) { int escaped = 0; /* True if the next character is escaped */ /* * Skip leading white-space. */ for(*wa=0; isspace((int)(unsigned char)string[*wa]); (*wa)++) ; /* * Find the first unescaped space, stopping early if the end of the * string is reached. */ for(*wb = *wa; ; (*wb)++) { int c = string[*wb]; if(c=='\\') escaped = !escaped; else if((!escaped && isspace((int)(unsigned char)c)) || c=='\0') break; }; return *wa == *wb; } /*....................................................................... * Display introductory text to the user, formatted according to the * current terminal width and enclosed in a box of asterixes. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void show_demo_introduction(GetLine *gl) { int start; /* The column in which gl_display_text() left the cursor */ int i; /* * Break the indtroductory text into an array of strings, so as to * avoid overflowing any compiler string limits. */ const char *doc[] = { "This program demonstrates the use of the pca_lookup_file() function ", "for finding executables in the UNIX PATH. It also demonstrates ", "tab completion of the names of executables found in the path. For ", "example, if you type:\n\n ta\n\nthen hit the tab key, you will be ", "presented with a list of executables such as tar and tail whose names ", "start with the string \"ta\". If you decide to add an \"r\" to select ", "the tar command, then you type return, the full pathname of the tar ", "program will be printed.\n\nThe file demo2.c contains the code ", "of this program, and is fully commented to enable its use as ", "a working example of how to use the facilities documented in the ", "pca_lookup_file man page.\n"}; /* * Form the top line of the documentation box by filling the area of * the line between a " *" prefix and a "* " suffix with asterixes. */ printf("\n"); gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); /* * Justify the documentation text within margins of asterixes. */ for(start=0,i=0; i= 0; i++) start = gl_display_text(gl, 0, " * ", " * ", ' ', 80, start,doc[i]); /* * Draw the bottom line of the documentation box. */ gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); printf("\n"); } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/direader.c0100644000076400007640000002011410027466660013061 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM /* * Standard includes. */ #include #include #include #include /* * Operating system includes. */ #include #include #include #include #include "direader.h" #include "errmsg.h" /* * Use the reentrant POSIX threads version of readdir()? */ #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L #define USE_READDIR_R 1 #endif /* * Objects of the following type are used to maintain the resources * needed to read directories. */ struct DirReader { ErrMsg *err; /* The error reporting buffer */ DIR *dir; /* The directory stream (if open, NULL otherwise) */ struct dirent *file; /* The latest directory entry */ #ifdef USE_READDIR_R struct dirent *buffer; /* A buffer used by the threaded version of */ /* readdir() */ int buffer_dim; /* The allocated size of buffer[] */ #endif }; static int _dr_path_is_dir(const char *pathname); /*....................................................................... * Create a new DirReader object. * * Output: * return DirReader * The new object, or NULL on error. */ DirReader *_new_DirReader(void) { DirReader *dr; /* The object to be returned */ /* * Allocate the container. */ dr = (DirReader *) malloc(sizeof(DirReader)); if(!dr) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_DirReader(). */ dr->err = NULL; dr->dir = NULL; dr->file = NULL; #ifdef USE_READDIR_R dr->buffer = NULL; dr->buffer_dim = 0; #endif /* * Allocate a place to record error messages. */ dr->err = _new_ErrMsg(); if(!dr->err) return _del_DirReader(dr); return dr; } /*....................................................................... * Delete a DirReader object. * * Input: * dr DirReader * The object to be deleted. * Output: * return DirReader * The deleted object (always NULL). */ DirReader *_del_DirReader(DirReader *dr) { if(dr) { _dr_close_dir(dr); #ifdef USE_READDIR_R free(dr->buffer); #endif dr->err = _del_ErrMsg(dr->err); free(dr); }; return NULL; } /*....................................................................... * Open a new directory. * * Input: * dr DirReader * The directory reader resource object. * path const char * The directory to be opened. * Input/Output: * errmsg char ** If an error occurs and errmsg isn't NULL, a * pointer to an error description will be assigned * to *errmsg. * Output: * return int 0 - OK. * 1 - Error (see *errmsg for a description). */ int _dr_open_dir(DirReader *dr, const char *path, char **errmsg) { DIR *dir = NULL; /* The directory stream */ /* * If a directory is already open, close it first. */ (void) _dr_close_dir(dr); /* * Is the path a directory? */ if(!_dr_path_is_dir(path)) { if(errmsg) { _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG); *errmsg = _err_get_msg(dr->err); }; return 1; }; /* * Attempt to open the directory. */ dir = opendir(path); if(!dir) { if(errmsg) { _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG); *errmsg = _err_get_msg(dr->err); }; return 1; }; /* * If using POSIX threads, allocate a buffer for readdir_r(). */ #ifdef USE_READDIR_R { size_t size; int name_max = pathconf(path, _PC_NAME_MAX); #ifdef NAME_MAX if(name_max < 0) name_max = NAME_MAX; #endif if(name_max < 0) { if(errmsg) { _err_record_msg(dr->err, "Unable to deduce readdir() buffer size.", END_ERR_MSG); *errmsg = _err_get_msg(dr->err); }; closedir(dir); return 1; }; /* * How big a buffer do we need to allocate? */ size = sizeof(struct dirent) + name_max; /* * Extend the buffer? */ if(size > dr->buffer_dim || !dr->buffer) { struct dirent *buffer = (struct dirent *) (dr->buffer ? realloc(dr->buffer, size) : malloc(size)); if(!buffer) { if(errmsg) { _err_record_msg(dr->err, "Insufficient memory for readdir() buffer.", END_ERR_MSG); *errmsg = _err_get_msg(dr->err); }; closedir(dir); errno = ENOMEM; return 1; }; dr->buffer = buffer; dr->buffer_dim = size; }; }; #endif /* * Record the successfully opened directory. */ dr->dir = dir; return 0; } /*....................................................................... * If the DirReader object is currently contains an open directory, * close it. * * Input: * dr DirReader * The directory reader resource object. */ void _dr_close_dir(DirReader *dr) { if(dr && dr->dir) { closedir(dr->dir); dr->dir = NULL; dr->file = NULL; _err_clear_msg(dr->err); }; } /*....................................................................... * Read the next file from the directory opened with _dr_open_dir(). * * Input: * dr DirReader * The directory reader resource object. * Output: * return char * The name of the new file, or NULL if we reached * the end of the directory. */ char *_dr_next_file(DirReader *dr) { /* * Are we currently reading a directory? */ if(dr->dir) { /* * Read the next directory entry. */ #ifdef USE_READDIR_R if(readdir_r(dr->dir, dr->buffer, &dr->file) == 0 && dr->file) return dr->file->d_name; #else dr->file = readdir(dr->dir); if(dr->file) return dr->file->d_name; #endif }; /* * When the end of a directory is reached, close it. */ _dr_close_dir(dr); return NULL; } /*....................................................................... * Return 1 if the specified pathname refers to a directory. * * Input: * pathname const char * The path to test. * Output: * return int 0 - Not a directory. * 1 - pathname[] refers to a directory. */ static int _dr_path_is_dir(const char *pathname) { struct stat statbuf; /* The file-statistics return buffer */ /* * Look up the file attributes. */ if(stat(pathname, &statbuf) < 0) return 0; /* * Is the file a directory? */ return S_ISDIR(statbuf.st_mode) != 0; } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/direader.h0100644000076400007640000000357010027466660013075 0ustar mcsmcs#ifndef dirreader_h #define dirreader_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ typedef struct DirReader DirReader; DirReader *_new_DirReader(void); DirReader *_del_DirReader(DirReader *dr); int _dr_open_dir(DirReader *dr, const char *pathname, char **errmsg); char *_dr_next_file(DirReader *dr); void _dr_close_dir(DirReader *dr); #endif ./libtecla/expand.c0100644000076400007640000013024010027466660012563 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM #include #include #include #include #include "freelist.h" #include "direader.h" #include "pathutil.h" #include "homedir.h" #include "stringrp.h" #include "libtecla.h" #include "ioutil.h" #include "expand.h" #include "errmsg.h" /* * Specify the number of elements to extend the files[] array by * when it proves to be too small. This also sets the initial size * of the array. */ #define MATCH_BLK_FACT 256 /* * A list of directory iterators is maintained using nodes of the * following form. */ typedef struct DirNode DirNode; struct DirNode { DirNode *next; /* The next directory in the list */ DirNode *prev; /* The node that precedes this node in the list */ DirReader *dr; /* The directory reader object */ }; typedef struct { FreeList *mem; /* Memory for DirNode list nodes */ DirNode *head; /* The head of the list of used and unused cache nodes */ DirNode *next; /* The next unused node between head and tail */ DirNode *tail; /* The tail of the list of unused cache nodes */ } DirCache; /* * Specify how many directory cache nodes to allocate at a time. */ #define DIR_CACHE_BLK 20 /* * Set the maximum length allowed for usernames. */ #define USR_LEN 100 /* * Set the maximum length allowed for environment variable names. */ #define ENV_LEN 100 /* * Set the default number of spaces place between columns when listing * a set of expansions. */ #define EF_COL_SEP 2 struct ExpandFile { ErrMsg *err; /* The error reporting buffer */ StringGroup *sg; /* A list of string segments in which */ /* matching filenames are stored. */ DirCache cache; /* The cache of directory reader objects */ PathName *path; /* The pathname being matched */ HomeDir *home; /* Home-directory lookup object */ int files_dim; /* The allocated dimension of result.files[] */ char usrnam[USR_LEN+1]; /* A user name */ char envnam[ENV_LEN+1]; /* An environment variable name */ FileExpansion result; /* The container used to return the results of */ /* expanding a path. */ }; static int ef_record_pathname(ExpandFile *ef, const char *pathname, int remove_escapes); static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, int remove_escapes); static void ef_clear_files(ExpandFile *ef); static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname); static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node); static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen); static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, const char *pattern, int separate); static int ef_matches_range(int c, const char *pattern, const char **endp); static int ef_string_matches_pattern(const char *file, const char *pattern, int xplicit, const char *nextp); static int ef_cmp_strings(const void *v1, const void *v2); /* * Encapsulate the formatting information needed to layout a * multi-column listing of expansions. */ typedef struct { int term_width; /* The width of the terminal (characters) */ int column_width; /* The number of characters within in each column. */ int ncol; /* The number of columns needed */ int nline; /* The number of lines needed */ } EfListFormat; /* * Given the current terminal width, and a list of file expansions, * determine how to best use the terminal width to display a multi-column * listing of expansions. */ static void ef_plan_listing(FileExpansion *result, int term_width, EfListFormat *fmt); /* * Display a given line of a multi-column list of file-expansions. */ static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data); /*....................................................................... * Create the resources needed to expand filenames. * * Output: * return ExpandFile * The new object, or NULL on error. */ ExpandFile *new_ExpandFile(void) { ExpandFile *ef; /* The object to be returned */ /* * Allocate the container. */ ef = (ExpandFile *) malloc(sizeof(ExpandFile)); if(!ef) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_ExpandFile(). */ ef->err = NULL; ef->sg = NULL; ef->cache.mem = NULL; ef->cache.head = NULL; ef->cache.next = NULL; ef->cache.tail = NULL; ef->path = NULL; ef->home = NULL; ef->result.files = NULL; ef->result.nfile = 0; ef->usrnam[0] = '\0'; ef->envnam[0] = '\0'; /* * Allocate a place to record error messages. */ ef->err = _new_ErrMsg(); if(!ef->err) return del_ExpandFile(ef); /* * Allocate a list of string segments for storing filenames. */ ef->sg = _new_StringGroup(_pu_pathname_dim()); if(!ef->sg) return del_ExpandFile(ef); /* * Allocate a freelist for allocating directory cache nodes. */ ef->cache.mem = _new_FreeList(sizeof(DirNode), DIR_CACHE_BLK); if(!ef->cache.mem) return del_ExpandFile(ef); /* * Allocate a pathname buffer. */ ef->path = _new_PathName(); if(!ef->path) return del_ExpandFile(ef); /* * Allocate an object for looking up home-directories. */ ef->home = _new_HomeDir(); if(!ef->home) return del_ExpandFile(ef); /* * Allocate an array for files. This will be extended later if needed. */ ef->files_dim = MATCH_BLK_FACT; ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) * ef->files_dim); if(!ef->result.files) { errno = ENOMEM; return del_ExpandFile(ef); }; return ef; } /*....................................................................... * Delete a ExpandFile object. * * Input: * ef ExpandFile * The object to be deleted. * Output: * return ExpandFile * The deleted object (always NULL). */ ExpandFile *del_ExpandFile(ExpandFile *ef) { if(ef) { DirNode *dnode; /* * Delete the string segments. */ ef->sg = _del_StringGroup(ef->sg); /* * Delete the cached directory readers. */ for(dnode=ef->cache.head; dnode; dnode=dnode->next) dnode->dr = _del_DirReader(dnode->dr); /* * Delete the memory from which the DirNode list was allocated, thus * deleting the list at the same time. */ ef->cache.mem = _del_FreeList(ef->cache.mem, 1); ef->cache.head = ef->cache.tail = ef->cache.next = NULL; /* * Delete the pathname buffer. */ ef->path = _del_PathName(ef->path); /* * Delete the home-directory lookup object. */ ef->home = _del_HomeDir(ef->home); /* * Delete the array of pointers to files. */ if(ef->result.files) { free(ef->result.files); ef->result.files = NULL; }; /* * Delete the error report buffer. */ ef->err = _del_ErrMsg(ef->err); /* * Delete the container. */ free(ef); }; return NULL; } /*....................................................................... * Expand a pathname, converting ~user/ and ~/ patterns at the start * of the pathname to the corresponding home directories, replacing * $envvar with the value of the corresponding environment variable, * and then, if there are any wildcards, matching these against existing * filenames. * * If no errors occur, a container is returned containing the array of * files that resulted from the expansion. If there were no wildcards * in the input pathname, this will contain just the original pathname * after expansion of ~ and $ expressions. If there were any wildcards, * then the array will contain the files that matched them. Note that * if there were any wildcards but no existing files match them, this * is counted as an error and NULL is returned. * * The supported wildcards and their meanings are: * * - Match any sequence of zero or more characters. * ? - Match any single character. * [chars] - Match any single character that appears in 'chars'. * If 'chars' contains an expression of the form a-b, * then any character between a and b, including a and b, * matches. The '-' character looses its special meaning * as a range specifier when it appears at the start * of the sequence of characters. * [^chars] - The same as [chars] except that it matches any single * character that doesn't appear in 'chars'. * * Wildcard expressions are applied to individual filename components. * They don't match across directory separators. A '.' character at * the beginning of a filename component must also be matched * explicitly by a '.' character in the input pathname, since these * are UNIX's hidden files. * * Input: * ef ExpandFile * The pathname expansion resource object. * path char * The path name to be expanded. * pathlen int The length of the suffix of path[] that * constitutes the filename to be expanded, * or -1 to specify that the whole of the * path string should be used. Note that * regardless of the value of this argument, * path[] must contain a '\0' terminated * string, since this function checks that * pathlen isn't mistakenly too long. * Output: * return FileExpansion * A pointer to a container within the given * ExpandFile object. This contains an array * of the pathnames that resulted from expanding * ~ and $ expressions and from matching any * wildcards, sorted into lexical order. * This container and its contents will be * recycled on subsequent calls, so if you need * to keep the results of two successive runs, * you will either have to allocate a private * copy of the array, or use two ExpandFile * objects. * * On error NULL is returned. A description * of the error can be acquired by calling the * ef_last_error() function. */ FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen) { DirNode *dnode; /* A directory-reader cache node */ const char *dirname; /* The name of the top level directory of the search */ const char *pptr; /* A pointer into path[] */ int wild; /* True if the path contains any wildcards */ /* * Check the arguments. */ if(!ef || !path) { if(ef) { _err_record_msg(ef->err, "ef_expand_file: NULL path argument", END_ERR_MSG); }; errno = EINVAL; return NULL; }; /* * If the caller specified that the whole of path[] be matched, * work out the corresponding length. */ if(pathlen < 0 || pathlen > strlen(path)) pathlen = strlen(path); /* * Discard previous expansion results. */ ef_clear_files(ef); /* * Preprocess the path, expanding ~/, ~user/ and $envvar references, * using ef->path as a work directory and returning a pointer to * a copy of the resulting pattern in the cache. */ path = ef_expand_special(ef, path, pathlen); if(!path) return NULL; /* * Clear the pathname buffer. */ _pn_clear_path(ef->path); /* * Does the pathname contain any wildcards? */ for(wild=0,pptr=path; !wild && *pptr; pptr++) { switch(*pptr) { case '\\': /* Skip escaped characters */ if(pptr[1]) pptr++; break; case '*': case '?': case '[': /* A wildcard character? */ wild = 1; break; }; }; /* * If there are no wildcards to match, copy the current expanded * path into the output array, removing backslash escapes while doing so. */ if(!wild) { if(ef_record_pathname(ef, path, 1)) return NULL; /* * Does the filename exist? */ ef->result.exists = _pu_file_exists(ef->result.files[0]); /* * Match wildcards against existing files. */ } else { /* * Only existing files that match the pattern will be returned in the * cache. */ ef->result.exists = 1; /* * Treat matching of the root-directory as a special case since it * isn't contained in a directory. */ if(strcmp(path, FS_ROOT_DIR) == 0) { if(ef_record_pathname(ef, FS_ROOT_DIR, 0)) return NULL; } else { /* * What should the top level directory of the search be? */ if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) { dirname = FS_ROOT_DIR; if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) { _err_record_msg(ef->err, "Insufficient memory to record path", END_ERR_MSG); return NULL; }; path += FS_ROOT_DIR_LEN; } else { dirname = FS_PWD; }; /* * Open the top-level directory of the search. */ dnode = ef_open_dir(ef, dirname); if(!dnode) return NULL; /* * Recursively match successive directory components of the path. */ if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) { dnode = ef_close_dir(ef, dnode); return NULL; }; /* * Cleanup. */ dnode = ef_close_dir(ef, dnode); }; /* * No files matched? */ if(ef->result.nfile < 1) { _err_record_msg(ef->err, "No files match", END_ERR_MSG); return NULL; }; /* * Sort the pathnames that matched. */ qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]), ef_cmp_strings); }; /* * Return the result container. */ return &ef->result; } /*....................................................................... * Attempt to recursively match the given pattern with the contents of * the current directory, descending sub-directories as needed. * * Input: * ef ExpandFile * The pathname expansion resource object. * dr DirReader * The directory reader object of the directory * to be searched. * pattern const char * The pattern to match with files in the current * directory. * separate int When appending a filename from the specified * directory to ef->pathname, insert a directory * separator between the existing pathname and * the filename, unless separate is zero. * Output: * return int 0 - OK. * 1 - Error. */ static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, const char *pattern, int separate) { const char *nextp; /* The pointer to the character that follows the part */ /* of the pattern that is to be matched with files */ /* in the current directory. */ char *file; /* The name of the file being matched */ int pathlen; /* The length of ef->pathname[] on entry to this */ /* function */ /* * Record the current length of the pathname string recorded in * ef->pathname[]. */ pathlen = strlen(ef->path->name); /* * Get a pointer to the character that follows the end of the part of * the pattern that should be matched to files within the current directory. * This will either point to a directory separator, or to the '\0' terminator * of the pattern string. */ for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; nextp++) ; /* * Read each file from the directory, attempting to match it to the * current pattern. */ while((file=_dr_next_file(dr)) != NULL) { /* * Does the latest file match the pattern up to nextp? */ if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) { /* * Append the new directory entry to the current matching pathname. */ if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) || _pn_append_to_path(ef->path, file, -1, 0)==NULL) { _err_record_msg(ef->err, "Insufficient memory to record path", END_ERR_MSG); return 1; }; /* * If we have reached the end of the pattern, record the accumulated * pathname in the list of matching files. */ if(*nextp == '\0') { if(ef_record_pathname(ef, ef->path->name, 0)) return 1; /* * If the matching directory entry is a subdirectory, and the * next character of the pattern is a directory separator, * recursively call the current function to scan the sub-directory * for matches. */ } else if(_pu_path_is_dir(ef->path->name) && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { /* * If the pattern finishes with the directory separator, then * record the pathame as matching. */ if(nextp[FS_DIR_SEP_LEN] == '\0') { if(ef_record_pathname(ef, ef->path->name, 0)) return 1; /* * Match files within the directory. */ } else { DirNode *subdnode = ef_open_dir(ef, ef->path->name); if(subdnode) { if(ef_match_relative_pathname(ef, subdnode->dr, nextp+FS_DIR_SEP_LEN, 1)) { subdnode = ef_close_dir(ef, subdnode); return 1; }; subdnode = ef_close_dir(ef, subdnode); }; }; }; /* * Remove the latest filename from the pathname string, so that * another matching file can be appended. */ ef->path->name[pathlen] = '\0'; }; }; return 0; } /*....................................................................... * Record a new matching filename. * * Input: * ef ExpandFile * The filename-match resource object. * pathname const char * The pathname to record. * remove_escapes int If true, remove backslash escapes in the * recorded copy of the pathname. * Output: * return int 0 - OK. * 1 - Error (ef->err will contain a * description of the error). */ static int ef_record_pathname(ExpandFile *ef, const char *pathname, int remove_escapes) { char *copy; /* The recorded copy of pathname[] */ /* * Attempt to make a copy of the pathname in the cache. */ copy = ef_cache_pathname(ef, pathname, remove_escapes); if(!copy) return 1; /* * If there isn't room to record a pointer to the recorded pathname in the * array of files, attempt to extend the array. */ if(ef->result.nfile + 1 > ef->files_dim) { int files_dim = ef->files_dim + MATCH_BLK_FACT; char **files = (char **) realloc(ef->result.files, files_dim * sizeof(files[0])); if(!files) { _err_record_msg(ef->err, "Insufficient memory to record all of the matching filenames", END_ERR_MSG); errno = ENOMEM; return 1; }; ef->result.files = files; ef->files_dim = files_dim; }; /* * Record a pointer to the new match. */ ef->result.files[ef->result.nfile++] = copy; return 0; } /*....................................................................... * Record a pathname in the cache. * * Input: * ef ExpandFile * The filename-match resource object. * pathname char * The pathname to record. * remove_escapes int If true, remove backslash escapes in the * copy of the pathname. * Output: * return char * The pointer to the copy of the pathname. * On error NULL is returned and a description * of the error is left in ef->err. */ static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, int remove_escapes) { char *copy = _sg_store_string(ef->sg, pathname, remove_escapes); if(!copy) _err_record_msg(ef->err, "Insufficient memory to store pathname", END_ERR_MSG); return copy; } /*....................................................................... * Clear the results of the previous expansion operation, ready for the * next. * * Input: * ef ExpandFile * The pathname expansion resource object. */ static void ef_clear_files(ExpandFile *ef) { _clr_StringGroup(ef->sg); _pn_clear_path(ef->path); ef->result.exists = 0; ef->result.nfile = 0; _err_clear_msg(ef->err); return; } /*....................................................................... * Get a new directory reader object from the cache. * * Input: * ef ExpandFile * The pathname expansion resource object. * pathname const char * The pathname of the directory. * Output: * return DirNode * The cache entry of the new directory reader, * or NULL on error. On error, ef->err will * contain a description of the error. */ static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname) { char *errmsg = NULL; /* An error message from a called function */ DirNode *node; /* The cache node used */ /* * Get the directory reader cache. */ DirCache *cache = &ef->cache; /* * Extend the cache if there are no free cache nodes. */ if(!cache->next) { node = (DirNode *) _new_FreeListNode(cache->mem); if(!node) { _err_record_msg(ef->err, "Insufficient memory to open a new directory", END_ERR_MSG); return NULL; }; /* * Initialize the cache node. */ node->next = NULL; node->prev = NULL; node->dr = NULL; /* * Allocate a directory reader object. */ node->dr = _new_DirReader(); if(!node->dr) { _err_record_msg(ef->err, "Insufficient memory to open a new directory", END_ERR_MSG); node = (DirNode *) _del_FreeListNode(cache->mem, node); return NULL; }; /* * Append the node to the cache list. */ node->prev = cache->tail; if(cache->tail) cache->tail->next = node; else cache->head = node; cache->next = cache->tail = node; }; /* * Get the first unused node, but don't remove it from the list yet. */ node = cache->next; /* * Attempt to open the specified directory. */ if(_dr_open_dir(node->dr, pathname, &errmsg)) { _err_record_msg(ef->err, errmsg, END_ERR_MSG); return NULL; }; /* * Now that we have successfully opened the specified directory, * remove the cache node from the list, and relink the list around it. */ cache->next = node->next; if(node->prev) node->prev->next = node->next; else cache->head = node->next; if(node->next) node->next->prev = node->prev; else cache->tail = node->prev; node->next = node->prev = NULL; /* * Return the successfully initialized cache node to the caller. */ return node; } /*....................................................................... * Return a directory reader object to the cache, after first closing * the directory that it was managing. * * Input: * ef ExpandFile * The pathname expansion resource object. * node DirNode * The cache entry of the directory reader, as returned * by ef_open_dir(). * Output: * return DirNode * The deleted DirNode (ie. allways NULL). */ static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node) { /* * Get the directory reader cache. */ DirCache *cache = &ef->cache; /* * Close the directory. */ _dr_close_dir(node->dr); /* * Return the node to the tail of the cache list. */ node->next = NULL; node->prev = cache->tail; if(cache->tail) cache->tail->next = node; else cache->head = cache->tail = node; if(!cache->next) cache->next = node; return NULL; } /*....................................................................... * Return non-zero if the specified file name matches a given glob * pattern. * * Input: * file const char * The file-name component to be matched to the pattern. * pattern const char * The start of the pattern to match against file[]. * xplicit int If non-zero, the first character must be matched * explicitly (ie. not with a wildcard). * nextp const char * The pointer to the the character following the * end of the pattern in pattern[]. * Output: * return int 0 - Doesn't match. * 1 - The file-name string matches the pattern. */ static int ef_string_matches_pattern(const char *file, const char *pattern, int xplicit, const char *nextp) { const char *pptr = pattern; /* The pointer used to scan the pattern */ const char *fptr = file; /* The pointer used to scan the filename string */ /* * Match each character of the pattern in turn. */ while(pptr < nextp) { /* * Handle the next character of the pattern. */ switch(*pptr) { /* * A match zero-or-more characters wildcard operator. */ case '*': /* * Skip the '*' character in the pattern. */ pptr++; /* * If wildcards aren't allowed, the pattern doesn't match. */ if(xplicit) return 0; /* * If the pattern ends with a the '*' wildcard, then the * rest of the filename matches this. */ if(pptr >= nextp) return 1; /* * Using the wildcard to match successively longer sections of * the remaining characters of the filename, attempt to match * the tail of the filename against the tail of the pattern. */ for( ; *fptr; fptr++) { if(ef_string_matches_pattern(fptr, pptr, 0, nextp)) return 1; }; return 0; /* The pattern following the '*' didn't match */ break; /* * A match-one-character wildcard operator. */ case '?': /* * If there is a character to be matched, skip it and advance the * pattern pointer. */ if(!xplicit && *fptr) { fptr++; pptr++; /* * If we hit the end of the filename string, there is no character * matching the operator, so the string doesn't match. */ } else { return 0; }; break; /* * A character range operator, with the character ranges enclosed * in matching square brackets. */ case '[': if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr)) return 0; break; /* * A backslash in the pattern prevents the following character as * being seen as a special character. */ case '\\': pptr++; /* Note fallthrough to default */ /* * A normal character to be matched explicitly. */ default: if(*fptr == *pptr) { fptr++; pptr++; } else { return 0; }; break; }; /* * After passing the first character, turn off the explicit match * requirement. */ xplicit = 0; }; /* * To get here the pattern must have been exhausted. If the filename * string matched, then the filename string must also have been * exhausted. */ return *fptr == '\0'; } /*....................................................................... * Match a character range expression terminated by an unescaped close * square bracket. * * Input: * c int The character to be matched with the range * pattern. * pattern const char * The range pattern to be matched (ie. after the * initiating '[' character). * endp const char ** On output a pointer to the character following the * range expression will be assigned to *endp. * Output: * return int 0 - Doesn't match. * 1 - The character matched. */ static int ef_matches_range(int c, const char *pattern, const char **endp) { const char *pptr = pattern; /* The pointer used to scan the pattern */ int invert = 0; /* True to invert the sense of the match */ int matched = 0; /* True if the character matched the pattern */ /* * If the first character is a caret, the sense of the match is * inverted and only if the character isn't one of those in the * range, do we say that it matches. */ if(*pptr == '^') { pptr++; invert = 1; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret). */ if(*pptr == '-') { pptr++; if(c == '-') { *endp = pptr; matched = 1; }; /* * Skip other leading '-' characters since they make no sense. */ while(*pptr == '-') pptr++; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret or a hyphen). */ if(*pptr == ']') { pptr++; if(c == ']') { *endp = pptr; matched = 1; }; }; /* * Having dealt with the characters that have special meanings at * the beginning of a character range expression, see if the * character matches any of the remaining characters of the range, * up until a terminating ']' character is seen. */ while(!matched && *pptr && *pptr != ']') { /* * Is this a range of characters signaled by the two end characters * separated by a hyphen? */ if(*pptr == '-') { if(pptr[1] != ']') { if(c >= pptr[-1] && c <= pptr[1]) matched = 1; pptr += 2; }; /* * A normal character to be compared directly. */ } else if(*pptr++ == c) { matched = 1; }; }; /* * Find the terminating ']'. */ while(*pptr && *pptr != ']') pptr++; /* * Did we find a terminating ']'? */ if(*pptr == ']') { *endp = pptr + 1; return matched ? !invert : invert; }; /* * If the pattern didn't end with a ']' then it doesn't match, regardless * of the value of the required sense of the match. */ *endp = pptr; return 0; } /*....................................................................... * This is a qsort() comparison function used to sort strings. * * Input: * v1, v2 void * Pointers to the two strings to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int ef_cmp_strings(const void *v1, const void *v2) { char * const *s1 = (char * const *) v1; char * const *s2 = (char * const *) v2; return strcmp(*s1, *s2); } /*....................................................................... * Preprocess a path, expanding ~/, ~user/ and $envvar references, using * ef->path as a work buffer, then copy the result into a cache entry, * and return a pointer to this copy. * * Input: * ef ExpandFile * The resource object of the file matcher. * pathlen int The length of the prefix of path[] to be expanded. * Output: * return char * A pointer to a copy of the output path in the * cache. On error NULL is returned, and a description * of the error is left in ef->err. */ static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen) { int spos; /* The index of the start of the path segment that needs */ /* to be copied from path[] to the output pathname. */ int ppos; /* The index of a character in path[] */ char *pptr; /* A pointer into the output path */ int escaped; /* True if the previous character was a '\' */ int i; /* * Clear the pathname buffer. */ _pn_clear_path(ef->path); /* * We need to perform two passes, one to expand environment variables * and a second to do tilde expansion. This caters for the case * where an initial dollar expansion yields a tilde expression. */ escaped = 0; for(spos=ppos=0; ppos < pathlen; ppos++) { int c = path[ppos]; if(escaped) { escaped = 0; } else if(c == '\\') { escaped = 1; } else if(c == '$') { int envlen; /* The length of the environment variable */ char *value; /* The value of the environment variable */ /* * Record the preceding unrecorded part of the pathname. */ if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) == NULL) { _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG); return NULL; }; /* * Skip the dollar. */ ppos++; /* * Copy the environment variable name that follows the dollar into * ef->envnam[], stopping if a directory separator or end of string * is seen. */ for(envlen=0; envlenenvnam[envlen] = path[ppos++]; /* * If the username overflowed the buffer, treat it as invalid (note that * on most unix systems only 8 characters are allowed in a username, * whereas our ENV_LEN is much bigger than that. */ if(envlen >= ENV_LEN) { _err_record_msg(ef->err, "Environment variable name too long", END_ERR_MSG); return NULL; }; /* * Terminate the environment variable name. */ ef->envnam[envlen] = '\0'; /* * Lookup the value of the environment variable. */ value = getenv(ef->envnam); if(!value) { _err_record_msg(ef->err, "No expansion found for: $", ef->envnam, END_ERR_MSG); return NULL; }; /* * Copy the value of the environment variable into the output pathname. */ if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) { _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG); return NULL; }; /* * Record the start of the uncopied tail of the input pathname. */ spos = ppos; }; }; /* * Record the uncopied tail of the pathname. */ if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) == NULL) { _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG); return NULL; }; /* * If the first character of the resulting pathname is a tilde, * then attempt to substitute the home directory of the specified user. */ pptr = ef->path->name; if(*pptr == '~' && path[0] != '\\') { int usrlen; /* The length of the username following the tilde */ const char *homedir; /* The home directory of the user */ int homelen; /* The length of the home directory string */ int plen; /* The current length of the path */ int skip=0; /* The number of characters to skip after the ~user */ /* * Get the current length of the output path. */ plen = strlen(ef->path->name); /* * Skip the tilde. */ pptr++; /* * Copy the optional username that follows the tilde into ef->usrnam[]. */ for(usrlen=0; usrlenusrnam[usrlen] = *pptr++; /* * If the username overflowed the buffer, treat it as invalid (note that * on most unix systems only 8 characters are allowed in a username, * whereas our USR_LEN is much bigger than that. */ if(usrlen >= USR_LEN) { _err_record_msg(ef->err, "Username too long", END_ERR_MSG); return NULL; }; /* * Terminate the username string. */ ef->usrnam[usrlen] = '\0'; /* * Lookup the home directory of the user. */ homedir = _hd_lookup_home_dir(ef->home, ef->usrnam); if(!homedir) { _err_record_msg(ef->err, _hd_last_home_dir_error(ef->home), END_ERR_MSG); return NULL; }; homelen = strlen(homedir); /* * ~user and ~ are usually followed by a directory separator to * separate them from the file contained in the home directory. * If the home directory is the root directory, then we don't want * to follow the home directory by a directory separator, so we must * erase it. */ if(strcmp(homedir, FS_ROOT_DIR) == 0 && strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { skip = FS_DIR_SEP_LEN; }; /* * If needed, increase the size of the pathname buffer to allow it * to accomodate the home directory instead of the tilde expression. * Note that pptr may not be valid after this call. */ if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) { _err_record_msg(ef->err, "Insufficient memory to expand filename", END_ERR_MSG); return NULL; }; /* * Move the part of the pathname that follows the tilde expression to * the end of where the home directory will need to be inserted. */ memmove(ef->path->name + homelen, ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1); /* * Write the home directory at the beginning of the string. */ for(i=0; ipath->name[i] = homedir[i]; }; /* * Copy the result into the cache, and return a pointer to the copy. */ return ef_cache_pathname(ef, ef->path->name, 0); } /*....................................................................... * Return a description of the last path-expansion error that occurred. * * Input: * ef ExpandFile * The path-expansion resource object. * Output: * return char * The description of the last error. */ const char *ef_last_error(ExpandFile *ef) { return ef ? _err_get_msg(ef->err) : "NULL ExpandFile argument"; } /*....................................................................... * Print out an array of matching files. * * Input: * result FileExpansion * The container of the sorted array of * expansions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width) { return _ef_output_expansions(result, _io_write_stdio, fp, term_width); } /*....................................................................... * Print out an array of matching files via a callback. * * Input: * result FileExpansion * The container of the sorted array of * expansions. * write_fn GlWriteFn * The function to call to write the * expansions or 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn, void *data, int term_width) { EfListFormat fmt; /* List formatting information */ int lnum; /* The sequential number of the line to print next */ /* * Not enough space to list anything? */ if(term_width < 1) return 0; /* * Do we have a callback to write via, and any expansions to be listed? */ if(write_fn && result && result->nfile>0) { /* * Work out how to arrange the listing into fixed sized columns. */ ef_plan_listing(result, term_width, &fmt); /* * Print the listing to the specified stream. */ for(lnum=0; lnum < fmt.nline; lnum++) { if(ef_format_line(result, &fmt, lnum, write_fn, data)) return 1; }; }; return 0; } /*....................................................................... * Work out how to arrange a given array of completions into a listing * of one or more fixed size columns. * * Input: * result FileExpansion * The set of completions to be listed. * term_width int The width of the terminal. A lower limit of * zero is quietly enforced. * Input/Output: * fmt EfListFormat * The formatting information will be assigned * to the members of *fmt. */ static void ef_plan_listing(FileExpansion *result, int term_width, EfListFormat *fmt) { int maxlen; /* The length of the longest matching string */ int i; /* * Ensure that term_width >= 0. */ if(term_width < 0) term_width = 0; /* * Start by assuming the worst case, that either nothing will fit * on the screen, or that there are no matches to be listed. */ fmt->term_width = term_width; fmt->column_width = 0; fmt->nline = fmt->ncol = 0; /* * Work out the maximum length of the matching strings. */ maxlen = 0; for(i=0; infile; i++) { int len = strlen(result->files[i]); if(len > maxlen) maxlen = len; }; /* * Nothing to list? */ if(maxlen == 0) return; /* * Split the available terminal width into columns of * maxlen + EF_COL_SEP characters. */ fmt->column_width = maxlen; fmt->ncol = fmt->term_width / (fmt->column_width + EF_COL_SEP); /* * If the column width is greater than the terminal width, zero columns * will have been selected. Set a lower limit of one column. Leave it * up to the caller how to deal with completions who's widths exceed * the available terminal width. */ if(fmt->ncol < 1) fmt->ncol = 1; /* * How many lines of output will be needed? */ fmt->nline = (result->nfile + fmt->ncol - 1) / fmt->ncol; return; } /*....................................................................... * Render one line of a multi-column listing of completions, using a * callback function to pass the output to an arbitrary destination. * * Input: * result FileExpansion * The container of the sorted array of * completions. * fmt EfListFormat * Formatting information. * lnum int The index of the line to print, starting * from 0, and incrementing until the return * value indicates that there is nothing more * to be printed. * write_fn GlWriteFn * The function to call to write the line, or * 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * Output: * return int 0 - Line printed ok. * 1 - Nothing to print. */ static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data) { int col; /* The index of the list column being output */ /* * If the line index is out of bounds, there is nothing to be written. */ if(lnum < 0 || lnum >= fmt->nline) return 1; /* * If no output function has been provided, return as though the line * had been printed. */ if(!write_fn) return 0; /* * Print the matches in 'ncol' columns, sorted in line order within each * column. */ for(col=0; col < fmt->ncol; col++) { int m = col*fmt->nline + lnum; /* * Is there another match to be written? Note that in general * the last line of a listing will have fewer filled columns * than the initial lines. */ if(m < result->nfile) { char *file = result->files[m]; /* * How long are the completion and type-suffix strings? */ int flen = strlen(file); /* * Write the completion string. */ if(write_fn(data, file, flen) != flen) return 1; /* * If another column follows the current one, pad to its start with spaces. */ if(col+1 < fmt->ncol) { /* * The following constant string of spaces is used to pad the output. */ static const char spaces[] = " "; static const int nspace = sizeof(spaces) - 1; /* * Pad to the next column, using as few sub-strings of the spaces[] * array as possible. */ int npad = fmt->column_width + EF_COL_SEP - flen; while(npad>0) { int n = npad > nspace ? nspace : npad; if(write_fn(data, spaces + nspace - n, n) != n) return 1; npad -= n; }; }; }; }; /* * Start a new line. */ { char s[] = "\r\n"; int n = strlen(s); if(write_fn(data, s, n) != n) return 1; }; return 0; } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/freelist.c0100644000076400007640000002752310027466660013132 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include "freelist.h" typedef struct FreeListBlock FreeListBlock; struct FreeListBlock { FreeListBlock *next; /* The next block in the list */ char *nodes; /* The array of free-list nodes */ }; struct FreeList { size_t node_size; /* The size of a free-list node */ unsigned blocking_factor; /* The number of nodes per block */ long nbusy; /* The number of nodes that are in use */ long ntotal; /* The total number of nodes in the free list */ FreeListBlock *block; /* The head of the list of free-list blocks */ void *free_list; /* The free-list of nodes */ }; static FreeListBlock *_new_FreeListBlock(FreeList *fl); static FreeListBlock *_del_FreeListBlock(FreeListBlock *fl); static void _thread_FreeListBlock(FreeList *fl, FreeListBlock *block); /*....................................................................... * Allocate a new free-list from blocks of 'blocking_factor' objects of size * node_size. * * Input: * node_size size_t The size of the free-list nodes to be returned * by _new_FreeListNode(). Use sizeof() to * determine this. * blocking_factor unsigned The number of objects of size 'object_size' * to allocate per block. * Output: * return FreeList * The new freelist, or NULL on error. */ FreeList *_new_FreeList(size_t node_size, unsigned blocking_factor) { FreeList *fl; /* The new free-list container */ /* * When a free-list node is on the free-list, it is used as a (void *) * link field. Roundup node_size to a mulitple of the size of a void * pointer. This, plus the fact that the array of nodes is obtained via * malloc, which returns memory suitably aligned for any object, will * ensure that the first sizeof(void *) bytes of each node will be * suitably aligned to use as a (void *) link pointer. */ node_size = sizeof(void *) * ((node_size + sizeof(void *) - 1) / sizeof(void *)); /* * Enfore a minimum block size. */ if(blocking_factor < 1) blocking_factor = 1; /* * Allocate the container of the free list. */ fl = (FreeList *) malloc(sizeof(FreeList)); if(!fl) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_FreeList(). */ fl->node_size = node_size; fl->blocking_factor = blocking_factor; fl->nbusy = 0; fl->ntotal = 0; fl->block = NULL; fl->free_list = NULL; /* * Allocate the first block of memory. */ fl->block = _new_FreeListBlock(fl); if(!fl->block) { errno = ENOMEM; return _del_FreeList(fl, 1); }; /* * Add the new list of nodes to the free-list. */ fl->free_list = fl->block->nodes; /* * Return the free-list for use. */ return fl; } /*....................................................................... * Re-thread a freelist to reclaim all allocated nodes. * This function should not be called unless if it is known that none * of the currently allocated nodes are still being used. * * Input: * fl FreeList * The free-list to be reset, or NULL. */ void _rst_FreeList(FreeList *fl) { if(fl) { FreeListBlock *block; /* * Re-thread the nodes of each block into individual free-lists. */ for(block=fl->block; block; block=block->next) _thread_FreeListBlock(fl, block); /* * Link all of the block freelists into one large freelist. */ fl->free_list = NULL; for(block=fl->block; block; block=block->next) { /* * Locate the last node of the current block. */ char *last_node = block->nodes + fl->node_size * (fl->blocking_factor - 1); /* * Make the link-field of the last node point to the first * node of the current freelist, then make the first node of the * new block the start of the freelist. */ *(void **)last_node = fl->free_list; fl->free_list = block->nodes; }; /* * All allocated nodes have now been returned to the freelist. */ fl->nbusy = 0; }; } /*....................................................................... * Delete a free-list. * * Input: * fl FreeList * The free-list to be deleted, or NULL. * force int If force==0 then _del_FreeList() will complain * and refuse to delete the free-list if any * of nodes have not been returned to the free-list. * If force!=0 then _del_FreeList() will not check * whether any nodes are still in use and will * always delete the list. * Output: * return FreeList * Always NULL (even if the list couldn't be * deleted). */ FreeList *_del_FreeList(FreeList *fl, int force) { if(fl) { /* * Check whether any nodes are in use. */ if(!force && _busy_FreeListNodes(fl) != 0) { errno = EBUSY; return NULL; }; /* * Delete the list blocks. */ { FreeListBlock *next = fl->block; while(next) { FreeListBlock *block = next; next = block->next; block = _del_FreeListBlock(block); }; }; fl->block = NULL; fl->free_list = NULL; /* * Discard the container. */ free(fl); }; return NULL; } /*....................................................................... * Allocate a new object from a free-list. * * Input: * fl FreeList * The free-list to return an object from. * Output: * return void * A new object of the size that was specified via * the node_size argument of _new_FreeList() when * the free-list was created, or NULL if there * is insufficient memory, or 'fl' is NULL. */ void *_new_FreeListNode(FreeList *fl) { void *node; /* The node to be returned */ /* * Check arguments. */ if(!fl) return NULL; /* * If the free-list has been exhausted extend it by allocating * another block of nodes. */ if(!fl->free_list) { FreeListBlock *block = _new_FreeListBlock(fl); if(!block) return NULL; /* * Prepend the new block to the list of free-list blocks. */ block->next = fl->block; fl->block = block; /* * Add the new list of nodes to the free-list. */ fl->free_list = fl->block->nodes; }; /* * Remove and return a node from the front of the free list. */ node = fl->free_list; fl->free_list = *(void **)node; /* * Record the loss of a node from the free-list. */ fl->nbusy++; /* * Return the node. */ return node; } /*....................................................................... * Return an object to the free-list that it was allocated from. * * Input: * fl FreeList * The free-list from which the object was taken. * object void * The node to be returned. * Output: * return void * Always NULL. */ void *_del_FreeListNode(FreeList *fl, void *object) { /* * Check arguments. */ if(!fl) return NULL; /* * Return the node to the head of the free list. */ if(object) { *(void **)object = fl->free_list; fl->free_list = object; /* * Record the return of the node to the free-list. */ fl->nbusy--; }; return NULL; } /*....................................................................... * Return a count of the number of nodes that are currently allocated. * * Input: * fl FreeList * The list to count wrt, or NULL. * Output: * return long The number of nodes (or 0 if fl==NULL). */ long _busy_FreeListNodes(FreeList *fl) { return fl ? fl->nbusy : 0; } /*....................................................................... * Query the number of allocated nodes in the freelist which are * currently unused. * * Input: * fl FreeList * The list to count wrt, or NULL. * Output: * return long The number of unused nodes (or 0 if fl==NULL). */ long _idle_FreeListNodes(FreeList *fl) { return fl ? (fl->ntotal - fl->nbusy) : 0; } /*....................................................................... * Allocate a new list of free-list nodes. On return the nodes will * be linked together as a list starting with the node at the lowest * address and ending with a NULL next pointer. * * Input: * fl FreeList * The free-list to allocate the list for. * Output: * return FreeListBlock * The new linked block of free-list nodes, * or NULL on error. */ static FreeListBlock *_new_FreeListBlock(FreeList *fl) { FreeListBlock *block; /* The new block to be returned */ /* * Allocate the container. */ block = (FreeListBlock *) malloc(sizeof(FreeListBlock)); if(!block) return NULL; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_FreeListBlock(). */ block->next = NULL; block->nodes = NULL; /* * Allocate the block of nodes. */ block->nodes = (char *) malloc(fl->node_size * fl->blocking_factor); if(!block->nodes) return _del_FreeListBlock(block); /* * Initialize the block as a linked list of FreeListNode's. */ _thread_FreeListBlock(fl, block); /* * Update the record of the number of nodes in the freelist. */ fl->ntotal += fl->blocking_factor; return block; } /*....................................................................... * Link each node of a freelist block to the node that follows it. * * Input: * fl FreeList * The freelist that contains the block. * block FreeListBlock * The block to be threaded. */ static void _thread_FreeListBlock(FreeList *fl, FreeListBlock *block) { char *mem = block->nodes; int i; for(i=0; iblocking_factor - 1; i++, mem += fl->node_size) *(void **)mem = mem + fl->node_size; /* Link to the next node */ *(void **)mem = NULL; /* Terminate the list */ } /*....................................................................... * Delete a free-list block. * * Input: * fl FreeListBlock * The block to be deleted, or NULL. * Output: * return FreeListBlock * Always NULL. */ static FreeListBlock *_del_FreeListBlock(FreeListBlock *fl) { if(fl) { fl->next = NULL; if(fl->nodes) free(fl->nodes); fl->nodes = NULL; free(fl); }; return NULL; } ./libtecla/freelist.h0100644000076400007640000000620110027466660013125 0ustar mcsmcs#ifndef freelist_h #define freelist_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * This module provides a memory allocation scheme that helps to * prevent memory fragmentation by allocating large blocks of * fixed sized objects and forming them into a free-list for * subsequent allocations. The free-list is expanded as needed. */ typedef struct FreeList FreeList; /* * Allocate a new free-list from blocks of 'blocking_factor' objects of size * node_size. The node_size argument should be determined by applying * the sizeof() operator to the object type that you intend to allocate from * the freelist. */ FreeList *_new_FreeList(size_t node_size, unsigned blocking_factor); /* * If it is known that none of the nodes currently allocated from * a freelist are still in use, the following function can be called * to return all nodes to the freelist without the overhead of * having to call del_FreeListNode() for every allocated node. The * nodes of the freelist can then be reused by future callers to * new_FreeListNode(). */ void _rst_FreeList(FreeList *fl); /* * Delete a free-list. */ FreeList *_del_FreeList(FreeList *fl, int force); /* * Determine the number of nodes that are currently in use. */ long _busy_FreeListNodes(FreeList *fl); /* * Query the number of allocated nodes in the freelist which are * currently unused. */ long _idle_FreeListNodes(FreeList *fl); /* * Allocate a new object from a free-list. */ void *_new_FreeListNode(FreeList *fl); /* * Return an object to the free-list that it was allocated from. */ void *_del_FreeListNode(FreeList *fl, void *object); #endif ./libtecla/getline.c0100644000076400007640000141566610141251323012737 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Standard headers. */ #include #include #include #include #include #include #include #include /* * UNIX headers. */ #include #ifdef HAVE_SELECT #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #endif /* * Handle the different sources of terminal control string and size * information. Note that if no terminal information database is available, * ANSI VT100 control sequences are used. */ #if defined(USE_TERMINFO) || defined(USE_TERMCAP) /* * Include curses.h or ncurses/curses.h depending on which is available. */ #ifdef HAVE_CURSES_H #include #elif defined(HAVE_NCURSES_CURSES_H) #include #endif /* * Include term.h where available. */ #if defined(HAVE_TERM_H) #include #elif defined(HAVE_NCURSES_TERM_H) #include #endif /* * When using termcap, include termcap.h on systems that have it. * Otherwise assume that all prototypes are provided by curses.h. */ #if defined(USE_TERMCAP) && defined(HAVE_TERMCAP_H) #include #endif /* * Under Solaris default Curses the output function that tputs takes is * declared to have a char argument. On all other systems and on Solaris * X/Open Curses (Issue 4, Version 2) it expects an int argument (using * c89 or options -I /usr/xpg4/include -L /usr/xpg4/lib -R /usr/xpg4/lib * selects XPG4v2 Curses on Solaris 2.6 and later). * * Similarly, under Mac OS X, the return value of the tputs output * function is declared as void, whereas it is declared as int on * other systems. */ #if defined __sun && defined __SVR4 && !defined _XOPEN_CURSES typedef int TputsRetType; typedef char TputsArgType; /* int tputs(char c, FILE *fp) */ #define TPUTS_RETURNS_VALUE 1 #elif defined(__APPLE__) && defined(__MACH__) typedef void TputsRetType; typedef int TputsArgType; /* void tputs(int c, FILE *fp) */ #define TPUTS_RETURNS_VALUE 0 #else typedef int TputsRetType; typedef int TputsArgType; /* int tputs(int c, FILE *fp) */ #define TPUTS_RETURNS_VALUE 1 #endif /* * Use the above specifications to prototype our tputs callback function. */ static TputsRetType gl_tputs_putchar(TputsArgType c); #endif /* defined(USE_TERMINFO) || defined(USE_TERMCAP) */ /* * If the library is being compiled without filesystem access facilities, * ensure that none of the action functions that normally do access the * filesystem are bound by default, and that it they do get bound, that * they don't do anything. */ #if WITHOUT_FILE_SYSTEM #define HIDE_FILE_SYSTEM #endif /* * POSIX headers. */ #include #include #include /* * Provide typedefs for standard POSIX structures. */ typedef struct sigaction SigAction; typedef struct termios Termios; /* * Which flag is used to select non-blocking I/O with fcntl()? */ #undef NON_BLOCKING_FLAG #if defined(O_NONBLOCK) #define NON_BLOCKING_FLAG (O_NONBLOCK) #elif defined(O_NDELAY) #define NON_BLOCKING_FLAG (O_NDELAY) #endif /* * What value should we give errno if I/O blocks when it shouldn't. */ #undef BLOCKED_ERRNO #if defined(EAGAIN) #define BLOCKED_ERRNO (EAGAIN) #elif defined(EWOULDBLOCK) #define BLOCKED_ERRNO (EWOULDBLOCK) #elif defined(EIO) #define BLOCKED_ERRNO (EIO) #else #define BLOCKED_ERRNO 0 #endif /* * Local headers. */ #ifndef WITHOUT_FILE_SYSTEM #include "pathutil.h" #endif #include "libtecla.h" #include "keytab.h" #include "getline.h" #include "ioutil.h" #include "history.h" #include "freelist.h" #include "stringrp.h" #include "chrqueue.h" #include "cplmatch.h" #ifndef WITHOUT_FILE_SYSTEM #include "expand.h" #endif #include "errmsg.h" /* * Enumerate the available editing styles. */ typedef enum { GL_EMACS_MODE, /* Emacs style editing */ GL_VI_MODE, /* Vi style editing */ GL_NO_EDITOR /* Fall back to the basic OS-provided editing */ } GlEditor; /* * Set the largest key-sequence that can be handled. */ #define GL_KEY_MAX 64 /* * In vi mode, the following datatype is used to implement the * undo command. It records a copy of the input line from before * the command-mode action which edited the input line. */ typedef struct { char *line; /* A historical copy of the input line */ int buff_curpos; /* The historical location of the cursor in */ /* line[] when the line was modified. */ int ntotal; /* The number of characters in line[] */ int saved; /* True once a line has been saved after the */ /* last call to gl_interpret_char(). */ } ViUndo; /* * In vi mode, the following datatype is used to record information * needed by the vi-repeat-change command. */ typedef struct { KtAction action; /* The last action function that made a */ /* change to the line. */ int count; /* The repeat count that was passed to the */ /* above command. */ int input_curpos; /* Whenever vi command mode is entered, the */ /* the position at which it was first left */ /* is recorded here. */ int command_curpos; /* Whenever vi command mode is entered, the */ /* the location of the cursor is recorded */ /* here. */ char input_char; /* Commands that call gl_read_terminal() */ /* record the character here, so that it can */ /* used on repeating the function. */ int saved; /* True if a function has been saved since the */ /* last call to gl_interpret_char(). */ int active; /* True while a function is being repeated. */ } ViRepeat; /* * The following datatype is used to encapsulate information specific * to vi mode. */ typedef struct { ViUndo undo; /* Information needed to implement the vi */ /* undo command. */ ViRepeat repeat; /* Information needed to implement the vi */ /* repeat command. */ int command; /* True in vi command-mode */ int find_forward; /* True if the last character search was in the */ /* forward direction. */ int find_onto; /* True if the last character search left the */ /* on top of the located character, as opposed */ /* to just before or after it. */ char find_char; /* The last character sought, or '\0' if no */ /* searches have been performed yet. */ } ViMode; #ifdef HAVE_SELECT /* * Define a type for recording a file-descriptor callback and its associated * data. */ typedef struct { GlFdEventFn *fn; /* The callback function */ void *data; /* Anonymous data to pass to the callback function */ } GlFdHandler; /* * A list of nodes of the following type is used to record file-activity * event handlers, but only on systems that have the select() system call. */ typedef struct GlFdNode GlFdNode; struct GlFdNode { GlFdNode *next; /* The next in the list of nodes */ int fd; /* The file descriptor being watched */ GlFdHandler rd; /* The callback to call when fd is readable */ GlFdHandler wr; /* The callback to call when fd is writable */ GlFdHandler ur; /* The callback to call when fd has urgent data */ }; /* * Set the number of the above structures to allocate every time that * the freelist of GlFdNode's becomes exhausted. */ #define GLFD_FREELIST_BLOCKING 10 static int gl_call_fd_handler(GetLine *gl, GlFdHandler *gfh, int fd, GlFdEvent event); static int gl_call_timeout_handler(GetLine *gl); #endif /* * Each signal that gl_get_line() traps is described by a list node * of the following type. */ typedef struct GlSignalNode GlSignalNode; struct GlSignalNode { GlSignalNode *next; /* The next signal in the list */ int signo; /* The number of the signal */ sigset_t proc_mask; /* A process mask which only includes signo */ SigAction original; /* The signal disposition of the calling program */ /* for this signal. */ unsigned flags; /* A bitwise union of GlSignalFlags enumerators */ GlAfterSignal after; /* What to do after the signal has been handled */ int errno_value; /* What to set errno to */ }; /* * Set the number of the above structures to allocate every time that * the freelist of GlSignalNode's becomes exhausted. */ #define GLS_FREELIST_BLOCKING 30 /* * Completion handlers and their callback data are recorded in * nodes of the following type. */ typedef struct GlCplCallback GlCplCallback; struct GlCplCallback { CplMatchFn *fn; /* The completion callback function */ void *data; /* Arbitrary callback data */ }; /* * The following function is used as the default completion handler when * the filesystem is to be hidden. It simply reports no completions. */ #ifdef HIDE_FILE_SYSTEM static CPL_MATCH_FN(gl_no_completions); #endif /* * Specify how many GlCplCallback nodes are added to the GlCplCallback freelist * whenever it becomes exhausted. */ #define GL_CPL_FREELIST_BLOCKING 10 /* * External action functions and their callback data are recorded in * nodes of the following type. */ typedef struct GlExternalAction GlExternalAction; struct GlExternalAction { GlActionFn *fn; /* The function which implements the action */ void *data; /* Arbitrary callback data */ }; /* * Specify how many GlExternalAction nodes are added to the * GlExternalAction freelist whenever it becomes exhausted. */ #define GL_EXT_ACT_FREELIST_BLOCKING 10 /* * Define the contents of the GetLine object. * Note that the typedef for this object can be found in libtecla.h. */ struct GetLine { ErrMsg *err; /* The error-reporting buffer */ GlHistory *glh; /* The line-history buffer */ WordCompletion *cpl; /* String completion resource object */ GlCplCallback cplfn; /* The completion callback */ #ifndef WITHOUT_FILE_SYSTEM ExpandFile *ef; /* ~user/, $envvar and wildcard expansion */ /* resource object. */ #endif StringGroup *capmem; /* Memory for recording terminal capability */ /* strings. */ GlCharQueue *cq; /* The terminal output character queue */ int input_fd; /* The file descriptor to read on */ int output_fd; /* The file descriptor to write to */ FILE *input_fp; /* A stream wrapper around input_fd */ FILE *output_fp; /* A stream wrapper around output_fd */ FILE *file_fp; /* When input is being temporarily taken from */ /* a file, this is its file-pointer. Otherwise */ /* it is NULL. */ char *term; /* The terminal type specified on the last call */ /* to gl_change_terminal(). */ int is_term; /* True if stdin is a terminal */ GlWriteFn *flush_fn; /* The function to call to write to the terminal */ GlIOMode io_mode; /* The I/O mode established by gl_io_mode() */ int raw_mode; /* True while the terminal is in raw mode */ GlPendingIO pending_io; /* The type of I/O that is currently pending */ GlReturnStatus rtn_status; /* The reason why gl_get_line() returned */ int rtn_errno; /* THe value of errno associated with rtn_status */ size_t linelen; /* The max number of characters per line */ char *line; /* A line-input buffer of allocated size */ /* linelen+2. The extra 2 characters are */ /* reserved for "\n\0". */ char *cutbuf; /* A cut-buffer of the same size as line[] */ char *prompt; /* The current prompt string */ int prompt_len; /* The length of the prompt string */ int prompt_changed; /* True after a callback changes the prompt */ int prompt_style; /* How the prompt string is displayed */ FreeList *cpl_mem; /* Memory for GlCplCallback objects */ FreeList *ext_act_mem; /* Memory for GlExternalAction objects */ FreeList *sig_mem; /* Memory for nodes of the signal list */ GlSignalNode *sigs; /* The head of the list of signals */ int signals_masked; /* True between calls to gl_mask_signals() and */ /* gl_unmask_signals() */ int signals_overriden; /* True between calls to gl_override_signals() */ /* and gl_restore_signals() */ sigset_t all_signal_set; /* The set of all signals that we are trapping */ sigset_t old_signal_set; /* The set of blocked signals on entry to */ /* gl_get_line(). */ sigset_t use_signal_set; /* The subset of all_signal_set to unblock */ /* while waiting for key-strokes */ Termios oldattr; /* Saved terminal attributes. */ KeyTab *bindings; /* A table of key-bindings */ int ntotal; /* The number of characters in gl->line[] */ int buff_curpos; /* The cursor position within gl->line[] */ int term_curpos; /* The cursor position on the terminal */ int term_len; /* The number of terminal characters used to */ /* display the current input line. */ int buff_mark; /* A marker location in the buffer */ int insert_curpos; /* The cursor position at start of insert */ int insert; /* True in insert mode */ int number; /* If >= 0, a numeric argument is being read */ int endline; /* True to tell gl_get_input_line() to return */ /* the current contents of gl->line[] */ int displayed; /* True if an input line is currently displayed */ int redisplay; /* If true, the input line will be redrawn */ /* either after the current action function */ /* returns, or when gl_get_input_line() */ /* is next called. */ int postpone; /* _gl_normal_io() sets this flag, to */ /* postpone any redisplays until */ /* is next called, to resume line editing. */ char keybuf[GL_KEY_MAX+1]; /* A buffer of currently unprocessed key presses */ int nbuf; /* The number of characters in keybuf[] */ int nread; /* The number of characters read from keybuf[] */ KtAction current_action; /* The action function that is being invoked */ int current_count; /* The repeat count passed to */ /* current_acction.fn() */ GlhLineID preload_id; /* When not zero, this should be the ID of a */ /* line in the history buffer for potential */ /* recall. */ int preload_history; /* If true, preload the above history line when */ /* gl_get_input_line() is next called. */ long keyseq_count; /* The number of key sequences entered by the */ /* the user since new_GetLine() was called. */ long last_search; /* The value of keyseq_count during the last */ /* history search operation. */ GlEditor editor; /* The style of editing, (eg. vi or emacs) */ int silence_bell; /* True if gl_ring_bell() should do nothing. */ int automatic_history; /* True to automatically archive entered lines */ /* in the history list. */ ViMode vi; /* Parameters used when editing in vi mode */ const char *left; /* The string that moves the cursor 1 character */ /* left. */ const char *right; /* The string that moves the cursor 1 character */ /* right. */ const char *up; /* The string that moves the cursor 1 character */ /* up. */ const char *down; /* The string that moves the cursor 1 character */ /* down. */ const char *home; /* The string that moves the cursor home */ const char *bol; /* Move cursor to beginning of line */ const char *clear_eol; /* The string that clears from the cursor to */ /* the end of the line. */ const char *clear_eod; /* The string that clears from the cursor to */ /* the end of the display. */ const char *u_arrow; /* The string returned by the up-arrow key */ const char *d_arrow; /* The string returned by the down-arrow key */ const char *l_arrow; /* The string returned by the left-arrow key */ const char *r_arrow; /* The string returned by the right-arrow key */ const char *sound_bell; /* The string needed to ring the terminal bell */ const char *bold; /* Switch to the bold font */ const char *underline; /* Underline subsequent characters */ const char *standout; /* Turn on standout mode */ const char *dim; /* Switch to a dim font */ const char *reverse; /* Turn on reverse video */ const char *blink; /* Switch to a blinking font */ const char *text_attr_off; /* Turn off all text attributes */ int nline; /* The height of the terminal in lines */ int ncolumn; /* The width of the terminal in columns */ #ifdef USE_TERMCAP char *tgetent_buf; /* The buffer that is used by tgetent() to */ /* store a terminal description. */ char *tgetstr_buf; /* The buffer that is used by tgetstr() to */ /* store terminal capabilities. */ #endif #ifdef USE_TERMINFO const char *left_n; /* The parameter string that moves the cursor */ /* n characters left. */ const char *right_n; /* The parameter string that moves the cursor */ /* n characters right. */ #endif char *app_file; /* The pathname of the application-specific */ /* .teclarc configuration file, or NULL. */ char *user_file; /* The pathname of the user-specific */ /* .teclarc configuration file, or NULL. */ int configured; /* True as soon as any teclarc configuration */ /* file has been read. */ int echo; /* True to display the line as it is being */ /* entered. If 0, only the prompt will be */ /* displayed, and the line will not be */ /* archived in the history list. */ int last_signal; /* The last signal that was caught by */ /* the last call to gl_get_line(), or -1 */ /* if no signal has been caught yet. */ #ifdef HAVE_SELECT FreeList *fd_node_mem; /* A freelist of GlFdNode structures */ GlFdNode *fd_nodes; /* The list of fd event descriptions */ fd_set rfds; /* The set of fds to watch for readability */ fd_set wfds; /* The set of fds to watch for writability */ fd_set ufds; /* The set of fds to watch for urgent data */ int max_fd; /* The maximum file-descriptor being watched */ struct { /* Inactivity timeout related data */ struct timeval dt; /* The inactivity timeout when timer.fn() */ /* isn't 0 */ GlTimeoutFn *fn; /* The application callback to call when */ /* the inactivity timer expires, or 0 if */ /* timeouts are not required. */ void *data; /* Application provided data to be passed to */ /* timer.fn(). */ } timer; #endif }; /* * Define the max amount of space needed to store a termcap terminal * description. Unfortunately this has to be done by guesswork, so * there is the potential for buffer overflows if we guess too small. * Fortunately termcap has been replaced by terminfo on most * platforms, and with terminfo this isn't an issue. The value that I * am using here is the conventional value, as recommended by certain * web references. */ #ifdef USE_TERMCAP #define TERMCAP_BUF_SIZE 2048 #endif /* * Set the size of the string segments used to store terminal capability * strings. */ #define CAPMEM_SEGMENT_SIZE 512 /* * If no terminal size information is available, substitute the * following vt100 default sizes. */ #define GL_DEF_NLINE 24 #define GL_DEF_NCOLUMN 80 /* * Enumerate the attributes needed to classify different types of * signals. These attributes reflect the standard default * characteristics of these signals (according to Richard Steven's * Advanced Programming in the UNIX Environment). Note that these values * are all powers of 2, so that they can be combined in a bitwise union. */ typedef enum { GLSA_TERM=1, /* A signal that terminates processes */ GLSA_SUSP=2, /* A signal that suspends processes */ GLSA_CONT=4, /* A signal that is sent when suspended processes resume */ GLSA_IGN=8, /* A signal that is ignored */ GLSA_CORE=16, /* A signal that generates a core dump */ GLSA_HARD=32, /* A signal generated by a hardware exception */ GLSA_SIZE=64 /* A signal indicating terminal size changes */ } GlSigAttr; /* * List the signals that we need to catch. In general these are * those that by default terminate or suspend the process, since * in such cases we need to restore terminal settings. */ static const struct GlDefSignal { int signo; /* The number of the signal */ unsigned flags; /* A bitwise union of GlSignalFlags enumerators */ GlAfterSignal after; /* What to do after the signal has been delivered */ int attr; /* The default attributes of this signal, expressed */ /* as a bitwise union of GlSigAttr enumerators */ int errno_value; /* What to set errno to */ } gl_signal_list[] = { {SIGABRT, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM|GLSA_CORE, EINTR}, {SIGALRM, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM, 0}, {SIGCONT, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_CONT|GLSA_IGN, 0}, #if defined(SIGHUP) #ifdef ENOTTY {SIGHUP, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, ENOTTY}, #else {SIGHUP, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EINTR}, #endif #endif {SIGINT, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EINTR}, #if defined(SIGPIPE) #ifdef EPIPE {SIGPIPE, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EPIPE}, #else {SIGPIPE, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EINTR}, #endif #endif #ifdef SIGPOLL {SIGPOLL, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EINTR}, #endif #ifdef SIGPWR {SIGPWR, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_IGN, 0}, #endif #ifdef SIGQUIT {SIGQUIT, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM|GLSA_CORE, EINTR}, #endif {SIGTERM, GLS_SUSPEND_INPUT, GLS_ABORT, GLSA_TERM, EINTR}, #ifdef SIGTSTP {SIGTSTP, GLS_SUSPEND_INPUT, GLS_CONTINUE, GLSA_SUSP, 0}, #endif #ifdef SIGTTIN {SIGTTIN, GLS_SUSPEND_INPUT, GLS_CONTINUE, GLSA_SUSP, 0}, #endif #ifdef SIGTTOU {SIGTTOU, GLS_SUSPEND_INPUT, GLS_CONTINUE, GLSA_SUSP, 0}, #endif #ifdef SIGUSR1 {SIGUSR1, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM, 0}, #endif #ifdef SIGUSR2 {SIGUSR2, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM, 0}, #endif #ifdef SIGVTALRM {SIGVTALRM, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM, 0}, #endif #ifdef SIGWINCH {SIGWINCH, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_SIZE|GLSA_IGN, 0}, #endif #ifdef SIGXCPU {SIGXCPU, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM|GLSA_CORE, 0}, #endif #ifdef SIGXFSZ {SIGXFSZ, GLS_RESTORE_ENV, GLS_CONTINUE, GLSA_TERM|GLSA_CORE, 0}, #endif }; /* * Define file-scope variables for use in signal handlers. */ static volatile sig_atomic_t gl_pending_signal = -1; static sigjmp_buf gl_setjmp_buffer; static void gl_signal_handler(int signo); static int gl_check_caught_signal(GetLine *gl); /* * Respond to an externally caught process suspension or * termination signal. */ static void gl_suspend_process(int signo, GetLine *gl, int ngl); /* Return the default attributes of a given signal */ static int gl_classify_signal(int signo); /* * Unfortunately both terminfo and termcap require one to use the tputs() * function to output terminal control characters, and this function * doesn't allow one to specify a file stream. As a result, the following * file-scope variable is used to pass the current output file stream. * This is bad, but there doesn't seem to be any alternative. */ static GetLine *tputs_gl = NULL; /* * Define a tab to be a string of 8 spaces. */ #define TAB_WIDTH 8 /* * Lookup the current size of the terminal. */ static void gl_query_size(GetLine *gl, int *ncolumn, int *nline); /* * Getline calls this to temporarily override certain signal handlers * of the calling program. */ static int gl_override_signal_handlers(GetLine *gl); /* * Getline calls this to restore the signal handlers of the calling * program. */ static int gl_restore_signal_handlers(GetLine *gl); /* * Temporarily block the delivery of all signals that gl_get_line() * is currently configured to trap. */ static int gl_mask_signals(GetLine *gl, sigset_t *oldset); /* * Restore the process signal mask that was overriden by a previous * call to gl_mask_signals(). */ static int gl_unmask_signals(GetLine *gl, sigset_t *oldset); /* * Unblock the signals that gl_get_line() has been configured to catch. */ static int gl_catch_signals(GetLine *gl); /* * Return the set of all trappable signals. */ static void gl_list_trappable_signals(sigset_t *signals); /* * Put the terminal into raw input mode, after saving the original * terminal attributes in gl->oldattr. */ static int gl_raw_terminal_mode(GetLine *gl); /* * Restore the terminal attributes from gl->oldattr. */ static int gl_restore_terminal_attributes(GetLine *gl); /* * Switch to non-blocking I/O if possible. */ static int gl_nonblocking_io(GetLine *gl, int fd); /* * Switch to blocking I/O if possible. */ static int gl_blocking_io(GetLine *gl, int fd); /* * Read a line from the user in raw mode. */ static int gl_get_input_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); /* * Query the user for a single character. */ static int gl_get_query_char(GetLine *gl, const char *prompt, int defchar); /* * Read input from a non-interactive input stream. */ static int gl_read_stream_line(GetLine *gl); /* * Read a single character from a non-interactive input stream. */ static int gl_read_stream_char(GetLine *gl); /* * Prepare to edit a new line. */ static int gl_present_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); /* * Reset all line-editing parameters for a new input line. */ static void gl_reset_editor(GetLine *gl); /* * Handle the receipt of the potential start of a new key-sequence from * the user. */ static int gl_interpret_char(GetLine *gl, char c); /* * Bind a single control or meta character to an action. */ static int gl_bind_control_char(GetLine *gl, KtBinder binder, char c, const char *action); /* * Set up terminal-specific key bindings. */ static int gl_bind_terminal_keys(GetLine *gl); /* * Lookup terminal control string and size information. */ static int gl_control_strings(GetLine *gl, const char *term); /* * Wrappers around the terminfo and termcap functions that lookup * strings in the terminal information databases. */ #ifdef USE_TERMINFO static const char *gl_tigetstr(GetLine *gl, const char *name); #elif defined(USE_TERMCAP) static const char *gl_tgetstr(GetLine *gl, const char *name, char **bufptr); #endif /* * Output a binary string directly to the terminal. */ static int gl_print_raw_string(GetLine *gl, int buffered, const char *string, int n); /* * Print an informational message, starting and finishing on new lines. * After the list of strings to be printed, the last argument MUST be * GL_END_INFO. */ static int gl_print_info(GetLine *gl, ...); #define GL_END_INFO ((const char *)0) /* * Start a newline and place the cursor at its start. */ static int gl_start_newline(GetLine *gl, int buffered); /* * Output a terminal control sequence. */ static int gl_print_control_sequence(GetLine *gl, int nline, const char *string); /* * Output a character or string to the terminal after converting tabs * to spaces and control characters to a caret followed by the modified * character. */ static int gl_print_char(GetLine *gl, char c, char pad); static int gl_print_string(GetLine *gl, const char *string, char pad); /* * Delete nc characters starting from the one under the cursor. * Optionally copy the deleted characters to the cut buffer. */ static int gl_delete_chars(GetLine *gl, int nc, int cut); /* * Add a character to the line buffer at the current cursor position, * inserting or overwriting according the current mode. */ static int gl_add_char_to_line(GetLine *gl, char c); /* * Insert/append a string to the line buffer and terminal at the current * cursor position. */ static int gl_add_string_to_line(GetLine *gl, const char *s); /* * Record a new character in the input-line buffer. */ static int gl_buffer_char(GetLine *gl, char c, int bufpos); /* * Record a string in the input-line buffer. */ static int gl_buffer_string(GetLine *gl, const char *s, int n, int bufpos); /* * Make way to insert a string in the input-line buffer. */ static int gl_make_gap_in_buffer(GetLine *gl, int start, int n); /* * Remove characters from the input-line buffer, and move any characters * that followed them to the start of the vacated space. */ static void gl_remove_from_buffer(GetLine *gl, int start, int n); /* * Terminate the input-line buffer after a specified number of characters. */ static int gl_truncate_buffer(GetLine *gl, int n); /* * Delete the displayed part of the input line that follows the current * terminal cursor position. */ static int gl_truncate_display(GetLine *gl); /* * Accomodate changes to the contents of the input line buffer * that weren't made by the above gl_*buffer functions. */ static void gl_update_buffer(GetLine *gl); /* * Read a single character from the terminal. */ static int gl_read_terminal(GetLine *gl, int keep, char *c); /* * Discard processed characters from the key-press lookahead buffer. */ static void gl_discard_chars(GetLine *gl, int nused); /* * Move the terminal cursor n positions to the left or right. */ static int gl_terminal_move_cursor(GetLine *gl, int n); /* * Move the terminal cursor to a given position. */ static int gl_set_term_curpos(GetLine *gl, int term_curpos); /* * Set the position of the cursor both in the line input buffer and on the * terminal. */ static int gl_place_cursor(GetLine *gl, int buff_curpos); /* * How many characters are needed to write a number as an octal string? */ static int gl_octal_width(unsigned num); /* * Return the number of spaces needed to display a tab character at * a given location of the terminal. */ static int gl_displayed_tab_width(GetLine *gl, int term_curpos); /* * Return the number of terminal characters needed to display a * given raw character. */ static int gl_displayed_char_width(GetLine *gl, char c, int term_curpos); /* * Return the number of terminal characters needed to display a * given substring. */ static int gl_displayed_string_width(GetLine *gl, const char *string, int nc, int term_curpos); /* * Return non-zero if 'c' is to be considered part of a word. */ static int gl_is_word_char(int c); /* * Read a tecla configuration file. */ static int _gl_read_config_file(GetLine *gl, const char *filename, KtBinder who); /* * Read a tecla configuration string. */ static int _gl_read_config_string(GetLine *gl, const char *buffer, KtBinder who); /* * Define the callback function used by _gl_parse_config_line() to * read the next character of a configuration stream. */ #define GLC_GETC_FN(fn) int (fn)(void *stream) typedef GLC_GETC_FN(GlcGetcFn); static GLC_GETC_FN(glc_file_getc); /* Read from a file */ static GLC_GETC_FN(glc_buff_getc); /* Read from a string */ /* * Parse a single configuration command line. */ static int _gl_parse_config_line(GetLine *gl, void *stream, GlcGetcFn *getc_fn, const char *origin, KtBinder who, int *lineno); static int gl_report_config_error(GetLine *gl, const char *origin, int lineno, const char *errmsg); /* * Bind the actual arrow key bindings to match those of the symbolic * arrow-key bindings. */ static int _gl_bind_arrow_keys(GetLine *gl); /* * Copy the binding of the specified symbolic arrow-key binding to * the terminal specific, and default arrow-key key-sequences. */ static int _gl_rebind_arrow_key(GetLine *gl, const char *name, const char *term_seq, const char *def_seq1, const char *def_seq2); /* * After the gl_read_from_file() action has been used to tell gl_get_line() * to temporarily read input from a file, gl_revert_input() arranges * for input to be reverted to the input stream last registered with * gl_change_terminal(). */ static void gl_revert_input(GetLine *gl); /* * Flush unwritten characters to the terminal. */ static int gl_flush_output(GetLine *gl); /* * The callback through which all terminal output is routed. * This simply appends characters to a queue buffer, which is * subsequently flushed to the output channel by gl_flush_output(). */ static GL_WRITE_FN(gl_write_fn); /* * The callback function which the output character queue object * calls to transfer characters to the output channel. */ static GL_WRITE_FN(gl_flush_terminal); /* * Enumerate the possible return statuses of gl_read_input(). */ typedef enum { GL_READ_OK, /* A character was read successfully */ GL_READ_ERROR, /* A read-error occurred */ GL_READ_BLOCKED, /* The read would have blocked the caller */ GL_READ_EOF /* The end of the current input file was reached */ } GlReadStatus; static GlReadStatus gl_read_input(GetLine *gl, char *c); /* * Private functions of gl_read_input(). */ static int gl_event_handler(GetLine *gl, int fd); static GlReadStatus gl_read_unmasked(GetLine *gl, int fd, char *c); /* * A private function of gl_tty_signals(). */ static int gl_set_tty_signal(int signo, void (*handler)(int)); /* * Change the editor style being emulated. */ static int gl_change_editor(GetLine *gl, GlEditor editor); /* * Searching in a given direction, return the index of a given (or * read) character in the input line, or the character that precedes * it in the specified search direction. Return -1 if not found. */ static int gl_find_char(GetLine *gl, int count, int forward, int onto, char c); /* * Return the buffer index of the nth word ending after the cursor. */ static int gl_nth_word_end_forward(GetLine *gl, int n); /* * Return the buffer index of the nth word start after the cursor. */ static int gl_nth_word_start_forward(GetLine *gl, int n); /* * Return the buffer index of the nth word start before the cursor. */ static int gl_nth_word_start_backward(GetLine *gl, int n); /* * When called when vi command mode is enabled, this function saves the * current line and cursor position for potential restoration later * by the vi undo command. */ static void gl_save_for_undo(GetLine *gl); /* * If in vi mode, switch to vi command mode. */ static void gl_vi_command_mode(GetLine *gl); /* * In vi mode this is used to delete up to or onto a given or read * character in the input line. Also switch to insert mode if requested * after the deletion. */ static int gl_delete_find(GetLine *gl, int count, char c, int forward, int onto, int change); /* * Copy the characters between the cursor and the count'th instance of * a specified (or read) character in the input line, into the cut buffer. */ static int gl_copy_find(GetLine *gl, int count, char c, int forward, int onto); /* * Return the line index of the parenthesis that either matches the one under * the cursor, or not over a parenthesis character, the index of the next * close parenthesis. Return -1 if not found. */ static int gl_index_of_matching_paren(GetLine *gl); /* * Replace a malloc'd string (or NULL), with another malloc'd copy of * a string (or NULL). */ static int gl_record_string(char **sptr, const char *string); /* * Enumerate text display attributes as powers of two, suitable for * use in a bit-mask. */ typedef enum { GL_TXT_STANDOUT=1, /* Display text highlighted */ GL_TXT_UNDERLINE=2, /* Display text underlined */ GL_TXT_REVERSE=4, /* Display text with reverse video */ GL_TXT_BLINK=8, /* Display blinking text */ GL_TXT_DIM=16, /* Display text in a dim font */ GL_TXT_BOLD=32 /* Display text using a bold font */ } GlTextAttr; /* * Display the prompt regardless of the current visibility mode. */ static int gl_display_prompt(GetLine *gl); /* * Return the number of characters used by the prompt on the terminal. */ static int gl_displayed_prompt_width(GetLine *gl); /* * Prepare to return the current input line to the caller of gl_get_line(). */ static int gl_line_ended(GetLine *gl, int newline_char); /* * Arrange for the input line to be redisplayed when the current contents * of the output queue have been flushed. */ static void gl_queue_redisplay(GetLine *gl); /* * Erase the displayed representation of the input line, without * touching the buffered copy. */ static int gl_erase_line(GetLine *gl); /* * This function is called whenever the input line has been erased. */ static void gl_line_erased(GetLine *gl); /* * Arrange for the current input line to be discarded. */ void _gl_abandon_line(GetLine *gl); /* * The following are private internally callable versions of pertinent * public functions. Unlike their public wrapper functions, they don't * block signals while running, and assume that their arguments are valid. * They are designed to be called from places where signals are already * blocked, and where simple sanity checks have already been applied to * their arguments. */ static char *_gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); static int _gl_query_char(GetLine *gl, const char *prompt, char defchar); static int _gl_read_char(GetLine *gl); static int _gl_update_size(GetLine *gl); /* * Redraw the current input line to account for a change in the terminal * size. Also install the new size in gl. */ static int gl_handle_tty_resize(GetLine *gl, int ncolumn, int nline); static int _gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term); static int _gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file); static int _gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines); static int _gl_load_history(GetLine *gl, const char *filename, const char *comment); static int _gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data); static void _gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline, GlTerminalSize *size); static void _gl_replace_prompt(GetLine *gl, const char *prompt); static int _gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value); static int _gl_raw_io(GetLine *gl, int redisplay); static int _gl_normal_io(GetLine *gl); static int _gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq); static int _gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq); static int _gl_io_mode(GetLine *gl, GlIOMode mode); static int _gl_set_term_size(GetLine *gl, int ncolumn, int nline); static int _gl_append_history(GetLine *gl, const char *line); /* * Reset the completion status and associated errno value in * gl->rtn_status and gl->rtn_errno. */ static void gl_clear_status(GetLine *gl); /* * Record a completion status, unless a previous abnormal completion * status has already been recorded for the current call. */ static void gl_record_status(GetLine *gl, GlReturnStatus rtn_status, int rtn_errno); /* * Set the maximum length of a line in a user's tecla configuration * file (not counting comments). */ #define GL_CONF_BUFLEN 100 /* * Set the maximum number of arguments supported by individual commands * in tecla configuration files. */ #define GL_CONF_MAXARG 10 /* * Prototype the available action functions. */ static KT_KEY_FN(gl_user_interrupt); static KT_KEY_FN(gl_abort); static KT_KEY_FN(gl_suspend); static KT_KEY_FN(gl_stop_output); static KT_KEY_FN(gl_start_output); static KT_KEY_FN(gl_literal_next); static KT_KEY_FN(gl_cursor_left); static KT_KEY_FN(gl_cursor_right); static KT_KEY_FN(gl_insert_mode); static KT_KEY_FN(gl_beginning_of_line); static KT_KEY_FN(gl_end_of_line); static KT_KEY_FN(gl_delete_line); static KT_KEY_FN(gl_kill_line); static KT_KEY_FN(gl_forward_word); static KT_KEY_FN(gl_backward_word); static KT_KEY_FN(gl_forward_delete_char); static KT_KEY_FN(gl_backward_delete_char); static KT_KEY_FN(gl_forward_delete_word); static KT_KEY_FN(gl_backward_delete_word); static KT_KEY_FN(gl_delete_refind); static KT_KEY_FN(gl_delete_invert_refind); static KT_KEY_FN(gl_delete_to_column); static KT_KEY_FN(gl_delete_to_parenthesis); static KT_KEY_FN(gl_forward_delete_find); static KT_KEY_FN(gl_backward_delete_find); static KT_KEY_FN(gl_forward_delete_to); static KT_KEY_FN(gl_backward_delete_to); static KT_KEY_FN(gl_upcase_word); static KT_KEY_FN(gl_downcase_word); static KT_KEY_FN(gl_capitalize_word); static KT_KEY_FN(gl_redisplay); static KT_KEY_FN(gl_clear_screen); static KT_KEY_FN(gl_transpose_chars); static KT_KEY_FN(gl_set_mark); static KT_KEY_FN(gl_exchange_point_and_mark); static KT_KEY_FN(gl_kill_region); static KT_KEY_FN(gl_copy_region_as_kill); static KT_KEY_FN(gl_yank); static KT_KEY_FN(gl_up_history); static KT_KEY_FN(gl_down_history); static KT_KEY_FN(gl_history_search_backward); static KT_KEY_FN(gl_history_re_search_backward); static KT_KEY_FN(gl_history_search_forward); static KT_KEY_FN(gl_history_re_search_forward); static KT_KEY_FN(gl_complete_word); #ifndef HIDE_FILE_SYSTEM static KT_KEY_FN(gl_expand_filename); static KT_KEY_FN(gl_read_from_file); static KT_KEY_FN(gl_read_init_files); static KT_KEY_FN(gl_list_glob); #endif static KT_KEY_FN(gl_del_char_or_list_or_eof); static KT_KEY_FN(gl_list_or_eof); static KT_KEY_FN(gl_beginning_of_history); static KT_KEY_FN(gl_end_of_history); static KT_KEY_FN(gl_digit_argument); static KT_KEY_FN(gl_newline); static KT_KEY_FN(gl_repeat_history); static KT_KEY_FN(gl_vi_insert); static KT_KEY_FN(gl_vi_overwrite); static KT_KEY_FN(gl_change_case); static KT_KEY_FN(gl_vi_insert_at_bol); static KT_KEY_FN(gl_vi_append_at_eol); static KT_KEY_FN(gl_vi_append); static KT_KEY_FN(gl_backward_kill_line); static KT_KEY_FN(gl_goto_column); static KT_KEY_FN(gl_forward_to_word); static KT_KEY_FN(gl_vi_replace_char); static KT_KEY_FN(gl_vi_change_rest_of_line); static KT_KEY_FN(gl_vi_change_line); static KT_KEY_FN(gl_vi_change_to_bol); static KT_KEY_FN(gl_vi_change_refind); static KT_KEY_FN(gl_vi_change_invert_refind); static KT_KEY_FN(gl_vi_change_to_column); static KT_KEY_FN(gl_vi_change_to_parenthesis); static KT_KEY_FN(gl_vi_forward_change_word); static KT_KEY_FN(gl_vi_backward_change_word); static KT_KEY_FN(gl_vi_forward_change_find); static KT_KEY_FN(gl_vi_backward_change_find); static KT_KEY_FN(gl_vi_forward_change_to); static KT_KEY_FN(gl_vi_backward_change_to); static KT_KEY_FN(gl_vi_forward_change_char); static KT_KEY_FN(gl_vi_backward_change_char); static KT_KEY_FN(gl_forward_copy_char); static KT_KEY_FN(gl_backward_copy_char); static KT_KEY_FN(gl_forward_find_char); static KT_KEY_FN(gl_backward_find_char); static KT_KEY_FN(gl_forward_to_char); static KT_KEY_FN(gl_backward_to_char); static KT_KEY_FN(gl_repeat_find_char); static KT_KEY_FN(gl_invert_refind_char); static KT_KEY_FN(gl_append_yank); static KT_KEY_FN(gl_backward_copy_word); static KT_KEY_FN(gl_forward_copy_word); static KT_KEY_FN(gl_copy_to_bol); static KT_KEY_FN(gl_copy_refind); static KT_KEY_FN(gl_copy_invert_refind); static KT_KEY_FN(gl_copy_to_column); static KT_KEY_FN(gl_copy_to_parenthesis); static KT_KEY_FN(gl_copy_rest_of_line); static KT_KEY_FN(gl_copy_line); static KT_KEY_FN(gl_backward_copy_find); static KT_KEY_FN(gl_forward_copy_find); static KT_KEY_FN(gl_backward_copy_to); static KT_KEY_FN(gl_forward_copy_to); static KT_KEY_FN(gl_vi_undo); static KT_KEY_FN(gl_emacs_editing_mode); static KT_KEY_FN(gl_vi_editing_mode); static KT_KEY_FN(gl_ring_bell); static KT_KEY_FN(gl_vi_repeat_change); static KT_KEY_FN(gl_find_parenthesis); static KT_KEY_FN(gl_list_history); static KT_KEY_FN(gl_list_completions); static KT_KEY_FN(gl_run_external_action); /* * Name the available action functions. */ static const struct {const char *name; KT_KEY_FN(*fn);} gl_actions[] = { {"user-interrupt", gl_user_interrupt}, {"abort", gl_abort}, {"suspend", gl_suspend}, {"stop-output", gl_stop_output}, {"start-output", gl_start_output}, {"literal-next", gl_literal_next}, {"cursor-right", gl_cursor_right}, {"cursor-left", gl_cursor_left}, {"insert-mode", gl_insert_mode}, {"beginning-of-line", gl_beginning_of_line}, {"end-of-line", gl_end_of_line}, {"delete-line", gl_delete_line}, {"kill-line", gl_kill_line}, {"forward-word", gl_forward_word}, {"backward-word", gl_backward_word}, {"forward-delete-char", gl_forward_delete_char}, {"backward-delete-char", gl_backward_delete_char}, {"forward-delete-word", gl_forward_delete_word}, {"backward-delete-word", gl_backward_delete_word}, {"delete-refind", gl_delete_refind}, {"delete-invert-refind", gl_delete_invert_refind}, {"delete-to-column", gl_delete_to_column}, {"delete-to-parenthesis", gl_delete_to_parenthesis}, {"forward-delete-find", gl_forward_delete_find}, {"backward-delete-find", gl_backward_delete_find}, {"forward-delete-to", gl_forward_delete_to}, {"backward-delete-to", gl_backward_delete_to}, {"upcase-word", gl_upcase_word}, {"downcase-word", gl_downcase_word}, {"capitalize-word", gl_capitalize_word}, {"redisplay", gl_redisplay}, {"clear-screen", gl_clear_screen}, {"transpose-chars", gl_transpose_chars}, {"set-mark", gl_set_mark}, {"exchange-point-and-mark", gl_exchange_point_and_mark}, {"kill-region", gl_kill_region}, {"copy-region-as-kill", gl_copy_region_as_kill}, {"yank", gl_yank}, {"up-history", gl_up_history}, {"down-history", gl_down_history}, {"history-search-backward", gl_history_search_backward}, {"history-re-search-backward", gl_history_re_search_backward}, {"history-search-forward", gl_history_search_forward}, {"history-re-search-forward", gl_history_re_search_forward}, {"complete-word", gl_complete_word}, #ifndef HIDE_FILE_SYSTEM {"expand-filename", gl_expand_filename}, {"read-from-file", gl_read_from_file}, {"read-init-files", gl_read_init_files}, {"list-glob", gl_list_glob}, #endif {"del-char-or-list-or-eof", gl_del_char_or_list_or_eof}, {"beginning-of-history", gl_beginning_of_history}, {"end-of-history", gl_end_of_history}, {"digit-argument", gl_digit_argument}, {"newline", gl_newline}, {"repeat-history", gl_repeat_history}, {"vi-insert", gl_vi_insert}, {"vi-overwrite", gl_vi_overwrite}, {"vi-insert-at-bol", gl_vi_insert_at_bol}, {"vi-append-at-eol", gl_vi_append_at_eol}, {"vi-append", gl_vi_append}, {"change-case", gl_change_case}, {"backward-kill-line", gl_backward_kill_line}, {"goto-column", gl_goto_column}, {"forward-to-word", gl_forward_to_word}, {"vi-replace-char", gl_vi_replace_char}, {"vi-change-rest-of-line", gl_vi_change_rest_of_line}, {"vi-change-line", gl_vi_change_line}, {"vi-change-to-bol", gl_vi_change_to_bol}, {"vi-change-refind", gl_vi_change_refind}, {"vi-change-invert-refind", gl_vi_change_invert_refind}, {"vi-change-to-column", gl_vi_change_to_column}, {"vi-change-to-parenthesis", gl_vi_change_to_parenthesis}, {"forward-copy-char", gl_forward_copy_char}, {"backward-copy-char", gl_backward_copy_char}, {"forward-find-char", gl_forward_find_char}, {"backward-find-char", gl_backward_find_char}, {"forward-to-char", gl_forward_to_char}, {"backward-to-char", gl_backward_to_char}, {"repeat-find-char", gl_repeat_find_char}, {"invert-refind-char", gl_invert_refind_char}, {"append-yank", gl_append_yank}, {"backward-copy-word", gl_backward_copy_word}, {"forward-copy-word", gl_forward_copy_word}, {"copy-to-bol", gl_copy_to_bol}, {"copy-refind", gl_copy_refind}, {"copy-invert-refind", gl_copy_invert_refind}, {"copy-to-column", gl_copy_to_column}, {"copy-to-parenthesis", gl_copy_to_parenthesis}, {"copy-rest-of-line", gl_copy_rest_of_line}, {"copy-line", gl_copy_line}, {"backward-copy-find", gl_backward_copy_find}, {"forward-copy-find", gl_forward_copy_find}, {"backward-copy-to", gl_backward_copy_to}, {"forward-copy-to", gl_forward_copy_to}, {"list-or-eof", gl_list_or_eof}, {"vi-undo", gl_vi_undo}, {"vi-backward-change-word", gl_vi_backward_change_word}, {"vi-forward-change-word", gl_vi_forward_change_word}, {"vi-backward-change-find", gl_vi_backward_change_find}, {"vi-forward-change-find", gl_vi_forward_change_find}, {"vi-backward-change-to", gl_vi_backward_change_to}, {"vi-forward-change-to", gl_vi_forward_change_to}, {"vi-backward-change-char", gl_vi_backward_change_char}, {"vi-forward-change-char", gl_vi_forward_change_char}, {"emacs-mode", gl_emacs_editing_mode}, {"vi-mode", gl_vi_editing_mode}, {"ring-bell", gl_ring_bell}, {"vi-repeat-change", gl_vi_repeat_change}, {"find-parenthesis", gl_find_parenthesis}, {"list-history", gl_list_history}, }; /* * Define the default key-bindings in emacs mode. */ static const KtKeyBinding gl_emacs_bindings[] = { {"right", "cursor-right"}, {"^F", "cursor-right"}, {"left", "cursor-left"}, {"^B", "cursor-left"}, {"M-i", "insert-mode"}, {"M-I", "insert-mode"}, {"^A", "beginning-of-line"}, {"^E", "end-of-line"}, {"^U", "delete-line"}, {"^K", "kill-line"}, {"M-f", "forward-word"}, {"M-F", "forward-word"}, {"M-b", "backward-word"}, {"M-B", "backward-word"}, {"^D", "del-char-or-list-or-eof"}, {"^H", "backward-delete-char"}, {"^?", "backward-delete-char"}, {"M-d", "forward-delete-word"}, {"M-D", "forward-delete-word"}, {"M-^H", "backward-delete-word"}, {"M-^?", "backward-delete-word"}, {"M-u", "upcase-word"}, {"M-U", "upcase-word"}, {"M-l", "downcase-word"}, {"M-L", "downcase-word"}, {"M-c", "capitalize-word"}, {"M-C", "capitalize-word"}, {"^R", "redisplay"}, {"^L", "clear-screen"}, {"^T", "transpose-chars"}, {"^@", "set-mark"}, {"^X^X", "exchange-point-and-mark"}, {"^W", "kill-region"}, {"M-w", "copy-region-as-kill"}, {"M-W", "copy-region-as-kill"}, {"^Y", "yank"}, {"^P", "up-history"}, {"up", "up-history"}, {"^N", "down-history"}, {"down", "down-history"}, {"M-p", "history-search-backward"}, {"M-P", "history-search-backward"}, {"M-n", "history-search-forward"}, {"M-N", "history-search-forward"}, {"\t", "complete-word"}, #ifndef HIDE_FILE_SYSTEM {"^X*", "expand-filename"}, {"^X^F", "read-from-file"}, {"^X^R", "read-init-files"}, {"^Xg", "list-glob"}, {"^XG", "list-glob"}, #endif {"^Xh", "list-history"}, {"^XH", "list-history"}, {"M-<", "beginning-of-history"}, {"M->", "end-of-history"}, {"M-0", "digit-argument"}, {"M-1", "digit-argument"}, {"M-2", "digit-argument"}, {"M-3", "digit-argument"}, {"M-4", "digit-argument"}, {"M-5", "digit-argument"}, {"M-6", "digit-argument"}, {"M-7", "digit-argument"}, {"M-8", "digit-argument"}, {"M-9", "digit-argument"}, {"\r", "newline"}, {"\n", "newline"}, {"M-o", "repeat-history"}, {"M-C-v", "vi-mode"}, }; /* * Define the default key-bindings in vi mode. Note that in vi-mode * meta-key bindings are command-mode bindings. For example M-i first * switches to command mode if not already in that mode, then moves * the cursor one position right, as in vi. */ static const KtKeyBinding gl_vi_bindings[] = { {"^D", "list-or-eof"}, #ifndef HIDE_FILE_SYSTEM {"^G", "list-glob"}, #endif {"^H", "backward-delete-char"}, {"\t", "complete-word"}, {"\r", "newline"}, {"\n", "newline"}, {"^L", "clear-screen"}, {"^N", "down-history"}, {"^P", "up-history"}, {"^R", "redisplay"}, {"^U", "backward-kill-line"}, {"^W", "backward-delete-word"}, #ifndef HIDE_FILE_SYSTEM {"^X^F", "read-from-file"}, {"^X^R", "read-init-files"}, {"^X*", "expand-filename"}, #endif {"^?", "backward-delete-char"}, {"M- ", "cursor-right"}, {"M-$", "end-of-line"}, #ifndef HIDE_FILE_SYSTEM {"M-*", "expand-filename"}, #endif {"M-+", "down-history"}, {"M--", "up-history"}, {"M-<", "beginning-of-history"}, {"M->", "end-of-history"}, {"M-^", "beginning-of-line"}, {"M-;", "repeat-find-char"}, {"M-,", "invert-refind-char"}, {"M-|", "goto-column"}, {"M-~", "change-case"}, {"M-.", "vi-repeat-change"}, {"M-%", "find-parenthesis"}, {"M-0", "digit-argument"}, {"M-1", "digit-argument"}, {"M-2", "digit-argument"}, {"M-3", "digit-argument"}, {"M-4", "digit-argument"}, {"M-5", "digit-argument"}, {"M-6", "digit-argument"}, {"M-7", "digit-argument"}, {"M-8", "digit-argument"}, {"M-9", "digit-argument"}, {"M-a", "vi-append"}, {"M-A", "vi-append-at-eol"}, {"M-b", "backward-word"}, {"M-B", "backward-word"}, {"M-C", "vi-change-rest-of-line"}, {"M-cb", "vi-backward-change-word"}, {"M-cB", "vi-backward-change-word"}, {"M-cc", "vi-change-line"}, {"M-ce", "vi-forward-change-word"}, {"M-cE", "vi-forward-change-word"}, {"M-cw", "vi-forward-change-word"}, {"M-cW", "vi-forward-change-word"}, {"M-cF", "vi-backward-change-find"}, {"M-cf", "vi-forward-change-find"}, {"M-cT", "vi-backward-change-to"}, {"M-ct", "vi-forward-change-to"}, {"M-c;", "vi-change-refind"}, {"M-c,", "vi-change-invert-refind"}, {"M-ch", "vi-backward-change-char"}, {"M-c^H", "vi-backward-change-char"}, {"M-c^?", "vi-backward-change-char"}, {"M-cl", "vi-forward-change-char"}, {"M-c ", "vi-forward-change-char"}, {"M-c^", "vi-change-to-bol"}, {"M-c0", "vi-change-to-bol"}, {"M-c$", "vi-change-rest-of-line"}, {"M-c|", "vi-change-to-column"}, {"M-c%", "vi-change-to-parenthesis"}, {"M-dh", "backward-delete-char"}, {"M-d^H", "backward-delete-char"}, {"M-d^?", "backward-delete-char"}, {"M-dl", "forward-delete-char"}, {"M-d ", "forward-delete-char"}, {"M-dd", "delete-line"}, {"M-db", "backward-delete-word"}, {"M-dB", "backward-delete-word"}, {"M-de", "forward-delete-word"}, {"M-dE", "forward-delete-word"}, {"M-dw", "forward-delete-word"}, {"M-dW", "forward-delete-word"}, {"M-dF", "backward-delete-find"}, {"M-df", "forward-delete-find"}, {"M-dT", "backward-delete-to"}, {"M-dt", "forward-delete-to"}, {"M-d;", "delete-refind"}, {"M-d,", "delete-invert-refind"}, {"M-d^", "backward-kill-line"}, {"M-d0", "backward-kill-line"}, {"M-d$", "kill-line"}, {"M-D", "kill-line"}, {"M-d|", "delete-to-column"}, {"M-d%", "delete-to-parenthesis"}, {"M-e", "forward-word"}, {"M-E", "forward-word"}, {"M-f", "forward-find-char"}, {"M-F", "backward-find-char"}, {"M--", "up-history"}, {"M-h", "cursor-left"}, {"M-H", "beginning-of-history"}, {"M-i", "vi-insert"}, {"M-I", "vi-insert-at-bol"}, {"M-j", "down-history"}, {"M-J", "history-search-forward"}, {"M-k", "up-history"}, {"M-K", "history-search-backward"}, {"M-l", "cursor-right"}, {"M-L", "end-of-history"}, {"M-n", "history-re-search-forward"}, {"M-N", "history-re-search-backward"}, {"M-p", "append-yank"}, {"M-P", "yank"}, {"M-r", "vi-replace-char"}, {"M-R", "vi-overwrite"}, {"M-s", "vi-forward-change-char"}, {"M-S", "vi-change-line"}, {"M-t", "forward-to-char"}, {"M-T", "backward-to-char"}, {"M-u", "vi-undo"}, {"M-w", "forward-to-word"}, {"M-W", "forward-to-word"}, {"M-x", "forward-delete-char"}, {"M-X", "backward-delete-char"}, {"M-yh", "backward-copy-char"}, {"M-y^H", "backward-copy-char"}, {"M-y^?", "backward-copy-char"}, {"M-yl", "forward-copy-char"}, {"M-y ", "forward-copy-char"}, {"M-ye", "forward-copy-word"}, {"M-yE", "forward-copy-word"}, {"M-yw", "forward-copy-word"}, {"M-yW", "forward-copy-word"}, {"M-yb", "backward-copy-word"}, {"M-yB", "backward-copy-word"}, {"M-yf", "forward-copy-find"}, {"M-yF", "backward-copy-find"}, {"M-yt", "forward-copy-to"}, {"M-yT", "backward-copy-to"}, {"M-y;", "copy-refind"}, {"M-y,", "copy-invert-refind"}, {"M-y^", "copy-to-bol"}, {"M-y0", "copy-to-bol"}, {"M-y$", "copy-rest-of-line"}, {"M-yy", "copy-line"}, {"M-Y", "copy-line"}, {"M-y|", "copy-to-column"}, {"M-y%", "copy-to-parenthesis"}, {"M-^E", "emacs-mode"}, {"M-^H", "cursor-left"}, {"M-^?", "cursor-left"}, {"M-^L", "clear-screen"}, {"M-^N", "down-history"}, {"M-^P", "up-history"}, {"M-^R", "redisplay"}, {"M-^D", "list-or-eof"}, {"M-\r", "newline"}, {"M-\t", "complete-word"}, {"M-\n", "newline"}, #ifndef HIDE_FILE_SYSTEM {"M-^X^R", "read-init-files"}, #endif {"M-^Xh", "list-history"}, {"M-^XH", "list-history"}, {"down", "down-history"}, {"up", "up-history"}, {"left", "cursor-left"}, {"right", "cursor-right"}, }; /*....................................................................... * Create a new GetLine object. * * Input: * linelen size_t The maximum line length to allow for. * histlen size_t The number of bytes to allocate for recording * a circular buffer of history lines. * Output: * return GetLine * The new object, or NULL on error. */ GetLine *new_GetLine(size_t linelen, size_t histlen) { GetLine *gl; /* The object to be returned */ int i; /* * Check the arguments. */ if(linelen < 10) { errno = ENOMEM; return NULL; }; /* * Allocate the container. */ gl = (GetLine *) malloc(sizeof(GetLine)); if(!gl) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_GetLine(). */ gl->err = NULL; gl->glh = NULL; gl->cpl = NULL; #ifndef HIDE_FILE_SYSTEM gl->cplfn.fn = cpl_file_completions; #else gl->cplfn.fn = gl_no_completions; #endif gl->cplfn.data = NULL; #ifndef WITHOUT_FILE_SYSTEM gl->ef = NULL; #endif gl->capmem = NULL; gl->cq = NULL; gl->input_fd = -1; gl->output_fd = -1; gl->input_fp = NULL; gl->output_fp = NULL; gl->file_fp = NULL; gl->term = NULL; gl->is_term = 0; gl->flush_fn = gl_flush_terminal; gl->io_mode = GL_NORMAL_MODE; gl->raw_mode = 0; gl->pending_io = GLP_WRITE; /* We will start by writing the prompt */ gl_clear_status(gl); gl->linelen = linelen; gl->line = NULL; gl->cutbuf = NULL; gl->prompt = NULL; gl->prompt_len = 0; gl->prompt_changed = 0; gl->prompt_style = GL_LITERAL_PROMPT; gl->cpl_mem = NULL; gl->ext_act_mem = NULL; gl->sig_mem = NULL; gl->sigs = NULL; gl->signals_masked = 0; gl->signals_overriden = 0; sigemptyset(&gl->all_signal_set); sigemptyset(&gl->old_signal_set); sigemptyset(&gl->use_signal_set); gl->bindings = NULL; gl->ntotal = 0; gl->buff_curpos = 0; gl->term_curpos = 0; gl->term_len = 0; gl->buff_mark = 0; gl->insert_curpos = 0; gl->insert = 1; gl->number = -1; gl->endline = 1; gl->displayed = 0; gl->redisplay = 0; gl->postpone = 0; gl->keybuf[0]='\0'; gl->nbuf = 0; gl->nread = 0; gl->current_action.fn = 0; gl->current_action.data = NULL; gl->current_count = 0; gl->preload_id = 0; gl->preload_history = 0; gl->keyseq_count = 0; gl->last_search = -1; gl->editor = GL_EMACS_MODE; gl->silence_bell = 0; gl->automatic_history = 1; gl->vi.undo.line = NULL; gl->vi.undo.buff_curpos = 0; gl->vi.undo.ntotal = 0; gl->vi.undo.saved = 0; gl->vi.repeat.action.fn = 0; gl->vi.repeat.action.data = 0; gl->vi.repeat.count = 0; gl->vi.repeat.input_curpos = 0; gl->vi.repeat.command_curpos = 0; gl->vi.repeat.input_char = '\0'; gl->vi.repeat.saved = 0; gl->vi.repeat.active = 0; gl->vi.command = 0; gl->vi.find_forward = 0; gl->vi.find_onto = 0; gl->vi.find_char = '\0'; gl->left = NULL; gl->right = NULL; gl->up = NULL; gl->down = NULL; gl->home = NULL; gl->bol = 0; gl->clear_eol = NULL; gl->clear_eod = NULL; gl->u_arrow = NULL; gl->d_arrow = NULL; gl->l_arrow = NULL; gl->r_arrow = NULL; gl->sound_bell = NULL; gl->bold = NULL; gl->underline = NULL; gl->standout = NULL; gl->dim = NULL; gl->reverse = NULL; gl->blink = NULL; gl->text_attr_off = NULL; gl->nline = 0; gl->ncolumn = 0; #ifdef USE_TERMINFO gl->left_n = NULL; gl->right_n = NULL; #elif defined(USE_TERMCAP) gl->tgetent_buf = NULL; gl->tgetstr_buf = NULL; #endif gl->app_file = NULL; gl->user_file = NULL; gl->configured = 0; gl->echo = 1; gl->last_signal = -1; #ifdef HAVE_SELECT gl->fd_node_mem = NULL; gl->fd_nodes = NULL; FD_ZERO(&gl->rfds); FD_ZERO(&gl->wfds); FD_ZERO(&gl->ufds); gl->max_fd = 0; gl->timer.dt.tv_sec = 0; gl->timer.dt.tv_usec = 0; gl->timer.fn = 0; gl->timer.data = NULL; #endif /* * Allocate an error reporting buffer. */ gl->err = _new_ErrMsg(); if(!gl->err) return del_GetLine(gl); /* * Allocate the history buffer. */ gl->glh = _new_GlHistory(histlen); if(!gl->glh) return del_GetLine(gl); /* * Allocate the resource object for file-completion. */ gl->cpl = new_WordCompletion(); if(!gl->cpl) return del_GetLine(gl); /* * Allocate the resource object for file-completion. */ #ifndef WITHOUT_FILE_SYSTEM gl->ef = new_ExpandFile(); if(!gl->ef) return del_GetLine(gl); #endif /* * Allocate a string-segment memory allocator for use in storing terminal * capablity strings. */ gl->capmem = _new_StringGroup(CAPMEM_SEGMENT_SIZE); if(!gl->capmem) return del_GetLine(gl); /* * Allocate the character queue that is used to buffer terminal output. */ gl->cq = _new_GlCharQueue(); if(!gl->cq) return del_GetLine(gl); /* * Allocate a line buffer, leaving 2 extra characters for the terminating * '\n' and '\0' characters */ gl->line = (char *) malloc(linelen + 2); if(!gl->line) { errno = ENOMEM; return del_GetLine(gl); }; /* * Start with an empty input line. */ gl_truncate_buffer(gl, 0); /* * Allocate a cut buffer. */ gl->cutbuf = (char *) malloc(linelen + 2); if(!gl->cutbuf) { errno = ENOMEM; return del_GetLine(gl); }; gl->cutbuf[0] = '\0'; /* * Allocate an initial empty prompt. */ _gl_replace_prompt(gl, NULL); if(!gl->prompt) { errno = ENOMEM; return del_GetLine(gl); }; /* * Allocate a vi undo buffer. */ gl->vi.undo.line = (char *) malloc(linelen + 2); if(!gl->vi.undo.line) { errno = ENOMEM; return del_GetLine(gl); }; gl->vi.undo.line[0] = '\0'; /* * Allocate a freelist from which to allocate nodes for the list * of completion functions. */ gl->cpl_mem = _new_FreeList(sizeof(GlCplCallback), GL_CPL_FREELIST_BLOCKING); if(!gl->cpl_mem) return del_GetLine(gl); /* * Allocate a freelist from which to allocate nodes for the list * of external action functions. */ gl->ext_act_mem = _new_FreeList(sizeof(GlExternalAction), GL_EXT_ACT_FREELIST_BLOCKING); if(!gl->ext_act_mem) return del_GetLine(gl); /* * Allocate a freelist from which to allocate nodes for the list * of signals. */ gl->sig_mem = _new_FreeList(sizeof(GlSignalNode), GLS_FREELIST_BLOCKING); if(!gl->sig_mem) return del_GetLine(gl); /* * Install initial dispositions for the default list of signals that * gl_get_line() traps. */ for(i=0; isigno, sig->flags, sig->after, sig->errno_value)) return del_GetLine(gl); }; /* * Allocate an empty table of key bindings. */ gl->bindings = _new_KeyTab(); if(!gl->bindings) return del_GetLine(gl); /* * Define the available actions that can be bound to key sequences. */ for(i=0; ibindings, gl_actions[i].name, gl_actions[i].fn, NULL)) return del_GetLine(gl); }; /* * Set up the default bindings. */ if(gl_change_editor(gl, gl->editor)) return del_GetLine(gl); /* * Allocate termcap buffers. */ #ifdef USE_TERMCAP gl->tgetent_buf = (char *) malloc(TERMCAP_BUF_SIZE); gl->tgetstr_buf = (char *) malloc(TERMCAP_BUF_SIZE); if(!gl->tgetent_buf || !gl->tgetstr_buf) { errno = ENOMEM; return del_GetLine(gl); }; #endif /* * Set up for I/O assuming stdin and stdout. */ if(_gl_change_terminal(gl, stdin, stdout, getenv("TERM"))) return del_GetLine(gl); /* * Create a freelist for use in allocating GlFdNode list nodes. */ #ifdef HAVE_SELECT gl->fd_node_mem = _new_FreeList(sizeof(GlFdNode), GLFD_FREELIST_BLOCKING); if(!gl->fd_node_mem) return del_GetLine(gl); #endif /* * We are done for now. */ return gl; } /*....................................................................... * Delete a GetLine object. * * Input: * gl GetLine * The object to be deleted. * Output: * return GetLine * The deleted object (always NULL). */ GetLine *del_GetLine(GetLine *gl) { if(gl) { /* * If the terminal is in raw server mode, reset it. */ _gl_normal_io(gl); /* * Deallocate all objects contained by gl. */ gl->err = _del_ErrMsg(gl->err); gl->glh = _del_GlHistory(gl->glh); gl->cpl = del_WordCompletion(gl->cpl); #ifndef WITHOUT_FILE_SYSTEM gl->ef = del_ExpandFile(gl->ef); #endif gl->capmem = _del_StringGroup(gl->capmem); gl->cq = _del_GlCharQueue(gl->cq); if(gl->file_fp) fclose(gl->file_fp); if(gl->term) free(gl->term); if(gl->line) free(gl->line); if(gl->cutbuf) free(gl->cutbuf); if(gl->prompt) free(gl->prompt); gl->cpl_mem = _del_FreeList(gl->cpl_mem, 1); gl->ext_act_mem = _del_FreeList(gl->ext_act_mem, 1); gl->sig_mem = _del_FreeList(gl->sig_mem, 1); gl->sigs = NULL; /* Already freed by freeing sig_mem */ gl->bindings = _del_KeyTab(gl->bindings); if(gl->vi.undo.line) free(gl->vi.undo.line); #ifdef USE_TERMCAP if(gl->tgetent_buf) free(gl->tgetent_buf); if(gl->tgetstr_buf) free(gl->tgetstr_buf); #endif if(gl->app_file) free(gl->app_file); if(gl->user_file) free(gl->user_file); #ifdef HAVE_SELECT gl->fd_node_mem = _del_FreeList(gl->fd_node_mem, 1); gl->fd_nodes = NULL; /* Already freed by freeing gl->fd_node_mem */ #endif /* * Delete the now empty container. */ free(gl); }; return NULL; } /*....................................................................... * Bind a control or meta character to an action. * * Input: * gl GetLine * The resource object of this program. * binder KtBinder The source of the binding. * c char The control or meta character. * If this is '\0', the call is ignored. * action const char * The action name to bind the key to. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_bind_control_char(GetLine *gl, KtBinder binder, char c, const char *action) { char keyseq[2]; /* * Quietly reject binding to the NUL control character, since this * is an ambiguous prefix of all bindings. */ if(c == '\0') return 0; /* * Making sure not to bind characters which aren't either control or * meta characters. */ if(IS_CTRL_CHAR(c) || IS_META_CHAR(c)) { keyseq[0] = c; keyseq[1] = '\0'; } else { return 0; }; /* * Install the binding. */ if(_kt_set_keybinding(gl->bindings, binder, keyseq, action)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Read a line from the user. * * Input: * gl GetLine * A resource object returned by new_GetLine(). * prompt char * The prompt to prefix the line with. * start_line char * The initial contents of the input line, or NULL * if it should start out empty. * start_pos int If start_line isn't NULL, this specifies the * index of the character over which the cursor * should initially be positioned within the line. * If you just want it to follow the last character * of the line, send -1. * Output: * return char * An internal buffer containing the input line, or * NULL at the end of input. If the line fitted in * the buffer there will be a '\n' newline character * before the terminating '\0'. If it was truncated * there will be no newline character, and the remains * of the line should be retrieved via further calls * to this function. */ char *gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos) { char *retval; /* The return value of _gl_get_line() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return NULL; }; /* * Temporarily block all of the signals that we have been asked to trap. */ if(gl_mask_signals(gl, &gl->old_signal_set)) return NULL; /* * Perform the command-line editing task. */ retval = _gl_get_line(gl, prompt, start_line, start_pos); /* * Restore the process signal mask to how it was when this function was * first called. */ gl_unmask_signals(gl, &gl->old_signal_set); return retval; } /*....................................................................... * This is the main body of the public function gl_get_line(). */ static char *_gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos) { int waserr = 0; /* True if an error occurs */ /* * Assume that this call will successfully complete the input * line until proven otherwise. */ gl_clear_status(gl); /* * If this is the first call to this function since new_GetLine(), * complete any postponed configuration. */ if(!gl->configured) { (void) _gl_configure_getline(gl, NULL, NULL, TECLA_CONFIG_FILE); gl->configured = 1; }; /* * Before installing our signal handler functions, record the fact * that there are no pending signals. */ gl_pending_signal = -1; /* * Temporarily override the signal handlers of the calling program, * so that we can intercept signals that would leave the terminal * in a bad state. */ waserr = gl_override_signal_handlers(gl); /* * After recording the current terminal settings, switch the terminal * into raw input mode. */ waserr = waserr || _gl_raw_io(gl, 1); /* * Attempt to read the line. This will require more than one attempt if * either a current temporary input file is opened by gl_get_input_line() * or the end of a temporary input file is reached by gl_read_stream_line(). */ while(!waserr) { /* * Read a line from a non-interactive stream? */ if(gl->file_fp || !gl->is_term) { if(gl_read_stream_line(gl)==0) { break; } else if(gl->file_fp) { gl_revert_input(gl); gl_record_status(gl, GLR_NEWLINE, 0); } else { waserr = 1; break; }; }; /* * Read from the terminal? Note that the above if() block may have * changed gl->file_fp, so it is necessary to retest it here, rather * than using an else statement. */ if(!gl->file_fp && gl->is_term) { if(gl_get_input_line(gl, prompt, start_line, start_pos)) waserr = 1; else break; }; }; /* * If an error occurred, but gl->rtn_status is still set to * GLR_NEWLINE, change the status to GLR_ERROR. Otherwise * leave it at whatever specific value was assigned by the function * that aborted input. This means that only functions that trap * non-generic errors have to remember to update gl->rtn_status * themselves. */ if(waserr && gl->rtn_status == GLR_NEWLINE) gl_record_status(gl, GLR_ERROR, errno); /* * Restore terminal settings. */ if(gl->io_mode != GL_SERVER_MODE) _gl_normal_io(gl); /* * Restore the signal handlers. */ gl_restore_signal_handlers(gl); /* * If gl_get_line() gets aborted early, the errno value associated * with the event that caused this to happen is recorded in * gl->rtn_errno. Since errno may have been overwritten by cleanup * functions after this, restore its value to the value that it had * when the error condition occured, so that the caller can examine it * to find out what happened. */ errno = gl->rtn_errno; /* * Check the completion status to see how to return. */ switch(gl->rtn_status) { case GLR_NEWLINE: /* Success */ return gl->line; case GLR_BLOCKED: /* These events abort the current input line, */ case GLR_SIGNAL: /* when in normal blocking I/O mode, but only */ case GLR_TIMEOUT: /* temporarily pause line editing when in */ case GLR_FDABORT: /* non-blocking server I/O mode. */ if(gl->io_mode != GL_SERVER_MODE) _gl_abandon_line(gl); return NULL; case GLR_ERROR: /* Unrecoverable errors abort the input line, */ case GLR_EOF: /* regardless of the I/O mode. */ default: _gl_abandon_line(gl); return NULL; }; } /*....................................................................... * Read a single character from the user. * * Input: * gl GetLine * A resource object returned by new_GetLine(). * prompt char * The prompt to prefix the line with, or NULL if * no prompt is required. * defchar char The character to substitute if the * user simply hits return, or '\n' if you don't * need to substitute anything. * Output: * return int The character that was read, or EOF if the read * had to be aborted (in which case you can call * gl_return_status() to find out why). */ int gl_query_char(GetLine *gl, const char *prompt, char defchar) { int retval; /* The return value of _gl_query_char() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return EOF; }; /* * Temporarily block all of the signals that we have been asked to trap. */ if(gl_mask_signals(gl, &gl->old_signal_set)) return EOF; /* * Perform the character reading task. */ retval = _gl_query_char(gl, prompt, defchar); /* * Restore the process signal mask to how it was when this function was * first called. */ gl_unmask_signals(gl, &gl->old_signal_set); return retval; } /*....................................................................... * This is the main body of the public function gl_query_char(). */ static int _gl_query_char(GetLine *gl, const char *prompt, char defchar) { int c = EOF; /* The character to be returned */ int waserr = 0; /* True if an error occurs */ /* * Assume that this call will successfully complete the input operation * until proven otherwise. */ gl_clear_status(gl); /* * If this is the first call to this function or gl_get_line(), * since new_GetLine(), complete any postponed configuration. */ if(!gl->configured) { (void) _gl_configure_getline(gl, NULL, NULL, TECLA_CONFIG_FILE); gl->configured = 1; }; /* * Before installing our signal handler functions, record the fact * that there are no pending signals. */ gl_pending_signal = -1; /* * Temporarily override the signal handlers of the calling program, * so that we can intercept signals that would leave the terminal * in a bad state. */ waserr = gl_override_signal_handlers(gl); /* * After recording the current terminal settings, switch the terminal * into raw input mode without redisplaying any partially entered * input line. */ waserr = waserr || _gl_raw_io(gl, 0); /* * Attempt to read the line. This will require more than one attempt if * either a current temporary input file is opened by gl_get_input_line() * or the end of a temporary input file is reached by gl_read_stream_line(). */ while(!waserr) { /* * Read a line from a non-interactive stream? */ if(gl->file_fp || !gl->is_term) { c = gl_read_stream_char(gl); if(c != EOF) { /* Success? */ if(c=='\n') c = defchar; break; } else if(gl->file_fp) { /* End of temporary input file? */ gl_revert_input(gl); gl_record_status(gl, GLR_NEWLINE, 0); } else { /* An error? */ waserr = 1; break; }; }; /* * Read from the terminal? Note that the above if() block may have * changed gl->file_fp, so it is necessary to retest it here, rather * than using an else statement. */ if(!gl->file_fp && gl->is_term) { c = gl_get_query_char(gl, prompt, defchar); if(c==EOF) waserr = 1; else break; }; }; /* * If an error occurred, but gl->rtn_status is still set to * GLR_NEWLINE, change the status to GLR_ERROR. Otherwise * leave it at whatever specific value was assigned by the function * that aborted input. This means that only functions that trap * non-generic errors have to remember to update gl->rtn_status * themselves. */ if(waserr && gl->rtn_status == GLR_NEWLINE) gl_record_status(gl, GLR_ERROR, errno); /* * Restore terminal settings. */ if(gl->io_mode != GL_SERVER_MODE) _gl_normal_io(gl); /* * Restore the signal handlers. */ gl_restore_signal_handlers(gl); /* * If this function gets aborted early, the errno value associated * with the event that caused this to happen is recorded in * gl->rtn_errno. Since errno may have been overwritten by cleanup * functions after this, restore its value to the value that it had * when the error condition occured, so that the caller can examine it * to find out what happened. */ errno = gl->rtn_errno; /* * Error conditions are signalled to the caller, by setting the returned * character to EOF. */ if(gl->rtn_status != GLR_NEWLINE) c = EOF; /* * In this mode, every character that is read is a completed * transaction, just like reading a completed input line, so prepare * for the next input line or character. */ _gl_abandon_line(gl); /* * Return the acquired character. */ return c; } /*....................................................................... * Record of the signal handlers of the calling program, so that they * can be restored later. * * Input: * gl GetLine * The resource object of this library. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_override_signal_handlers(GetLine *gl) { GlSignalNode *sig; /* A node in the list of signals to be caught */ /* * Set up our signal handler. */ SigAction act; act.sa_handler = gl_signal_handler; memcpy(&act.sa_mask, &gl->all_signal_set, sizeof(sigset_t)); act.sa_flags = 0; /* * Get the subset of the signals that we are supposed to trap that * should actually be trapped. */ sigemptyset(&gl->use_signal_set); for(sig=gl->sigs; sig; sig=sig->next) { /* * Trap this signal? If it is blocked by the calling program and we * haven't been told to unblock it, don't arrange to trap this signal. */ if(sig->flags & GLS_UNBLOCK_SIG || !sigismember(&gl->old_signal_set, sig->signo)) { if(sigaddset(&gl->use_signal_set, sig->signo) == -1) { _err_record_msg(gl->err, "sigaddset error", END_ERR_MSG); return 1; }; }; }; /* * Override the actions of the signals that we are trapping. */ for(sig=gl->sigs; sig; sig=sig->next) { if(sigismember(&gl->use_signal_set, sig->signo)) { sigdelset(&act.sa_mask, sig->signo); if(sigaction(sig->signo, &act, &sig->original)) { _err_record_msg(gl->err, "sigaction error", END_ERR_MSG); return 1; }; sigaddset(&act.sa_mask, sig->signo); }; }; /* * Record the fact that the application's signal handlers have now * been overriden. */ gl->signals_overriden = 1; /* * Just in case a SIGWINCH signal was sent to the process while our * SIGWINCH signal handler wasn't in place, check to see if the terminal * size needs updating. */ if(_gl_update_size(gl)) return 1; return 0; } /*....................................................................... * Restore the signal handlers of the calling program. * * Input: * gl GetLine * The resource object of this library. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_restore_signal_handlers(GetLine *gl) { GlSignalNode *sig; /* A node in the list of signals to be caught */ /* * Restore application signal handlers that were overriden * by gl_override_signal_handlers(). */ for(sig=gl->sigs; sig; sig=sig->next) { if(sigismember(&gl->use_signal_set, sig->signo) && sigaction(sig->signo, &sig->original, NULL)) { _err_record_msg(gl->err, "sigaction error", END_ERR_MSG); return 1; }; }; /* * Record the fact that the application's signal handlers have now * been restored. */ gl->signals_overriden = 0; return 0; } /*....................................................................... * This signal handler simply records the fact that a given signal was * caught in the file-scope gl_pending_signal variable. */ static void gl_signal_handler(int signo) { gl_pending_signal = signo; siglongjmp(gl_setjmp_buffer, 1); } /*....................................................................... * Switch the terminal into raw mode after storing the previous terminal * settings in gl->attributes. * * Input: * gl GetLine * The resource object of this program. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_raw_terminal_mode(GetLine *gl) { Termios newattr; /* The new terminal attributes */ /* * If the terminal is already in raw mode, do nothing. */ if(gl->raw_mode) return 0; /* * Record the current terminal attributes. */ if(tcgetattr(gl->input_fd, &gl->oldattr)) { _err_record_msg(gl->err, "tcgetattr error", END_ERR_MSG); return 1; }; /* * This function shouldn't do anything but record the current terminal * attritubes if editing has been disabled. */ if(gl->editor == GL_NO_EDITOR) return 0; /* * Modify the existing attributes. */ newattr = gl->oldattr; /* * Turn off local echo, canonical input mode and extended input processing. */ newattr.c_lflag &= ~(ECHO | ICANON | IEXTEN); /* * Don't translate carriage return to newline, turn off input parity * checking, don't strip off 8th bit, turn off output flow control. */ newattr.c_iflag &= ~(ICRNL | INPCK | ISTRIP); /* * Clear size bits, turn off parity checking, and allow 8-bit characters. */ newattr.c_cflag &= ~(CSIZE | PARENB); newattr.c_cflag |= CS8; /* * Turn off output processing. */ newattr.c_oflag &= ~(OPOST); /* * Request one byte at a time, without waiting. */ newattr.c_cc[VMIN] = gl->io_mode==GL_SERVER_MODE ? 0:1; newattr.c_cc[VTIME] = 0; /* * Install the new terminal modes. */ while(tcsetattr(gl->input_fd, TCSADRAIN, &newattr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); return 1; }; }; /* * Record the new terminal mode. */ gl->raw_mode = 1; return 0; } /*....................................................................... * Restore the terminal attributes recorded in gl->oldattr. * * Input: * gl GetLine * The resource object of this library. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_restore_terminal_attributes(GetLine *gl) { int waserr = 0; /* * If not in raw mode, do nothing. */ if(!gl->raw_mode) return 0; /* * Before changing the terminal attributes, make sure that all output * has been passed to the terminal. */ if(gl_flush_output(gl)) waserr = 1; /* * Reset the terminal attributes to the values that they had on * entry to gl_get_line(). */ while(tcsetattr(gl->input_fd, TCSADRAIN, &gl->oldattr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); waserr = 1; break; }; }; /* * Record the new terminal mode. */ gl->raw_mode = 0; return waserr; } /*....................................................................... * Switch the terminal file descriptor to use non-blocking I/O. * * Input: * gl GetLine * The resource object of gl_get_line(). * fd int The file descriptor to make non-blocking. */ static int gl_nonblocking_io(GetLine *gl, int fd) { int fcntl_flags; /* The new file-descriptor control flags */ /* * Is non-blocking I/O supported on this system? Note that even * without non-blocking I/O, the terminal will probably still act as * though it was non-blocking, because we also set the terminal * attributes to return immediately if no input is available and we * use select() to wait to be able to write. If select() also isn't * available, then input will probably remain fine, but output could * block, depending on the behaviour of the terminal driver. */ #if defined(NON_BLOCKING_FLAG) /* * Query the current file-control flags, and add the * non-blocking I/O flag. */ fcntl_flags = fcntl(fd, F_GETFL) | NON_BLOCKING_FLAG; /* * Install the new control flags. */ if(fcntl(fd, F_SETFL, fcntl_flags) == -1) { _err_record_msg(gl->err, "fcntl error", END_ERR_MSG); return 1; }; #endif return 0; } /*....................................................................... * Switch to blocking terminal I/O. * * Input: * gl GetLine * The resource object of gl_get_line(). * fd int The file descriptor to make blocking. */ static int gl_blocking_io(GetLine *gl, int fd) { int fcntl_flags; /* The new file-descriptor control flags */ /* * Is non-blocking I/O implemented on this system? */ #if defined(NON_BLOCKING_FLAG) /* * Query the current file control flags and remove the non-blocking * I/O flag. */ fcntl_flags = fcntl(fd, F_GETFL) & ~NON_BLOCKING_FLAG; /* * Install the modified control flags. */ if(fcntl(fd, F_SETFL, fcntl_flags) == -1) { _err_record_msg(gl->err, "fcntl error", END_ERR_MSG); return 1; }; #endif return 0; } /*....................................................................... * Read a new input line from the user. * * Input: * gl GetLine * The resource object of this library. * prompt char * The prompt to prefix the line with, or NULL to * use the same prompt that was used by the previous * line. * start_line char * The initial contents of the input line, or NULL * if it should start out empty. * start_pos int If start_line isn't NULL, this specifies the * index of the character over which the cursor * should initially be positioned within the line. * If you just want it to follow the last character * of the line, send -1. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_get_input_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos) { char c; /* The character being read */ /* * Flush any pending output to the terminal. */ if(_glq_char_count(gl->cq) > 0 && gl_flush_output(gl)) return 1; /* * Are we starting a new line? */ if(gl->endline) { /* * Delete any incompletely enterred line. */ if(gl_erase_line(gl)) return 1; /* * Display the new line to be edited. */ if(gl_present_line(gl, prompt, start_line, start_pos)) return 1; }; /* * Read one character at a time. */ while(gl_read_terminal(gl, 1, &c) == 0) { /* * Increment the count of the number of key sequences entered. */ gl->keyseq_count++; /* * Interpret the character either as the start of a new key-sequence, * as a continuation of a repeat count, or as a printable character * to be added to the line. */ if(gl_interpret_char(gl, c)) break; /* * If we just ran an action function which temporarily asked for * input to be taken from a file, abort this call. */ if(gl->file_fp) return 0; /* * Has the line been completed? */ if(gl->endline) return gl_line_ended(gl, c); }; /* * To get here, gl_read_terminal() must have returned non-zero. See * whether a signal was caught that requested that the current line * be returned. */ if(gl->endline) return gl_line_ended(gl, '\n'); /* * If I/O blocked while attempting to get the latest character * of the key sequence, rewind the key buffer to allow interpretation of * the current key sequence to be restarted on the next call to this * function. */ if(gl->rtn_status == GLR_BLOCKED && gl->pending_io == GLP_READ) gl->nread = 0; return 1; } /*....................................................................... * This is the private function of gl_query_char() that handles * prompting the user, reading a character from the terminal, and * displaying what the user entered. * * Input: * gl GetLine * The resource object of this library. * prompt char * The prompt to prefix the line with. * defchar char The character to substitute if the * user simply hits return, or '\n' if you don't * need to substitute anything. * Output: * return int The character that was read, or EOF if something * prevented a character from being read. */ static int gl_get_query_char(GetLine *gl, const char *prompt, int defchar) { char c; /* The character being read */ int retval; /* The return value of this function */ /* * Flush any pending output to the terminal. */ if(_glq_char_count(gl->cq) > 0 && gl_flush_output(gl)) return EOF; /* * Delete any incompletely entered line. */ if(gl_erase_line(gl)) return EOF; /* * Reset the line input parameters and display the prompt, if any. */ if(gl_present_line(gl, prompt, NULL, 0)) return EOF; /* * Read one character. */ if(gl_read_terminal(gl, 1, &c) == 0) { /* * In this mode, count each character as being a new key-sequence. */ gl->keyseq_count++; /* * Delete the character that was read, from the key-press buffer. */ gl_discard_chars(gl, gl->nread); /* * Convert carriage returns to newlines. */ if(c == '\r') c = '\n'; /* * If the user just hit return, subsitute the default character. */ if(c == '\n') c = defchar; /* * Display the entered character to the right of the prompt. */ if(c!='\n') { if(gl_end_of_line(gl, 1, NULL)==0) gl_print_char(gl, c, ' '); }; /* * Record the return character, and mark the call as successful. */ retval = c; gl_record_status(gl, GLR_NEWLINE, 0); /* * Was a signal caught whose disposition is to cause the current input * line to be returned? If so return a newline character. */ } else if(gl->endline) { retval = '\n'; gl_record_status(gl, GLR_NEWLINE, 0); } else { retval = EOF; }; /* * Start a new line. */ if(gl_start_newline(gl, 1)) return EOF; /* * Attempt to flush any pending output. */ (void) gl_flush_output(gl); /* * Return either the character that was read, or EOF if an error occurred. */ return retval; } /*....................................................................... * Add a character to the line buffer at the current cursor position, * inserting or overwriting according the current mode. * * Input: * gl GetLine * The resource object of this library. * c char The character to be added. * Output: * return int 0 - OK. * 1 - Insufficient room. */ static int gl_add_char_to_line(GetLine *gl, char c) { /* * Keep a record of the current cursor position. */ int buff_curpos = gl->buff_curpos; int term_curpos = gl->term_curpos; /* * Work out the displayed width of the new character. */ int width = gl_displayed_char_width(gl, c, term_curpos); /* * If we are in insert mode, or at the end of the line, * check that we can accomodate a new character in the buffer. * If not, simply return, leaving it up to the calling program * to check for the absence of a newline character. */ if((gl->insert || buff_curpos >= gl->ntotal) && gl->ntotal >= gl->linelen) return 0; /* * Are we adding characters to the line (ie. inserting or appending)? */ if(gl->insert || buff_curpos >= gl->ntotal) { /* * If inserting, make room for the new character. */ if(buff_curpos < gl->ntotal) gl_make_gap_in_buffer(gl, buff_curpos, 1); /* * Copy the character into the buffer. */ gl_buffer_char(gl, c, buff_curpos); gl->buff_curpos++; /* * Redraw the line from the cursor position to the end of the line, * and move the cursor to just after the added character. */ if(gl_print_string(gl, gl->line + buff_curpos, '\0') || gl_set_term_curpos(gl, term_curpos + width)) return 1; /* * Are we overwriting an existing character? */ } else { /* * Get the width of the character being overwritten. */ int old_width = gl_displayed_char_width(gl, gl->line[buff_curpos], term_curpos); /* * Overwrite the character in the buffer. */ gl_buffer_char(gl, c, buff_curpos); /* * If we are replacing with a narrower character, we need to * redraw the terminal string to the end of the line, then * overwrite the trailing old_width - width characters * with spaces. */ if(old_width > width) { if(gl_print_string(gl, gl->line + buff_curpos, '\0')) return 1; /* * Clear to the end of the terminal. */ if(gl_truncate_display(gl)) return 1; /* * Move the cursor to the end of the new character. */ if(gl_set_term_curpos(gl, term_curpos + width)) return 1; gl->buff_curpos++; /* * If we are replacing with a wider character, then we will be * inserting new characters, and thus extending the line. */ } else if(width > old_width) { /* * Redraw the line from the cursor position to the end of the line, * and move the cursor to just after the added character. */ if(gl_print_string(gl, gl->line + buff_curpos, '\0') || gl_set_term_curpos(gl, term_curpos + width)) return 1; gl->buff_curpos++; /* * The original and replacement characters have the same width, * so simply overwrite. */ } else { /* * Copy the character into the buffer. */ gl_buffer_char(gl, c, buff_curpos); gl->buff_curpos++; /* * Overwrite the original character. */ if(gl_print_char(gl, c, gl->line[gl->buff_curpos])) return 1; }; }; return 0; } /*....................................................................... * Insert/append a string to the line buffer and terminal at the current * cursor position. * * Input: * gl GetLine * The resource object of this library. * s char * The string to be added. * Output: * return int 0 - OK. * 1 - Insufficient room. */ static int gl_add_string_to_line(GetLine *gl, const char *s) { int buff_slen; /* The length of the string being added to line[] */ int term_slen; /* The length of the string being written to the terminal */ int buff_curpos; /* The original value of gl->buff_curpos */ int term_curpos; /* The original value of gl->term_curpos */ /* * Keep a record of the current cursor position. */ buff_curpos = gl->buff_curpos; term_curpos = gl->term_curpos; /* * How long is the string to be added? */ buff_slen = strlen(s); term_slen = gl_displayed_string_width(gl, s, buff_slen, term_curpos); /* * Check that we can accomodate the string in the buffer. * If not, simply return, leaving it up to the calling program * to check for the absence of a newline character. */ if(gl->ntotal + buff_slen > gl->linelen) return 0; /* * Move the characters that follow the cursor in the buffer by * buff_slen characters to the right. */ if(gl->ntotal > gl->buff_curpos) gl_make_gap_in_buffer(gl, gl->buff_curpos, buff_slen); /* * Copy the string into the buffer. */ gl_buffer_string(gl, s, buff_slen, gl->buff_curpos); gl->buff_curpos += buff_slen; /* * Write the modified part of the line to the terminal, then move * the terminal cursor to the end of the displayed input string. */ if(gl_print_string(gl, gl->line + buff_curpos, '\0') || gl_set_term_curpos(gl, term_curpos + term_slen)) return 1; return 0; } /*....................................................................... * Read a single character from the terminal. * * Input: * gl GetLine * The resource object of this library. * keep int If true, the returned character will be kept in * the input buffer, for potential replays. It should * subsequently be removed from the buffer when the * key sequence that it belongs to has been fully * processed, by calling gl_discard_chars(). * Input/Output: * c char * The character that is read, is assigned to *c. * Output: * return int 0 - OK. * 1 - Either an I/O error occurred, or a signal was * caught who's disposition is to abort gl_get_line() * or to have gl_get_line() return the current line * as though the user had pressed return. In the * latter case gl->endline will be non-zero. */ static int gl_read_terminal(GetLine *gl, int keep, char *c) { /* * Before waiting for a new character to be input, flush unwritten * characters to the terminal. */ if(gl_flush_output(gl)) return 1; /* * Record the fact that we are about to read from the terminal. */ gl->pending_io = GLP_READ; /* * If there is already an unread character in the buffer, * return it. */ if(gl->nread < gl->nbuf) { *c = gl->keybuf[gl->nread]; /* * Retain the character in the key buffer, but mark it as having been read? */ if(keep) { gl->nread++; /* * Completely remove the character from the key buffer? */ } else { memmove(gl->keybuf + gl->nread, gl->keybuf + gl->nread + 1, gl->nbuf - gl->nread - 1); }; return 0; }; /* * Make sure that there is space in the key buffer for one more character. * This should always be true if gl_interpret_char() is called for each * new character added, since it will clear the buffer once it has recognized * or rejected a key sequence. */ if(gl->nbuf + 1 > GL_KEY_MAX) { gl_print_info(gl, "gl_read_terminal: Buffer overflow avoided.", GL_END_INFO); errno = EIO; return 1; }; /* * Read one character from the terminal. */ switch(gl_read_input(gl, c)) { case GL_READ_OK: break; case GL_READ_BLOCKED: gl_record_status(gl, GLR_BLOCKED, BLOCKED_ERRNO); return 1; break; default: return 1; break; }; /* * Append the character to the key buffer? */ if(keep) { gl->keybuf[gl->nbuf] = *c; gl->nread = ++gl->nbuf; }; return 0; } /*....................................................................... * Read one or more keypresses from the terminal of an input stream. * * Input: * gl GetLine * The resource object of this module. * c char * The character that was read is assigned to *c. * Output: * return GlReadStatus The completion status of the read operation. */ static GlReadStatus gl_read_input(GetLine *gl, char *c) { /* * We may have to repeat the read if window change signals are received. */ for(;;) { /* * Which file descriptor should we read from? Mark this volatile, so * that siglongjmp() can't clobber it. */ volatile int fd = gl->file_fp ? fileno(gl->file_fp) : gl->input_fd; /* * If the endline flag becomes set, don't wait for another character. */ if(gl->endline) return GL_READ_ERROR; /* * Since the code in this function can block, trap signals. */ if(sigsetjmp(gl_setjmp_buffer, 1)==0) { /* * Handle the different I/O modes. */ switch(gl->io_mode) { /* * In normal I/O mode, we call the event handler before attempting * to read, since read() blocks. */ case GL_NORMAL_MODE: if(gl_event_handler(gl, fd)) return GL_READ_ERROR; return gl_read_unmasked(gl, fd, c); /* Read one character */ break; /* * In non-blocking server I/O mode, we attempt to read a character, * and only if this fails, call the event handler to wait for a any * user-configured timeout and any other user-configured events. In * addition, we turn off the fcntl() non-blocking flag when reading * from the terminal, to work around a bug in Solaris. We can do this * without causing the read() to block, because when in non-blocking * server-I/O mode, gl_raw_io() sets the VMIN terminal attribute to 0, * which tells the terminal driver to return immediately if no * characters are available to be read. */ case GL_SERVER_MODE: { GlReadStatus status; /* The return status */ if(isatty(fd)) /* If we reading from a terminal, */ gl_blocking_io(gl, fd); /* switch to blocking I/O */ status = gl_read_unmasked(gl, fd, c); /* Try reading */ if(status == GL_READ_BLOCKED) { /* Nothing readable yet */ if(gl_event_handler(gl, fd)) /* Wait for input */ status = GL_READ_ERROR; else status = gl_read_unmasked(gl, fd, c); /* Try reading again */ }; gl_nonblocking_io(gl, fd); /* Restore non-blocking I/O */ return status; }; break; }; }; /* * To get here, one of the signals that we are trapping must have * been received. Note that by using sigsetjmp() instead of setjmp() * the signal mask that was blocking these signals will have been * reinstated, so we can be sure that no more of these signals will * be received until we explicitly unblock them again. * * First, if non-blocking I/O was temporarily disabled, reinstate it. */ if(gl->io_mode == GL_SERVER_MODE) gl_nonblocking_io(gl, fd); /* * Now respond to the signal that was caught. */ if(gl_check_caught_signal(gl)) return GL_READ_ERROR; }; } /*....................................................................... * This is a private function of gl_read_input(), which unblocks signals * temporarily while it reads a single character from the specified file * descriptor. * * Input: * gl GetLine * The resource object of this module. * fd int The file descriptor to read from. * c char * The character that was read is assigned to *c. * Output: * return GlReadStatus The completion status of the read. */ static GlReadStatus gl_read_unmasked(GetLine *gl, int fd, char *c) { int nread; /* The return value of read() */ /* * Unblock the signals that we are trapping, while waiting for I/O. */ gl_catch_signals(gl); /* * Attempt to read one character from the terminal, restarting the read * if any signals that we aren't trapping, are received. */ do { errno = 0; nread = read(fd, c, 1); } while(nread < 0 && errno==EINTR); /* * Block all of the signals that we are trapping. */ gl_mask_signals(gl, NULL); /* * Check the completion status of the read. */ switch(nread) { case 1: return GL_READ_OK; case 0: return (isatty(fd) || errno != 0) ? GL_READ_BLOCKED : GL_READ_EOF; default: return GL_READ_ERROR; }; } /*....................................................................... * Remove a specified number of characters from the start of the * key-press lookahead buffer, gl->keybuf[], and arrange for the next * read to start from the character at the start of the shifted buffer. * * Input: * gl GetLine * The resource object of this module. * nused int The number of characters to discard from the start * of the buffer. */ static void gl_discard_chars(GetLine *gl, int nused) { int nkeep = gl->nbuf - nused; if(nkeep > 0) { memmove(gl->keybuf, gl->keybuf + nused, nkeep); gl->nbuf = nkeep; gl->nread = 0; } else { gl->nbuf = gl->nread = 0; }; } /*....................................................................... * This function is called to handle signals caught between calls to * sigsetjmp() and siglongjmp(). * * Input: * gl GetLine * The resource object of this library. * Output: * return int 0 - Signal handled internally. * 1 - Signal requires gl_get_line() to abort. */ static int gl_check_caught_signal(GetLine *gl) { GlSignalNode *sig; /* The signal disposition */ SigAction keep_action; /* The signal disposition of tecla signal handlers */ unsigned flags; /* The signal processing flags to use */ int signo; /* The signal to be handled */ /* * Was no signal caught? */ if(gl_pending_signal == -1) return 0; /* * Get the signal to be handled. */ signo = gl_pending_signal; /* * Mark the signal as handled. Note that at this point, all of * the signals that we are trapping are blocked from delivery. */ gl_pending_signal = -1; /* * Record the signal that was caught, so that the user can query it later. */ gl->last_signal = signo; /* * In non-blocking server mode, the application is responsible for * responding to terminal signals, and we don't want gl_get_line()s * normal signal handling to clash with this, so whenever a signal * is caught, we arrange for gl_get_line() to abort and requeue the * signal while signals are still blocked. If the application * had the signal unblocked when gl_get_line() was called, the signal * will be delivered again as soon as gl_get_line() restores the * process signal mask, just before returning to the application. * Note that the caller of this function should set gl->pending_io * to the appropriate choice of GLP_READ and GLP_WRITE, before returning. */ if(gl->io_mode==GL_SERVER_MODE) { gl_record_status(gl, GLR_SIGNAL, EINTR); raise(signo); return 1; }; /* * Lookup the requested disposition of this signal. */ for(sig=gl->sigs; sig && sig->signo != signo; sig=sig->next) ; if(!sig) return 0; /* * Get the signal response flags for this signal. */ flags = sig->flags; /* * Only perform terminal-specific actions if the session is interactive. */ if(gl->is_term) { /* * Did we receive a terminal size signal? */ #ifdef SIGWINCH if(signo == SIGWINCH && _gl_update_size(gl)) return 1; #endif /* * Start a fresh line? */ if(flags & GLS_RESTORE_LINE) { if(gl_start_newline(gl, 0)) return 1; }; /* * Restore terminal settings to how they were before gl_get_line() was * called? */ if(flags & GLS_RESTORE_TTY) gl_restore_terminal_attributes(gl); }; /* * Restore signal handlers to how they were before gl_get_line() was * called? If this hasn't been requested, only reinstate the signal * handler of the signal that we are handling. */ if(flags & GLS_RESTORE_SIG) { gl_restore_signal_handlers(gl); gl_unmask_signals(gl, &gl->old_signal_set); } else { (void) sigaction(sig->signo, &sig->original, &keep_action); (void) sigprocmask(SIG_UNBLOCK, &sig->proc_mask, NULL); }; /* * Forward the signal to the application's signal handler. */ if(!(flags & GLS_DONT_FORWARD)) raise(signo); /* * Reinstate our signal handlers. */ if(flags & GLS_RESTORE_SIG) { gl_mask_signals(gl, NULL); gl_override_signal_handlers(gl); } else { (void) sigaction(sig->signo, &keep_action, NULL); (void) sigprocmask(SIG_BLOCK, &sig->proc_mask, NULL); }; /* * Prepare the terminal for continued editing, if this is an interactive * session. */ if(gl->is_term) { /* * Do we need to reinstate our terminal settings? */ if(flags & GLS_RESTORE_TTY) gl_raw_terminal_mode(gl); /* * Redraw the line? */ if(flags & GLS_REDRAW_LINE) gl_queue_redisplay(gl); }; /* * What next? */ switch(sig->after) { case GLS_RETURN: gl_newline(gl, 1, NULL); return gl->is_term && gl_flush_output(gl); break; case GLS_ABORT: gl_record_status(gl, GLR_SIGNAL, sig->errno_value); return 1; break; case GLS_CONTINUE: return gl->is_term && gl_flush_output(gl); break; }; return 0; } /*....................................................................... * Get pertinent terminal control strings and the initial terminal size. * * Input: * gl GetLine * The resource object of this library. * term char * The type of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_control_strings(GetLine *gl, const char *term) { int bad_term = 0; /* True if term is unusable */ /* * Discard any existing control strings from a previous terminal. */ gl->left = NULL; gl->right = NULL; gl->up = NULL; gl->down = NULL; gl->home = NULL; gl->bol = 0; gl->clear_eol = NULL; gl->clear_eod = NULL; gl->u_arrow = NULL; gl->d_arrow = NULL; gl->l_arrow = NULL; gl->r_arrow = NULL; gl->sound_bell = NULL; gl->bold = NULL; gl->underline = NULL; gl->standout = NULL; gl->dim = NULL; gl->reverse = NULL; gl->blink = NULL; gl->text_attr_off = NULL; gl->nline = 0; gl->ncolumn = 0; #ifdef USE_TERMINFO gl->left_n = NULL; gl->right_n = NULL; #endif /* * If possible lookup the information in a terminal information * database. */ #ifdef USE_TERMINFO { int errret; if(!term || setupterm((char *)term, gl->input_fd, &errret) == ERR) { bad_term = 1; } else { _clr_StringGroup(gl->capmem); gl->left = gl_tigetstr(gl, "cub1"); gl->right = gl_tigetstr(gl, "cuf1"); gl->up = gl_tigetstr(gl, "cuu1"); gl->down = gl_tigetstr(gl, "cud1"); gl->home = gl_tigetstr(gl, "home"); gl->clear_eol = gl_tigetstr(gl, "el"); gl->clear_eod = gl_tigetstr(gl, "ed"); gl->u_arrow = gl_tigetstr(gl, "kcuu1"); gl->d_arrow = gl_tigetstr(gl, "kcud1"); gl->l_arrow = gl_tigetstr(gl, "kcub1"); gl->r_arrow = gl_tigetstr(gl, "kcuf1"); gl->left_n = gl_tigetstr(gl, "cub"); gl->right_n = gl_tigetstr(gl, "cuf"); gl->sound_bell = gl_tigetstr(gl, "bel"); gl->bold = gl_tigetstr(gl, "bold"); gl->underline = gl_tigetstr(gl, "smul"); gl->standout = gl_tigetstr(gl, "smso"); gl->dim = gl_tigetstr(gl, "dim"); gl->reverse = gl_tigetstr(gl, "rev"); gl->blink = gl_tigetstr(gl, "blink"); gl->text_attr_off = gl_tigetstr(gl, "sgr0"); }; }; #elif defined(USE_TERMCAP) if(!term || tgetent(gl->tgetent_buf, (char *)term) < 0) { bad_term = 1; } else { char *tgetstr_buf_ptr = gl->tgetstr_buf; _clr_StringGroup(gl->capmem); gl->left = gl_tgetstr(gl, "le", &tgetstr_buf_ptr); gl->right = gl_tgetstr(gl, "nd", &tgetstr_buf_ptr); gl->up = gl_tgetstr(gl, "up", &tgetstr_buf_ptr); gl->down = gl_tgetstr(gl, "do", &tgetstr_buf_ptr); gl->home = gl_tgetstr(gl, "ho", &tgetstr_buf_ptr); gl->clear_eol = gl_tgetstr(gl, "ce", &tgetstr_buf_ptr); gl->clear_eod = gl_tgetstr(gl, "cd", &tgetstr_buf_ptr); gl->u_arrow = gl_tgetstr(gl, "ku", &tgetstr_buf_ptr); gl->d_arrow = gl_tgetstr(gl, "kd", &tgetstr_buf_ptr); gl->l_arrow = gl_tgetstr(gl, "kl", &tgetstr_buf_ptr); gl->r_arrow = gl_tgetstr(gl, "kr", &tgetstr_buf_ptr); gl->sound_bell = gl_tgetstr(gl, "bl", &tgetstr_buf_ptr); gl->bold = gl_tgetstr(gl, "md", &tgetstr_buf_ptr); gl->underline = gl_tgetstr(gl, "us", &tgetstr_buf_ptr); gl->standout = gl_tgetstr(gl, "so", &tgetstr_buf_ptr); gl->dim = gl_tgetstr(gl, "mh", &tgetstr_buf_ptr); gl->reverse = gl_tgetstr(gl, "mr", &tgetstr_buf_ptr); gl->blink = gl_tgetstr(gl, "mb", &tgetstr_buf_ptr); gl->text_attr_off = gl_tgetstr(gl, "me", &tgetstr_buf_ptr); }; #endif /* * Report term being unusable. */ if(bad_term) { gl_print_info(gl, "Bad terminal type: \"", term ? term : "(null)", "\". Will assume vt100.", GL_END_INFO); }; /* * Fill in missing information with ANSI VT100 strings. */ if(!gl->left) gl->left = "\b"; /* ^H */ if(!gl->right) gl->right = GL_ESC_STR "[C"; if(!gl->up) gl->up = GL_ESC_STR "[A"; if(!gl->down) gl->down = "\n"; if(!gl->home) gl->home = GL_ESC_STR "[H"; if(!gl->bol) gl->bol = "\r"; if(!gl->clear_eol) gl->clear_eol = GL_ESC_STR "[K"; if(!gl->clear_eod) gl->clear_eod = GL_ESC_STR "[J"; if(!gl->u_arrow) gl->u_arrow = GL_ESC_STR "[A"; if(!gl->d_arrow) gl->d_arrow = GL_ESC_STR "[B"; if(!gl->l_arrow) gl->l_arrow = GL_ESC_STR "[D"; if(!gl->r_arrow) gl->r_arrow = GL_ESC_STR "[C"; if(!gl->sound_bell) gl->sound_bell = "\a"; if(!gl->bold) gl->bold = GL_ESC_STR "[1m"; if(!gl->underline) gl->underline = GL_ESC_STR "[4m"; if(!gl->standout) gl->standout = GL_ESC_STR "[1;7m"; if(!gl->dim) gl->dim = ""; /* Not available */ if(!gl->reverse) gl->reverse = GL_ESC_STR "[7m"; if(!gl->blink) gl->blink = GL_ESC_STR "[5m"; if(!gl->text_attr_off) gl->text_attr_off = GL_ESC_STR "[m"; /* * Find out the current terminal size. */ (void) _gl_terminal_size(gl, GL_DEF_NCOLUMN, GL_DEF_NLINE, NULL); return 0; } #ifdef USE_TERMINFO /*....................................................................... * This is a private function of gl_control_strings() used to look up * a termninal capability string from the terminfo database and make * a private copy of it. * * Input: * gl GetLine * The resource object of gl_get_line(). * name const char * The name of the terminfo string to look up. * Output: * return const char * The local copy of the capability, or NULL * if not available. */ static const char *gl_tigetstr(GetLine *gl, const char *name) { const char *value = tigetstr((char *)name); if(!value || value == (char *) -1) return NULL; return _sg_store_string(gl->capmem, value, 0); } #elif defined(USE_TERMCAP) /*....................................................................... * This is a private function of gl_control_strings() used to look up * a termninal capability string from the termcap database and make * a private copy of it. Note that some emulations of tgetstr(), such * as that used by Solaris, ignores the buffer pointer that is past to * it, so we can't assume that a private copy has been made that won't * be trashed by another call to gl_control_strings() by another * GetLine object. So we make what may be a redundant private copy * of the string in gl->capmem. * * Input: * gl GetLine * The resource object of gl_get_line(). * name const char * The name of the terminfo string to look up. * Input/Output: * bufptr char ** On input *bufptr points to the location in * gl->tgetstr_buf at which to record the * capability string. On output *bufptr is * incremented over the stored string. * Output: * return const char * The local copy of the capability, or NULL * on error. */ static const char *gl_tgetstr(GetLine *gl, const char *name, char **bufptr) { const char *value = tgetstr((char *)name, bufptr); if(!value || value == (char *) -1) return NULL; return _sg_store_string(gl->capmem, value, 0); } #endif /*....................................................................... * This is an action function that implements a user interrupt (eg. ^C). */ static KT_KEY_FN(gl_user_interrupt) { raise(SIGINT); return 1; } /*....................................................................... * This is an action function that implements the abort signal. */ static KT_KEY_FN(gl_abort) { raise(SIGABRT); return 1; } /*....................................................................... * This is an action function that sends a suspend signal (eg. ^Z) to the * the parent process. */ static KT_KEY_FN(gl_suspend) { raise(SIGTSTP); return 0; } /*....................................................................... * This is an action function that halts output to the terminal. */ static KT_KEY_FN(gl_stop_output) { tcflow(gl->output_fd, TCOOFF); return 0; } /*....................................................................... * This is an action function that resumes halted terminal output. */ static KT_KEY_FN(gl_start_output) { tcflow(gl->output_fd, TCOON); return 0; } /*....................................................................... * This is an action function that allows the next character to be accepted * without any interpretation as a special character. */ static KT_KEY_FN(gl_literal_next) { char c; /* The character to be added to the line */ int i; /* * Get the character to be inserted literally. */ if(gl_read_terminal(gl, 1, &c)) return 1; /* * Add the character to the line 'count' times. */ for(i=0; incolumn) % TAB_WIDTH); } /*....................................................................... * Return the number of characters needed to display a given character * on the screen. Tab characters require eight spaces, and control * characters are represented by a caret followed by the modified * character. * * Input: * gl GetLine * The resource object of this library. * c char The character to be displayed. * term_curpos int The destination terminal location of the character. * This is needed because the width of tab characters * depends on where they are, relative to the * preceding tab stops. * Output: * return int The number of terminal charaters needed. */ static int gl_displayed_char_width(GetLine *gl, char c, int term_curpos) { if(c=='\t') return gl_displayed_tab_width(gl, term_curpos); if(IS_CTRL_CHAR(c)) return 2; if(!isprint((int)(unsigned char) c)) return gl_octal_width((int)(unsigned char)c) + 1; return 1; } /*....................................................................... * Work out the length of given string of characters on the terminal. * * Input: * gl GetLine * The resource object of this library. * string char * The string to be measured. * nc int The number of characters to be measured, or -1 * to measure the whole string. * term_curpos int The destination terminal location of the character. * This is needed because the width of tab characters * depends on where they are, relative to the * preceding tab stops. * Output: * return int The number of displayed characters. */ static int gl_displayed_string_width(GetLine *gl, const char *string, int nc, int term_curpos) { int slen = 0; /* The displayed number of characters */ int i; /* * How many characters are to be measured? */ if(nc < 0) nc = strlen(string); /* * Add up the length of the displayed string. */ for(i=0; iflush_fn; /* * Only display output when echoing is turned on. */ if(gl->echo) { int ndone = 0; /* The number of characters written so far */ /* * When using un-buffered I/O, flush pending output first. */ if(!buffered) { if(gl_flush_output(gl)) return 1; }; /* * If no length has been provided, measure the length of the string. */ if(n < 0) n = strlen(string); /* * Write the string. */ if(write_fn(gl, string + ndone, n-ndone) != n) return 1; }; return 0; } /*....................................................................... * Output a terminal control sequence. When using terminfo, * this must be a sequence returned by tgetstr() or tigetstr() * respectively. * * Input: * gl GetLine * The resource object of this library. * nline int The number of lines affected by the operation, * or 1 if not relevant. * string char * The control sequence to be sent. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_print_control_sequence(GetLine *gl, int nline, const char *string) { int waserr = 0; /* True if an error occurs */ /* * Only write characters to the terminal when echoing is enabled. */ if(gl->echo) { #if defined(USE_TERMINFO) || defined(USE_TERMCAP) tputs_gl = gl; errno = 0; tputs((char *)string, nline, gl_tputs_putchar); waserr = errno != 0; #else waserr = gl_print_raw_string(gl, 1, string, -1); #endif }; return waserr; } #if defined(USE_TERMINFO) || defined(USE_TERMCAP) /*....................................................................... * The following callback function is called by tputs() to output a raw * control character to the terminal. */ static TputsRetType gl_tputs_putchar(TputsArgType c) { char ch = c; #if TPUTS_RETURNS_VALUE return gl_print_raw_string(tputs_gl, 1, &ch, 1); #else (void) gl_print_raw_string(tputs_gl, 1, &ch, 1); #endif } #endif /*....................................................................... * Move the terminal cursor n characters to the left or right. * * Input: * gl GetLine * The resource object of this program. * n int number of positions to the right (> 0) or left (< 0). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_terminal_move_cursor(GetLine *gl, int n) { int cur_row, cur_col; /* The current terminal row and column index of */ /* the cursor wrt the start of the input line. */ int new_row, new_col; /* The target terminal row and column index of */ /* the cursor wrt the start of the input line. */ /* * Do nothing if the input line isn't currently displayed. In this * case, the cursor will be moved to the right place when the line * is next redisplayed. */ if(!gl->displayed) return 0; /* * How far can we move left? */ if(gl->term_curpos + n < 0) n = gl->term_curpos; /* * Break down the current and target cursor locations into rows and columns. */ cur_row = gl->term_curpos / gl->ncolumn; cur_col = gl->term_curpos % gl->ncolumn; new_row = (gl->term_curpos + n) / gl->ncolumn; new_col = (gl->term_curpos + n) % gl->ncolumn; /* * Move down to the next line. */ for(; cur_row < new_row; cur_row++) { if(gl_print_control_sequence(gl, 1, gl->down)) return 1; }; /* * Move up to the previous line. */ for(; cur_row > new_row; cur_row--) { if(gl_print_control_sequence(gl, 1, gl->up)) return 1; }; /* * Move to the right within the target line? */ if(cur_col < new_col) { #ifdef USE_TERMINFO /* * Use a parameterized control sequence if it generates less control * characters (guess based on ANSI terminal termcap entry). */ if(gl->right_n != NULL && new_col - cur_col > 1) { if(gl_print_control_sequence(gl, 1, tparm((char *)gl->right_n, (long)(new_col - cur_col), 0l, 0l, 0l, 0l, 0l, 0l, 0l, 0l))) return 1; } else #endif { for(; cur_col < new_col; cur_col++) { if(gl_print_control_sequence(gl, 1, gl->right)) return 1; }; }; /* * Move to the left within the target line? */ } else if(cur_col > new_col) { #ifdef USE_TERMINFO /* * Use a parameterized control sequence if it generates less control * characters (guess based on ANSI terminal termcap entry). */ if(gl->left_n != NULL && cur_col - new_col > 3) { if(gl_print_control_sequence(gl, 1, tparm((char *)gl->left_n, (long)(cur_col - new_col), 0l, 0l, 0l, 0l, 0l, 0l, 0l, 0l))) return 1; } else #endif { for(; cur_col > new_col; cur_col--) { if(gl_print_control_sequence(gl, 1, gl->left)) return 1; }; }; } /* * Update the recorded position of the terminal cursor. */ gl->term_curpos += n; return 0; } /*....................................................................... * Write a character to the terminal after expanding tabs and control * characters to their multi-character representations. * * Input: * gl GetLine * The resource object of this program. * c char The character to be output. * pad char Many terminals have the irritating feature that * when one writes a character in the last column of * of the terminal, the cursor isn't wrapped to the * start of the next line until one more character * is written. Some terminals don't do this, so * after such a write, we don't know where the * terminal is unless we output an extra character. * This argument specifies the character to write. * If at the end of the input line send '\0' or a * space, and a space will be written. Otherwise, * pass the next character in the input line * following the one being written. * Output: * return int 0 - OK. */ static int gl_print_char(GetLine *gl, char c, char pad) { char string[TAB_WIDTH + 4]; /* A work area for composing compound strings */ int nchar; /* The number of terminal characters */ int i; /* * Check for special characters. */ if(c == '\t') { /* * How many spaces do we need to represent a tab at the current terminal * column? */ nchar = gl_displayed_tab_width(gl, gl->term_curpos); /* * Compose the tab string. */ for(i=0; iterm_curpos += nchar; /* * Keep a record of the number of characters in the terminal version * of the input line. */ if(gl->term_curpos > gl->term_len) gl->term_len = gl->term_curpos; /* * If the new character ended exactly at the end of a line, * most terminals won't move the cursor onto the next line until we * have written a character on the next line, so append an extra * space then move the cursor back. */ if(gl->term_curpos % gl->ncolumn == 0) { int term_curpos = gl->term_curpos; if(gl_print_char(gl, pad ? pad : ' ', ' ') || gl_set_term_curpos(gl, term_curpos)) return 1; }; return 0; } /*....................................................................... * Write a string to the terminal after expanding tabs and control * characters to their multi-character representations. * * Input: * gl GetLine * The resource object of this program. * string char * The string to be output. * pad char Many terminals have the irritating feature that * when one writes a character in the last column of * of the terminal, the cursor isn't wrapped to the * start of the next line until one more character * is written. Some terminals don't do this, so * after such a write, we don't know where the * terminal is unless we output an extra character. * This argument specifies the character to write. * If at the end of the input line send '\0' or a * space, and a space will be written. Otherwise, * pass the next character in the input line * following the one being written. * Output: * return int 0 - OK. */ static int gl_print_string(GetLine *gl, const char *string, char pad) { const char *cptr; /* A pointer into string[] */ for(cptr=string; *cptr; cptr++) { char nextc = cptr[1]; if(gl_print_char(gl, *cptr, nextc ? nextc : pad)) return 1; }; return 0; } /*....................................................................... * Move the terminal cursor position. * * Input: * gl GetLine * The resource object of this library. * term_curpos int The destination terminal cursor position. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_set_term_curpos(GetLine *gl, int term_curpos) { return gl_terminal_move_cursor(gl, term_curpos - gl->term_curpos); } /*....................................................................... * This is an action function that moves the buffer cursor one character * left, and updates the terminal cursor to match. */ static KT_KEY_FN(gl_cursor_left) { return gl_place_cursor(gl, gl->buff_curpos - count); } /*....................................................................... * This is an action function that moves the buffer cursor one character * right, and updates the terminal cursor to match. */ static KT_KEY_FN(gl_cursor_right) { return gl_place_cursor(gl, gl->buff_curpos + count); } /*....................................................................... * This is an action function that toggles between overwrite and insert * mode. */ static KT_KEY_FN(gl_insert_mode) { gl->insert = !gl->insert; return 0; } /*....................................................................... * This is an action function which moves the cursor to the beginning of * the line. */ static KT_KEY_FN(gl_beginning_of_line) { return gl_place_cursor(gl, 0); } /*....................................................................... * This is an action function which moves the cursor to the end of * the line. */ static KT_KEY_FN(gl_end_of_line) { return gl_place_cursor(gl, gl->ntotal); } /*....................................................................... * This is an action function which deletes the entire contents of the * current line. */ static KT_KEY_FN(gl_delete_line) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Copy the contents of the line to the cut buffer. */ strcpy(gl->cutbuf, gl->line); /* * Clear the buffer. */ gl_truncate_buffer(gl, 0); /* * Move the terminal cursor to just after the prompt. */ if(gl_place_cursor(gl, 0)) return 1; /* * Clear from the end of the prompt to the end of the terminal. */ if(gl_truncate_display(gl)) return 1; return 0; } /*....................................................................... * This is an action function which deletes all characters between the * current cursor position and the end of the line. */ static KT_KEY_FN(gl_kill_line) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Copy the part of the line that is about to be deleted to the cut buffer. */ strcpy(gl->cutbuf, gl->line + gl->buff_curpos); /* * Terminate the buffered line at the current cursor position. */ gl_truncate_buffer(gl, gl->buff_curpos); /* * Clear the part of the line that follows the cursor. */ if(gl_truncate_display(gl)) return 1; /* * Explicitly reset the cursor position to allow vi command mode * constraints on its position to be set. */ return gl_place_cursor(gl, gl->buff_curpos); } /*....................................................................... * This is an action function which deletes all characters between the * start of the line and the current cursor position. */ static KT_KEY_FN(gl_backward_kill_line) { /* * How many characters are to be deleted from before the cursor? */ int nc = gl->buff_curpos - gl->insert_curpos; if (!nc) return 0; /* * Move the cursor to the start of the line, or in vi input mode, * the start of the sub-line at which insertion started, and delete * up to the old cursor position. */ return gl_place_cursor(gl, gl->insert_curpos) || gl_delete_chars(gl, nc, gl->editor == GL_EMACS_MODE || gl->vi.command); } /*....................................................................... * This is an action function which moves the cursor forward by a word. */ static KT_KEY_FN(gl_forward_word) { return gl_place_cursor(gl, gl_nth_word_end_forward(gl, count) + (gl->editor==GL_EMACS_MODE)); } /*....................................................................... * This is an action function which moves the cursor forward to the start * of the next word. */ static KT_KEY_FN(gl_forward_to_word) { return gl_place_cursor(gl, gl_nth_word_start_forward(gl, count)); } /*....................................................................... * This is an action function which moves the cursor backward by a word. */ static KT_KEY_FN(gl_backward_word) { return gl_place_cursor(gl, gl_nth_word_start_backward(gl, count)); } /*....................................................................... * Delete one or more characters, starting with the one under the cursor. * * Input: * gl GetLine * The resource object of this library. * nc int The number of characters to delete. * cut int If true, copy the characters to the cut buffer. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_delete_chars(GetLine *gl, int nc, int cut) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * If there are fewer than nc characters following the cursor, limit * nc to the number available. */ if(gl->buff_curpos + nc > gl->ntotal) nc = gl->ntotal - gl->buff_curpos; /* * Copy the about to be deleted region to the cut buffer. */ if(cut) { memcpy(gl->cutbuf, gl->line + gl->buff_curpos, nc); gl->cutbuf[nc] = '\0'; } /* * Nothing to delete? */ if(nc <= 0) return 0; /* * In vi overwrite mode, restore any previously overwritten characters * from the undo buffer. */ if(gl->editor == GL_VI_MODE && !gl->vi.command && !gl->insert) { /* * How many of the characters being deleted can be restored from the * undo buffer? */ int nrestore = gl->buff_curpos + nc <= gl->vi.undo.ntotal ? nc : gl->vi.undo.ntotal - gl->buff_curpos; /* * Restore any available characters. */ if(nrestore > 0) { gl_buffer_string(gl, gl->vi.undo.line + gl->buff_curpos, nrestore, gl->buff_curpos); }; /* * If their were insufficient characters in the undo buffer, then this * implies that we are deleting from the end of the line, so we need * to terminate the line either where the undo buffer ran out, or if * we are deleting from beyond the end of the undo buffer, at the current * cursor position. */ if(nc != nrestore) { gl_truncate_buffer(gl, (gl->vi.undo.ntotal > gl->buff_curpos) ? gl->vi.undo.ntotal : gl->buff_curpos); }; } else { /* * Copy the remaining part of the line back over the deleted characters. */ gl_remove_from_buffer(gl, gl->buff_curpos, nc); }; /* * Redraw the remaining characters following the cursor. */ if(gl_print_string(gl, gl->line + gl->buff_curpos, '\0')) return 1; /* * Clear to the end of the terminal. */ if(gl_truncate_display(gl)) return 1; /* * Place the cursor at the start of where the deletion was performed. */ return gl_place_cursor(gl, gl->buff_curpos); } /*....................................................................... * This is an action function which deletes character(s) under the * cursor without moving the cursor. */ static KT_KEY_FN(gl_forward_delete_char) { /* * Delete 'count' characters. */ return gl_delete_chars(gl, count, gl->vi.command); } /*....................................................................... * This is an action function which deletes character(s) under the * cursor and moves the cursor back one character. */ static KT_KEY_FN(gl_backward_delete_char) { /* * Restrict the deletion count to the number of characters that * precede the insertion point. */ if(count > gl->buff_curpos - gl->insert_curpos) count = gl->buff_curpos - gl->insert_curpos; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); return gl_cursor_left(gl, count, NULL) || gl_delete_chars(gl, count, gl->vi.command); } /*....................................................................... * Starting from the cursor position delete to the specified column. */ static KT_KEY_FN(gl_delete_to_column) { if (--count >= gl->buff_curpos) return gl_forward_delete_char(gl, count - gl->buff_curpos, NULL); else return gl_backward_delete_char(gl, gl->buff_curpos - count, NULL); } /*....................................................................... * Starting from the cursor position delete characters to a matching * parenthesis. */ static KT_KEY_FN(gl_delete_to_parenthesis) { int curpos = gl_index_of_matching_paren(gl); if(curpos >= 0) { gl_save_for_undo(gl); if(curpos >= gl->buff_curpos) return gl_forward_delete_char(gl, curpos - gl->buff_curpos + 1, NULL); else return gl_backward_delete_char(gl, ++gl->buff_curpos - curpos + 1, NULL); }; return 0; } /*....................................................................... * This is an action function which deletes from the cursor to the end * of the word that the cursor is either in or precedes. */ static KT_KEY_FN(gl_forward_delete_word) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * In emacs mode delete to the end of the word. In vi mode delete to the * start of the net word. */ if(gl->editor == GL_EMACS_MODE) { return gl_delete_chars(gl, gl_nth_word_end_forward(gl,count) - gl->buff_curpos + 1, 1); } else { return gl_delete_chars(gl, gl_nth_word_start_forward(gl,count) - gl->buff_curpos, gl->vi.command); }; } /*....................................................................... * This is an action function which deletes the word that precedes the * cursor. */ static KT_KEY_FN(gl_backward_delete_word) { /* * Keep a record of the current cursor position. */ int buff_curpos = gl->buff_curpos; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Move back 'count' words. */ if(gl_backward_word(gl, count, NULL)) return 1; /* * Delete from the new cursor position to the original one. */ return gl_delete_chars(gl, buff_curpos - gl->buff_curpos, gl->editor == GL_EMACS_MODE || gl->vi.command); } /*....................................................................... * Searching in a given direction, delete to the count'th * instance of a specified or queried character, in the input line. * * Input: * gl GetLine * The getline resource object. * count int The number of times to search. * c char The character to be searched for, or '\0' if * the character should be read from the user. * forward int True if searching forward. * onto int True if the search should end on top of the * character, false if the search should stop * one character before the character in the * specified search direction. * change int If true, this function is being called upon * to do a vi change command, in which case the * user will be left in insert mode after the * deletion. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_delete_find(GetLine *gl, int count, char c, int forward, int onto, int change) { /* * Search for the character, and abort the deletion if not found. */ int pos = gl_find_char(gl, count, forward, onto, c); if(pos < 0) return 0; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Allow the cursor to be at the end of the line if this is a change * command. */ if(change) gl->vi.command = 0; /* * Delete the appropriate span of characters. */ if(forward) { if(gl_delete_chars(gl, pos - gl->buff_curpos + 1, 1)) return 1; } else { int buff_curpos = gl->buff_curpos; if(gl_place_cursor(gl, pos) || gl_delete_chars(gl, buff_curpos - gl->buff_curpos, 1)) return 1; }; /* * If this is a change operation, switch the insert mode. */ if(change && gl_vi_insert(gl, 0, NULL)) return 1; return 0; } /*....................................................................... * This is an action function which deletes forward from the cursor up to and * including a specified character. */ static KT_KEY_FN(gl_forward_delete_find) { return gl_delete_find(gl, count, '\0', 1, 1, 0); } /*....................................................................... * This is an action function which deletes backward from the cursor back to * and including a specified character. */ static KT_KEY_FN(gl_backward_delete_find) { return gl_delete_find(gl, count, '\0', 0, 1, 0); } /*....................................................................... * This is an action function which deletes forward from the cursor up to but * not including a specified character. */ static KT_KEY_FN(gl_forward_delete_to) { return gl_delete_find(gl, count, '\0', 1, 0, 0); } /*....................................................................... * This is an action function which deletes backward from the cursor back to * but not including a specified character. */ static KT_KEY_FN(gl_backward_delete_to) { return gl_delete_find(gl, count, '\0', 0, 0, 0); } /*....................................................................... * This is an action function which deletes to a character specified by a * previous search. */ static KT_KEY_FN(gl_delete_refind) { return gl_delete_find(gl, count, gl->vi.find_char, gl->vi.find_forward, gl->vi.find_onto, 0); } /*....................................................................... * This is an action function which deletes to a character specified by a * previous search, but in the opposite direction. */ static KT_KEY_FN(gl_delete_invert_refind) { return gl_delete_find(gl, count, gl->vi.find_char, !gl->vi.find_forward, gl->vi.find_onto, 0); } /*....................................................................... * This is an action function which converts the characters in the word * following the cursor to upper case. */ static KT_KEY_FN(gl_upcase_word) { /* * Locate the count'th word ending after the cursor. */ int last = gl_nth_word_end_forward(gl, count); /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Upcase characters from the current cursor position to 'last'. */ while(gl->buff_curpos <= last) { char *cptr = gl->line + gl->buff_curpos; /* * Convert the character to upper case? */ if(islower((int)(unsigned char) *cptr)) gl_buffer_char(gl, toupper((int) *cptr), gl->buff_curpos); gl->buff_curpos++; /* * Write the possibly modified character back. Note that for non-modified * characters we want to do this as well, so as to advance the cursor. */ if(gl_print_char(gl, *cptr, cptr[1])) return 1; }; return gl_place_cursor(gl, gl->buff_curpos); /* bounds check */ } /*....................................................................... * This is an action function which converts the characters in the word * following the cursor to lower case. */ static KT_KEY_FN(gl_downcase_word) { /* * Locate the count'th word ending after the cursor. */ int last = gl_nth_word_end_forward(gl, count); /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Upcase characters from the current cursor position to 'last'. */ while(gl->buff_curpos <= last) { char *cptr = gl->line + gl->buff_curpos; /* * Convert the character to upper case? */ if(isupper((int)(unsigned char) *cptr)) gl_buffer_char(gl, tolower((int) *cptr), gl->buff_curpos); gl->buff_curpos++; /* * Write the possibly modified character back. Note that for non-modified * characters we want to do this as well, so as to advance the cursor. */ if(gl_print_char(gl, *cptr, cptr[1])) return 1; }; return gl_place_cursor(gl, gl->buff_curpos); /* bounds check */ } /*....................................................................... * This is an action function which converts the first character of the * following word to upper case, in order to capitalize the word, and * leaves the cursor at the end of the word. */ static KT_KEY_FN(gl_capitalize_word) { char *cptr; /* &gl->line[gl->buff_curpos] */ int first; /* True for the first letter of the word */ int i; /* * Keep a record of the current insert mode and the cursor position. */ int insert = gl->insert; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * We want to overwrite the modified word. */ gl->insert = 0; /* * Capitalize 'count' words. */ for(i=0; ibuff_curpos < gl->ntotal; i++) { int pos = gl->buff_curpos; /* * If we are not already within a word, skip to the start of the word. */ for(cptr = gl->line + pos ; posntotal && !gl_is_word_char((int) *cptr); pos++, cptr++) ; /* * Move the cursor to the new position. */ if(gl_place_cursor(gl, pos)) return 1; /* * While searching for the end of the word, change lower case letters * to upper case. */ for(first=1; gl->buff_curposntotal && gl_is_word_char((int) *cptr); gl->buff_curpos++, cptr++) { /* * Convert the character to upper case? */ if(first) { if(islower((int)(unsigned char) *cptr)) gl_buffer_char(gl, toupper((int) *cptr), cptr - gl->line); } else { if(isupper((int)(unsigned char) *cptr)) gl_buffer_char(gl, tolower((int) *cptr), cptr - gl->line); }; first = 0; /* * Write the possibly modified character back. Note that for non-modified * characters we want to do this as well, so as to advance the cursor. */ if(gl_print_char(gl, *cptr, cptr[1])) return 1; }; }; /* * Restore the insertion mode. */ gl->insert = insert; return gl_place_cursor(gl, gl->buff_curpos); /* bounds check */ } /*....................................................................... * This is an action function which redraws the current line. */ static KT_KEY_FN(gl_redisplay) { /* * Keep a record of the current cursor position. */ int buff_curpos = gl->buff_curpos; /* * Do nothing if there is no line to be redisplayed. */ if(gl->endline) return 0; /* * Erase the current input line. */ if(gl_erase_line(gl)) return 1; /* * Display the current prompt. */ if(gl_display_prompt(gl)) return 1; /* * Render the part of the line that the user has typed in so far. */ if(gl_print_string(gl, gl->line, '\0')) return 1; /* * Restore the cursor position. */ if(gl_place_cursor(gl, buff_curpos)) return 1; /* * Mark the redisplay operation as having been completed. */ gl->redisplay = 0; /* * Flush the redisplayed line to the terminal. */ return gl_flush_output(gl); } /*....................................................................... * This is an action function which clears the display and redraws the * input line from the home position. */ static KT_KEY_FN(gl_clear_screen) { /* * Home the cursor and clear from there to the end of the display. */ if(gl_print_control_sequence(gl, gl->nline, gl->home) || gl_print_control_sequence(gl, gl->nline, gl->clear_eod)) return 1; /* * The input line is no longer displayed. */ gl_line_erased(gl); /* * Arrange for the input line to be redisplayed. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This is an action function which swaps the character under the cursor * with the character to the left of the cursor. */ static KT_KEY_FN(gl_transpose_chars) { char from[3]; /* The original string of 2 characters */ char swap[3]; /* The swapped string of two characters */ /* * If we are at the beginning or end of the line, there aren't two * characters to swap. */ if(gl->buff_curpos < 1 || gl->buff_curpos >= gl->ntotal) return 0; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Get the original and swapped strings of the two characters. */ from[0] = gl->line[gl->buff_curpos - 1]; from[1] = gl->line[gl->buff_curpos]; from[2] = '\0'; swap[0] = gl->line[gl->buff_curpos]; swap[1] = gl->line[gl->buff_curpos - 1]; swap[2] = '\0'; /* * Move the cursor to the start of the two characters. */ if(gl_place_cursor(gl, gl->buff_curpos-1)) return 1; /* * Swap the two characters in the buffer. */ gl_buffer_char(gl, swap[0], gl->buff_curpos); gl_buffer_char(gl, swap[1], gl->buff_curpos+1); /* * If the sum of the displayed width of the two characters * in their current and final positions is the same, swapping can * be done by just overwriting with the two swapped characters. */ if(gl_displayed_string_width(gl, from, -1, gl->term_curpos) == gl_displayed_string_width(gl, swap, -1, gl->term_curpos)) { int insert = gl->insert; gl->insert = 0; if(gl_print_char(gl, swap[0], swap[1]) || gl_print_char(gl, swap[1], gl->line[gl->buff_curpos+2])) return 1; gl->insert = insert; /* * If the swapped substring has a different displayed size, we need to * redraw everything after the first of the characters. */ } else { if(gl_print_string(gl, gl->line + gl->buff_curpos, '\0') || gl_truncate_display(gl)) return 1; }; /* * Advance the cursor to the character after the swapped pair. */ return gl_place_cursor(gl, gl->buff_curpos + 2); } /*....................................................................... * This is an action function which sets a mark at the current cursor * location. */ static KT_KEY_FN(gl_set_mark) { gl->buff_mark = gl->buff_curpos; return 0; } /*....................................................................... * This is an action function which swaps the mark location for the * cursor location. */ static KT_KEY_FN(gl_exchange_point_and_mark) { /* * Get the old mark position, and limit to the extent of the input * line. */ int old_mark = gl->buff_mark <= gl->ntotal ? gl->buff_mark : gl->ntotal; /* * Make the current cursor position the new mark. */ gl->buff_mark = gl->buff_curpos; /* * Move the cursor to the old mark position. */ return gl_place_cursor(gl, old_mark); } /*....................................................................... * This is an action function which deletes the characters between the * mark and the cursor, recording them in gl->cutbuf for later pasting. */ static KT_KEY_FN(gl_kill_region) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Limit the mark to be within the line. */ if(gl->buff_mark > gl->ntotal) gl->buff_mark = gl->ntotal; /* * If there are no characters between the cursor and the mark, simply clear * the cut buffer. */ if(gl->buff_mark == gl->buff_curpos) { gl->cutbuf[0] = '\0'; return 0; }; /* * If the mark is before the cursor, swap the cursor and the mark. */ if(gl->buff_mark < gl->buff_curpos && gl_exchange_point_and_mark(gl,1,NULL)) return 1; /* * Delete the characters. */ if(gl_delete_chars(gl, gl->buff_mark - gl->buff_curpos, 1)) return 1; /* * Make the mark the same as the cursor position. */ gl->buff_mark = gl->buff_curpos; return 0; } /*....................................................................... * This is an action function which records the characters between the * mark and the cursor, in gl->cutbuf for later pasting. */ static KT_KEY_FN(gl_copy_region_as_kill) { int ca, cb; /* The indexes of the first and last characters in the region */ int mark; /* The position of the mark */ /* * Get the position of the mark, limiting it to lie within the line. */ mark = gl->buff_mark > gl->ntotal ? gl->ntotal : gl->buff_mark; /* * If there are no characters between the cursor and the mark, clear * the cut buffer. */ if(mark == gl->buff_curpos) { gl->cutbuf[0] = '\0'; return 0; }; /* * Get the line indexes of the first and last characters in the region. */ if(mark < gl->buff_curpos) { ca = mark; cb = gl->buff_curpos - 1; } else { ca = gl->buff_curpos; cb = mark - 1; }; /* * Copy the region to the cut buffer. */ memcpy(gl->cutbuf, gl->line + ca, cb + 1 - ca); gl->cutbuf[cb + 1 - ca] = '\0'; return 0; } /*....................................................................... * This is an action function which inserts the contents of the cut * buffer at the current cursor location. */ static KT_KEY_FN(gl_yank) { int i; /* * Set the mark at the current location. */ gl->buff_mark = gl->buff_curpos; /* * Do nothing else if the cut buffer is empty. */ if(gl->cutbuf[0] == '\0') return gl_ring_bell(gl, 1, NULL); /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Insert the string count times. */ for(i=0; icutbuf)) return 1; }; /* * gl_add_string_to_line() leaves the cursor after the last character that * was pasted, whereas vi leaves the cursor over the last character pasted. */ if(gl->editor == GL_VI_MODE && gl_cursor_left(gl, 1, NULL)) return 1; return 0; } /*....................................................................... * This is an action function which inserts the contents of the cut * buffer one character beyond the current cursor location. */ static KT_KEY_FN(gl_append_yank) { int was_command = gl->vi.command; int i; /* * If the cut buffer is empty, ring the terminal bell. */ if(gl->cutbuf[0] == '\0') return gl_ring_bell(gl, 1, NULL); /* * Set the mark at the current location + 1. */ gl->buff_mark = gl->buff_curpos + 1; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Arrange to paste the text in insert mode after the current character. */ if(gl_vi_append(gl, 0, NULL)) return 1; /* * Insert the string count times. */ for(i=0; icutbuf)) return 1; }; /* * Switch back to command mode if necessary. */ if(was_command) gl_vi_command_mode(gl); return 0; } /*....................................................................... * Attempt to ask the terminal for its current size. On systems that * don't support the TIOCWINSZ ioctl() for querying the terminal size, * the current values of gl->ncolumn and gl->nrow are returned. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * ncolumn int * The number of columns will be assigned to *ncolumn. * nline int * The number of lines will be assigned to *nline. */ static void gl_query_size(GetLine *gl, int *ncolumn, int *nline) { #ifdef TIOCGWINSZ /* * Query the new terminal window size. Ignore invalid responses. */ struct winsize size; if(ioctl(gl->output_fd, TIOCGWINSZ, &size) == 0 && size.ws_row > 0 && size.ws_col > 0) { *ncolumn = size.ws_col; *nline = size.ws_row; return; }; #endif /* * Return the existing values. */ *ncolumn = gl->ncolumn; *nline = gl->nline; return; } /*....................................................................... * Query the size of the terminal, and if it has changed, redraw the * current input line accordingly. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int _gl_update_size(GetLine *gl) { int ncolumn, nline; /* The new size of the terminal */ /* * Query the new terminal window size. */ gl_query_size(gl, &ncolumn, &nline); /* * Update gl and the displayed line to fit the new dimensions. */ return gl_handle_tty_resize(gl, ncolumn, nline); } /*....................................................................... * Redraw the current input line to account for a change in the terminal * size. Also install the new size in gl. * * Input: * gl GetLine * The resource object of gl_get_line(). * ncolumn int The new number of columns. * nline int The new number of lines. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_handle_tty_resize(GetLine *gl, int ncolumn, int nline) { /* * If the input device isn't a terminal, just record the new size. */ if(!gl->is_term) { gl->nline = nline; gl->ncolumn = ncolumn; /* * Has the size actually changed? */ } else if(ncolumn != gl->ncolumn || nline != gl->nline) { /* * If we are currently editing a line, erase it. */ if(gl_erase_line(gl)) return 1; /* * Update the recorded window size. */ gl->nline = nline; gl->ncolumn = ncolumn; /* * Arrange for the input line to be redrawn before the next character * is read from the terminal. */ gl_queue_redisplay(gl); }; return 0; } /*....................................................................... * This is the action function that recalls the previous line in the * history buffer. */ static KT_KEY_FN(gl_up_history) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Forget any previous recall session. */ gl->preload_id = 0; /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * We don't want a search prefix for this function. */ if(_glh_search_prefix(gl->glh, gl->line, 0)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; /* * Recall the count'th next older line in the history list. If the first one * fails we can return since nothing has changed, otherwise we must continue * and update the line state. */ if(_glh_find_backwards(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; while(--count && _glh_find_backwards(gl->glh, gl->line, gl->linelen+1)) ; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange to have the cursor placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This is the action function that recalls the next line in the * history buffer. */ static KT_KEY_FN(gl_down_history) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * If no search is currently in progress continue a previous recall * session from a previous entered line if possible. */ if(_glh_line_id(gl->glh, 0) == 0 && gl->preload_id) { _glh_recall_line(gl->glh, gl->preload_id, gl->line, gl->linelen+1); gl->preload_id = 0; } else { /* * We don't want a search prefix for this function. */ if(_glh_search_prefix(gl->glh, gl->line, 0)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; /* * Recall the count'th next newer line in the history list. If the first one * fails we can return since nothing has changed otherwise we must continue * and update the line state. */ if(_glh_find_forwards(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; while(--count && _glh_find_forwards(gl->glh, gl->line, gl->linelen+1)) ; }; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange to have the cursor placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This is the action function that recalls the previous line in the * history buffer whos prefix matches the characters that currently * precede the cursor. By setting count=-1, this can be used internally * to force searching for the prefix used in the last search. */ static KT_KEY_FN(gl_history_search_backward) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Forget any previous recall session. */ gl->preload_id = 0; /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * If a prefix search isn't already in progress, replace the search * prefix to the string that precedes the cursor. In vi command mode * include the character that is under the cursor in the string. If * count<0 keep the previous search prefix regardless, so as to force * a repeat search even if the last command wasn't a history command. */ if(count >= 0 && !_glh_search_active(gl->glh) && _glh_search_prefix(gl->glh, gl->line, gl->buff_curpos + (gl->editor==GL_VI_MODE && gl->ntotal>0))) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; /* * Search backwards for a match to the part of the line which precedes the * cursor. */ if(_glh_find_backwards(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange to have the cursor placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This is the action function that recalls the previous line in the * history buffer who's prefix matches that specified in an earlier call * to gl_history_search_backward() or gl_history_search_forward(). */ static KT_KEY_FN(gl_history_re_search_backward) { return gl_history_search_backward(gl, -1, NULL); } /*....................................................................... * This is the action function that recalls the next line in the * history buffer who's prefix matches that specified in the earlier call * to gl_history_search_backward) which started the history search. * By setting count=-1, this can be used internally to force searching * for the prefix used in the last search. */ static KT_KEY_FN(gl_history_search_forward) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * If a prefix search isn't already in progress, replace the search * prefix to the string that precedes the cursor. In vi command mode * include the character that is under the cursor in the string. If * count<0 keep the previous search prefix regardless, so as to force * a repeat search even if the last command wasn't a history command. */ if(count >= 0 && !_glh_search_active(gl->glh) && _glh_search_prefix(gl->glh, gl->line, gl->buff_curpos + (gl->editor==GL_VI_MODE && gl->ntotal>0))) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; /* * Search forwards for the next matching line. */ if(_glh_find_forwards(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange for the cursor to be placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This is the action function that recalls the next line in the * history buffer who's prefix matches that specified in an earlier call * to gl_history_search_backward() or gl_history_search_forward(). */ static KT_KEY_FN(gl_history_re_search_forward) { return gl_history_search_forward(gl, -1, NULL); } #ifdef HIDE_FILE_SYSTEM /*....................................................................... * The following function is used as the default completion handler when * the filesystem is to be hidden. It simply reports no completions. */ static CPL_MATCH_FN(gl_no_completions) { return 0; } #endif /*....................................................................... * This is the tab completion function that completes the filename that * precedes the cursor position. Its callback data argument must be a * pointer to a GlCplCallback containing the completion callback function * and its callback data, or NULL to use the builtin filename completer. */ static KT_KEY_FN(gl_complete_word) { CplMatches *matches; /* The possible completions */ int suffix_len; /* The length of the completion extension */ int cont_len; /* The length of any continuation suffix */ int nextra; /* The number of characters being added to the */ /* total length of the line. */ int buff_pos; /* The buffer index at which the completion is */ /* to be inserted. */ int waserr = 0; /* True after errors */ /* * Get the container of the completion callback and its callback data. */ GlCplCallback *cb = data ? (GlCplCallback *) data : &gl->cplfn; /* * In vi command mode, switch to append mode so that the character under * the cursor is included in the completion (otherwise people can't * complete at the end of the line). */ if(gl->vi.command && gl_vi_append(gl, 0, NULL)) return 1; /* * Get the cursor position at which the completion is to be inserted. */ buff_pos = gl->buff_curpos; /* * Perform the completion. */ matches = cpl_complete_word(gl->cpl, gl->line, gl->buff_curpos, cb->data, cb->fn); /* * No matching completions? */ if(!matches) { waserr = gl_print_info(gl, cpl_last_error(gl->cpl), GL_END_INFO); /* * Are there any completions? */ } else if(matches->nmatch >= 1) { /* * If there any ambiguous matches, report them, starting on a new line. */ if(matches->nmatch > 1 && gl->echo) { if(_gl_normal_io(gl) || _cpl_output_completions(matches, gl_write_fn, gl, gl->ncolumn)) waserr = 1; }; /* * Get the length of the suffix and any continuation suffix to add to it. */ suffix_len = strlen(matches->suffix); cont_len = strlen(matches->cont_suffix); /* * If there is an unambiguous match, and the continuation suffix ends in * a newline, strip that newline and arrange to have getline return * after this action function returns. */ if(matches->nmatch==1 && cont_len > 0 && matches->cont_suffix[cont_len - 1] == '\n') { cont_len--; if(gl_newline(gl, 1, NULL)) waserr = 1; }; /* * Work out the number of characters that are to be added. */ nextra = suffix_len + cont_len; /* * Is there anything to be added? */ if(!waserr && nextra) { /* * Will there be space for the expansion in the line buffer? */ if(gl->ntotal + nextra < gl->linelen) { /* * Make room to insert the filename extension. */ gl_make_gap_in_buffer(gl, gl->buff_curpos, nextra); /* * Insert the filename extension. */ gl_buffer_string(gl, matches->suffix, suffix_len, gl->buff_curpos); /* * Add the terminating characters. */ gl_buffer_string(gl, matches->cont_suffix, cont_len, gl->buff_curpos + suffix_len); /* * Place the cursor position at the end of the completion. */ gl->buff_curpos += nextra; /* * If we don't have to redisplay the whole line, redisplay the part * of the line which follows the original cursor position, and place * the cursor at the end of the completion. */ if(gl->displayed) { if(gl_truncate_display(gl) || gl_print_string(gl, gl->line + buff_pos, '\0') || gl_place_cursor(gl, gl->buff_curpos)) waserr = 1; }; } else { (void) gl_print_info(gl, "Insufficient room in line for file completion.", GL_END_INFO); waserr = 1; }; }; }; /* * If any output had to be written to the terminal, then editing will * have been suspended, make sure that we are back in raw line editing * mode before returning. */ if(_gl_raw_io(gl, 1)) waserr = 1; return 0; } #ifndef HIDE_FILE_SYSTEM /*....................................................................... * This is the function that expands the filename that precedes the * cursor position. It expands ~user/ expressions, $envvar expressions, * and wildcards. */ static KT_KEY_FN(gl_expand_filename) { char *start_path; /* The pointer to the start of the pathname in */ /* gl->line[]. */ FileExpansion *result; /* The results of the filename expansion */ int pathlen; /* The length of the pathname being expanded */ int length; /* The number of characters needed to display the */ /* expanded files. */ int nextra; /* The number of characters to be added */ int i,j; /* * In vi command mode, switch to append mode so that the character under * the cursor is included in the completion (otherwise people can't * complete at the end of the line). */ if(gl->vi.command && gl_vi_append(gl, 0, NULL)) return 1; /* * Locate the start of the filename that precedes the cursor position. */ start_path = _pu_start_of_path(gl->line, gl->buff_curpos); if(!start_path) return 1; /* * Get the length of the string that is to be expanded. */ pathlen = gl->buff_curpos - (start_path - gl->line); /* * Attempt to expand it. */ result = ef_expand_file(gl->ef, start_path, pathlen); /* * If there was an error, report the error on a new line. */ if(!result) return gl_print_info(gl, ef_last_error(gl->ef), GL_END_INFO); /* * If no files matched, report this as well. */ if(result->nfile == 0 || !result->exists) return gl_print_info(gl, "No files match.", GL_END_INFO); /* * If in vi command mode, preserve the current line for potential use by * vi-undo. */ gl_save_for_undo(gl); /* * Work out how much space we will need to display all of the matching * filenames, taking account of the space that we need to place between * them, and the number of additional '\' characters needed to escape * spaces, tabs and backslash characters in the individual filenames. */ length = 0; for(i=0; infile; i++) { char *file = result->files[i]; while(*file) { int c = *file++; switch(c) { case ' ': case '\t': case '\\': case '*': case '?': case '[': length++; /* Count extra backslash characters */ }; length++; /* Count the character itself */ }; length++; /* Count the space that follows each filename */ }; /* * Work out the number of characters that are to be added. */ nextra = length - pathlen; /* * Will there be space for the expansion in the line buffer? */ if(gl->ntotal + nextra >= gl->linelen) { return gl_print_info(gl, "Insufficient room in line for file expansion.", GL_END_INFO); } else { /* * Do we need to move the part of the line that followed the unexpanded * filename? */ if(nextra > 0) { gl_make_gap_in_buffer(gl, gl->buff_curpos, nextra); } else if(nextra < 0) { gl->buff_curpos += nextra; gl_remove_from_buffer(gl, gl->buff_curpos, -nextra); }; /* * Insert the filenames, separated by spaces, and with internal spaces, * tabs and backslashes escaped with backslashes. */ for(i=0,j=start_path - gl->line; infile; i++) { char *file = result->files[i]; while(*file) { int c = *file++; switch(c) { case ' ': case '\t': case '\\': case '*': case '?': case '[': gl_buffer_char(gl, '\\', j++); }; gl_buffer_char(gl, c, j++); }; gl_buffer_char(gl, ' ', j++); }; }; /* * Redisplay the part of the line which follows the start of * the original filename. */ if(gl_place_cursor(gl, start_path - gl->line) || gl_truncate_display(gl) || gl_print_string(gl, start_path, start_path[length])) return 1; /* * Move the cursor to the end of the expansion. */ return gl_place_cursor(gl, (start_path - gl->line) + length); } #endif #ifndef HIDE_FILE_SYSTEM /*....................................................................... * This is the action function that lists glob expansions of the * filename that precedes the cursor position. It expands ~user/ * expressions, $envvar expressions, and wildcards. */ static KT_KEY_FN(gl_list_glob) { char *start_path; /* The pointer to the start of the pathname in */ /* gl->line[]. */ FileExpansion *result; /* The results of the filename expansion */ int pathlen; /* The length of the pathname being expanded */ /* * Locate the start of the filename that precedes the cursor position. */ start_path = _pu_start_of_path(gl->line, gl->buff_curpos); if(!start_path) return 1; /* * Get the length of the string that is to be expanded. */ pathlen = gl->buff_curpos - (start_path - gl->line); /* * Attempt to expand it. */ result = ef_expand_file(gl->ef, start_path, pathlen); /* * If there was an error, report it. */ if(!result) { return gl_print_info(gl, ef_last_error(gl->ef), GL_END_INFO); /* * If no files matched, report this as well. */ } else if(result->nfile == 0 || !result->exists) { return gl_print_info(gl, "No files match.", GL_END_INFO); /* * List the matching expansions. */ } else if(gl->echo) { if(gl_start_newline(gl, 1) || _ef_output_expansions(result, gl_write_fn, gl, gl->ncolumn)) return 1; gl_queue_redisplay(gl); }; return 0; } #endif /*....................................................................... * Return non-zero if a character should be considered a part of a word. * * Input: * c int The character to be tested. * Output: * return int True if the character should be considered part of a word. */ static int gl_is_word_char(int c) { return isalnum((int)(unsigned char)c) || strchr(GL_WORD_CHARS, c) != NULL; } /*....................................................................... * Override the builtin file-completion callback that is bound to the * "complete_word" action function. * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * This is passed to match_fn() whenever it is * called. It could, for example, point to a * symbol table where match_fn() could look * for possible completions. * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * report matching symbols. * Output: * return int 0 - OK. * 1 - Error. */ int gl_customize_completion(GetLine *gl, void *data, CplMatchFn *match_fn) { sigset_t oldset; /* The signals that were blocked on entry to this function */ /* * Check the arguments. */ if(!gl || !match_fn) { if(gl) _err_record_msg(gl->err, "NULL argument", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Record the new completion function and its callback data. */ gl->cplfn.fn = match_fn; gl->cplfn.data = data; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return 0; } /*....................................................................... * Change the terminal (or stream) that getline interacts with. * * Input: * gl GetLine * The resource object of the command-line input * module. * input_fp FILE * The stdio stream to read from. * output_fp FILE * The stdio stream to write to. * term char * The terminal type. This can be NULL if * either or both of input_fp and output_fp don't * refer to a terminal. Otherwise it should refer * to an entry in the terminal information database. * Output: * return int 0 - OK. * 1 - Error. */ int gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_change_terminal() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_change_terminal(gl, input_fp, output_fp, term); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_change_terminal() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term) { int is_term = 0; /* True if both input_fd and output_fd are associated */ /* with a terminal. */ /* * Require that input_fp and output_fp both be valid. */ if(!input_fp || !output_fp) { gl_print_info(gl, "Can't change terminal. Bad input/output stream(s).", GL_END_INFO); return 1; }; /* * Are we displacing an existing terminal (as opposed to setting the * initial terminal)? */ if(gl->input_fd >= 0) { /* * Make sure to leave the previous terminal in a usable state. */ if(_gl_normal_io(gl)) return 1; /* * Remove the displaced terminal from the list of fds to watch. */ #ifdef HAVE_SELECT FD_CLR(gl->input_fd, &gl->rfds); #endif }; /* * Record the file descriptors and streams. */ gl->input_fp = input_fp; gl->input_fd = fileno(input_fp); gl->output_fp = output_fp; gl->output_fd = fileno(output_fp); /* * If needed, expand the record of the maximum file-descriptor that might * need to be monitored with select(). */ #ifdef HAVE_SELECT if(gl->input_fd > gl->max_fd) gl->max_fd = gl->input_fd; #endif /* * Disable terminal interaction until we have enough info to interact * with the terminal. */ gl->is_term = 0; /* * For terminal editing, we need both output_fd and input_fd to refer to * a terminal. While we can't verify that they both point to the same * terminal, we can verify that they point to terminals. If the user * sets the TERM environment variable to "dumb", treat a terminal as * a non-interactive I/O stream. */ is_term = (isatty(gl->input_fd) && isatty(gl->output_fd)) && !(term && strcmp(term, "dumb")==0); /* * If we are interacting with a terminal and no terminal type has been * specified, treat it as a generic ANSI terminal. */ if(is_term && !term) term = "ansi"; /* * Make a copy of the terminal type string. */ if(term != gl->term) { /* * Delete any old terminal type string. */ if(gl->term) { free(gl->term); gl->term = NULL; }; /* * Make a copy of the new terminal-type string, if any. */ if(term) { gl->term = (char *) malloc(strlen(term)+1); if(gl->term) strcpy(gl->term, term); }; }; /* * Clear any terminal-specific key bindings that were taken from the * settings of the last terminal. */ _kt_clear_bindings(gl->bindings, KTB_TERM); /* * If we have a terminal install new bindings for it. */ if(is_term) { /* * Get the current settings of the terminal. */ if(tcgetattr(gl->input_fd, &gl->oldattr)) { _err_record_msg(gl->err, "tcgetattr error", END_ERR_MSG); return 1; }; /* * If we don't set this now, gl_control_strings() won't know * that it is talking to a terminal. */ gl->is_term = 1; /* * Lookup the terminal control string and size information. */ if(gl_control_strings(gl, term)) { gl->is_term = 0; return 1; }; /* * Bind terminal-specific keys. */ if(gl_bind_terminal_keys(gl)) return 1; }; /* * Assume that the caller has given us a terminal in a sane state. */ gl->io_mode = GL_NORMAL_MODE; /* * Switch into the currently configured I/O mode. */ if(_gl_io_mode(gl, gl->io_mode)) return 1; return 0; } /*....................................................................... * Set up terminal-specific key bindings. * * Input: * gl GetLine * The resource object of the command-line input * module. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_bind_terminal_keys(GetLine *gl) { /* * Install key-bindings for the special terminal characters. */ if(gl_bind_control_char(gl, KTB_TERM, gl->oldattr.c_cc[VINTR], "user-interrupt") || gl_bind_control_char(gl, KTB_TERM, gl->oldattr.c_cc[VQUIT], "abort") || gl_bind_control_char(gl, KTB_TERM, gl->oldattr.c_cc[VSUSP], "suspend")) return 1; /* * In vi-mode, arrange for the above characters to be seen in command * mode. */ if(gl->editor == GL_VI_MODE) { if(gl_bind_control_char(gl, KTB_TERM, MAKE_META(gl->oldattr.c_cc[VINTR]), "user-interrupt") || gl_bind_control_char(gl, KTB_TERM, MAKE_META(gl->oldattr.c_cc[VQUIT]), "abort") || gl_bind_control_char(gl, KTB_TERM, MAKE_META(gl->oldattr.c_cc[VSUSP]), "suspend")) return 1; }; /* * Non-universal special keys. */ #ifdef VLNEXT if(gl_bind_control_char(gl, KTB_TERM, gl->oldattr.c_cc[VLNEXT], "literal-next")) return 1; #else if(_kt_set_keybinding(gl->bindings, KTB_TERM, "^V", "literal-next")) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; #endif /* * Bind action functions to the terminal-specific arrow keys * looked up by gl_control_strings(). */ if(_gl_bind_arrow_keys(gl)) return 1; return 0; } /*....................................................................... * This function is normally bound to control-D. When it is invoked within * a line it deletes the character which follows the cursor. When invoked * at the end of the line it lists possible file completions, and when * invoked on an empty line it causes gl_get_line() to return EOF. This * function emulates the one that is normally bound to control-D by tcsh. */ static KT_KEY_FN(gl_del_char_or_list_or_eof) { /* * If we have an empty line arrange to return EOF. */ if(gl->ntotal < 1) { gl_record_status(gl, GLR_EOF, 0); return 1; /* * If we are at the end of the line list possible completions. */ } else if(gl->buff_curpos >= gl->ntotal) { return gl_list_completions(gl, 1, NULL); /* * Within the line delete the character that follows the cursor. */ } else { /* * If in vi command mode, first preserve the current line for potential use * by vi-undo. */ gl_save_for_undo(gl); /* * Delete 'count' characters. */ return gl_forward_delete_char(gl, count, NULL); }; } /*....................................................................... * This function is normally bound to control-D in vi mode. When it is * invoked within a line it lists possible file completions, and when * invoked on an empty line it causes gl_get_line() to return EOF. This * function emulates the one that is normally bound to control-D by tcsh. */ static KT_KEY_FN(gl_list_or_eof) { /* * If we have an empty line arrange to return EOF. */ if(gl->ntotal < 1) { gl_record_status(gl, GLR_EOF, 0); return 1; /* * Otherwise list possible completions. */ } else { return gl_list_completions(gl, 1, NULL); }; } /*....................................................................... * List possible completions of the word that precedes the cursor. The * callback data argument must either be NULL to select the default * file completion callback, or be a GlCplCallback object containing the * completion callback function to call. */ static KT_KEY_FN(gl_list_completions) { int waserr = 0; /* True after errors */ /* * Get the container of the completion callback and its callback data. */ GlCplCallback *cb = data ? (GlCplCallback *) data : &gl->cplfn; /* * Get the list of possible completions. */ CplMatches *matches = cpl_complete_word(gl->cpl, gl->line, gl->buff_curpos, cb->data, cb->fn); /* * No matching completions? */ if(!matches) { waserr = gl_print_info(gl, cpl_last_error(gl->cpl), GL_END_INFO); /* * List the matches. */ } else if(matches->nmatch > 0 && gl->echo) { if(_gl_normal_io(gl) || _cpl_output_completions(matches, gl_write_fn, gl, gl->ncolumn)) waserr = 1; }; /* * If any output had to be written to the terminal, then editing will * have been suspended, make sure that we are back in raw line editing * mode before returning. */ if(_gl_raw_io(gl, 1)) waserr = 1; return waserr; } /*....................................................................... * Where the user has used the symbolic arrow-key names to specify * arrow key bindings, bind the specified action functions to the default * and terminal specific arrow key sequences. * * Input: * gl GetLine * The getline resource object. * Output: * return int 0 - OK. * 1 - Error. */ static int _gl_bind_arrow_keys(GetLine *gl) { /* * Process each of the arrow keys. */ if(_gl_rebind_arrow_key(gl, "up", gl->u_arrow, "^[[A", "^[OA") || _gl_rebind_arrow_key(gl, "down", gl->d_arrow, "^[[B", "^[OB") || _gl_rebind_arrow_key(gl, "left", gl->l_arrow, "^[[D", "^[OD") || _gl_rebind_arrow_key(gl, "right", gl->r_arrow, "^[[C", "^[OC")) return 1; return 0; } /*....................................................................... * Lookup the action function of a symbolic arrow-key binding, and bind * it to the terminal-specific and default arrow-key sequences. Note that * we don't trust the terminal-specified key sequences to be correct. * The main reason for this is that on some machines the xterm terminfo * entry is for hardware X-terminals, rather than xterm terminal emulators * and the two terminal types emit different character sequences when the * their cursor keys are pressed. As a result we also supply a couple * of default key sequences. * * Input: * gl GetLine * The resource object of gl_get_line(). * name char * The symbolic name of the arrow key. * term_seq char * The terminal-specific arrow-key sequence. * def_seq1 char * The first default arrow-key sequence. * def_seq2 char * The second arrow-key sequence. * Output: * return int 0 - OK. * 1 - Error. */ static int _gl_rebind_arrow_key(GetLine *gl, const char *name, const char *term_seq, const char *def_seq1, const char *def_seq2) { KeySym *keysym; /* The binding-table entry matching the arrow-key name */ int nsym; /* The number of ambiguous matches */ /* * Lookup the key binding for the symbolic name of the arrow key. This * will either be the default action, or a user provided one. */ if(_kt_lookup_keybinding(gl->bindings, name, strlen(name), &keysym, &nsym) == KT_EXACT_MATCH) { /* * Get the action function. */ KtAction *action = keysym->actions + keysym->binder; KtKeyFn *fn = action->fn; void *data = action->data; /* * Bind this to each of the specified key sequences. */ if((term_seq && _kt_set_keyfn(gl->bindings, KTB_TERM, term_seq, fn, data)) || (def_seq1 && _kt_set_keyfn(gl->bindings, KTB_NORM, def_seq1, fn, data)) || (def_seq2 && _kt_set_keyfn(gl->bindings, KTB_NORM, def_seq2, fn, data))) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; }; return 0; } /*....................................................................... * Read getline configuration information from a given file. * * Input: * gl GetLine * The getline resource object. * filename const char * The name of the file to read configuration * information from. The contents of this file * are as described in the gl_get_line(3) man * page for the default ~/.teclarc configuration * file. * who KtBinder Who bindings are to be installed for. * Output: * return int 0 - OK. * 1 - Irrecoverable error. */ static int _gl_read_config_file(GetLine *gl, const char *filename, KtBinder who) { /* * If filesystem access is to be excluded, configuration files can't * be read. */ #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(gl->err, "Can't read configuration files without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FileExpansion *expansion; /* The expansion of the filename */ FILE *fp; /* The opened file */ int waserr = 0; /* True if an error occurred while reading */ int lineno = 1; /* The line number being processed */ /* * Check the arguments. */ if(!gl || !filename) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Expand the filename. */ expansion = ef_expand_file(gl->ef, filename, -1); if(!expansion) { gl_print_info(gl, "Unable to expand ", filename, " (", ef_last_error(gl->ef), ").", GL_END_INFO); return 1; }; /* * Attempt to open the file. */ fp = fopen(expansion->files[0], "r"); /* * It isn't an error for there to be no configuration file. */ if(!fp) return 0; /* * Parse the contents of the file. */ while(!waserr && !feof(fp)) waserr = _gl_parse_config_line(gl, fp, glc_file_getc, filename, who, &lineno); /* * Bind action functions to the terminal-specific arrow keys. */ if(_gl_bind_arrow_keys(gl)) return 1; /* * Clean up. */ (void) fclose(fp); return waserr; #endif } /*....................................................................... * Read GetLine configuration information from a string. The contents of * the string are the same as those described in the gl_get_line(3) * man page for the contents of the ~/.teclarc configuration file. */ static int _gl_read_config_string(GetLine *gl, const char *buffer, KtBinder who) { const char *bptr; /* A pointer into buffer[] */ int waserr = 0; /* True if an error occurred while reading */ int lineno = 1; /* The line number being processed */ /* * Check the arguments. */ if(!gl || !buffer) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Get a pointer to the start of the buffer. */ bptr = buffer; /* * Parse the contents of the buffer. */ while(!waserr && *bptr) waserr = _gl_parse_config_line(gl, &bptr, glc_buff_getc, "", who, &lineno); /* * Bind action functions to the terminal-specific arrow keys. */ if(_gl_bind_arrow_keys(gl)) return 1; return waserr; } /*....................................................................... * Parse the next line of a getline configuration file. * * Input: * gl GetLine * The getline resource object. * stream void * The pointer representing the stream to be read * by getc_fn(). * getc_fn GlcGetcFn * A callback function which when called with * 'stream' as its argument, returns the next * unread character from the stream. * origin const char * The name of the entity being read (eg. a * file name). * who KtBinder Who bindings are to be installed for. * Input/Output: * lineno int * The line number being processed is to be * maintained in *lineno. * Output: * return int 0 - OK. * 1 - Irrecoverable error. */ static int _gl_parse_config_line(GetLine *gl, void *stream, GlcGetcFn *getc_fn, const char *origin, KtBinder who, int *lineno) { char buffer[GL_CONF_BUFLEN+1]; /* The input line buffer */ char *argv[GL_CONF_MAXARG]; /* The argument list */ int argc = 0; /* The number of arguments in argv[] */ int c; /* A character from the file */ int escaped = 0; /* True if the next character is escaped */ int i; /* * Skip spaces and tabs. */ do c = getc_fn(stream); while(c==' ' || c=='\t'); /* * Comments extend to the end of the line. */ if(c=='#') do c = getc_fn(stream); while(c != '\n' && c != EOF); /* * Ignore empty lines. */ if(c=='\n' || c==EOF) { (*lineno)++; return 0; }; /* * Record the buffer location of the start of the first argument. */ argv[argc] = buffer; /* * Read the rest of the line, stopping early if a comment is seen, or * the buffer overflows, and replacing sequences of spaces with a * '\0', and recording the thus terminated string as an argument. */ i = 0; while(i= GL_CONF_MAXARG) { gl_report_config_error(gl, origin, *lineno, "Too many arguments."); do c = getc_fn(stream); while(c!='\n' && c!=EOF); /* Skip past eol */ return 0; }; argv[argc] = buffer + i; /* * The next character was preceded by spaces, so it isn't escaped. */ escaped = 0; } else { /* * If we hit an unescaped backslash, this means that we should arrange * to treat the next character like a simple alphabetical character. */ if(c=='\\' && !escaped) { escaped = 1; /* * Splice lines where the newline is escaped. */ } else if(c=='\n' && escaped) { (*lineno)++; /* * Record a normal character, preserving any preceding backslash. */ } else { if(escaped) buffer[i++] = '\\'; if(i>=GL_CONF_BUFLEN) break; escaped = 0; buffer[i++] = c; }; /* * Get the next character. */ c = getc_fn(stream); }; }; /* * Did the buffer overflow? */ if(i>=GL_CONF_BUFLEN) { gl_report_config_error(gl, origin, *lineno, "Line too long."); return 0; }; /* * The first argument should be a command name. */ if(strcmp(argv[0], "bind") == 0) { const char *action = NULL; /* A NULL action removes a keybinding */ const char *keyseq = NULL; switch(argc) { case 3: action = argv[2]; case 2: /* Note the intentional fallthrough */ keyseq = argv[1]; /* * Attempt to record the new keybinding. */ if(_kt_set_keybinding(gl->bindings, who, keyseq, action)) { gl_report_config_error(gl, origin, *lineno, _kt_last_error(gl->bindings)); }; break; default: gl_report_config_error(gl, origin, *lineno, "Wrong number of arguments."); }; } else if(strcmp(argv[0], "edit-mode") == 0) { if(argc == 2 && strcmp(argv[1], "emacs") == 0) { gl_change_editor(gl, GL_EMACS_MODE); } else if(argc == 2 && strcmp(argv[1], "vi") == 0) { gl_change_editor(gl, GL_VI_MODE); } else if(argc == 2 && strcmp(argv[1], "none") == 0) { gl_change_editor(gl, GL_NO_EDITOR); } else { gl_report_config_error(gl, origin, *lineno, "The argument of editor should be vi or emacs."); }; } else if(strcmp(argv[0], "nobeep") == 0) { gl->silence_bell = 1; } else { gl_report_config_error(gl, origin, *lineno, "Unknown command name."); }; /* * Skip any trailing comment. */ while(c != '\n' && c != EOF) c = getc_fn(stream); (*lineno)++; return 0; } /*....................................................................... * This is a private function of _gl_parse_config_line() which prints * out an error message about the contents of the line, prefixed by the * name of the origin of the line and its line number. * * Input: * gl GetLine * The resource object of gl_get_line(). * origin const char * The name of the entity being read (eg. a * file name). * lineno int The line number at which the error occurred. * errmsg const char * The error message. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_report_config_error(GetLine *gl, const char *origin, int lineno, const char *errmsg) { char lnum[20]; /* A buffer in which to render a single integer */ /* * Convert the line number into a string. */ sprintf(lnum, "%d", lineno); /* * Have the string printed on the terminal. */ return gl_print_info(gl, origin, ":", lnum, ": ", errmsg, GL_END_INFO); } /*....................................................................... * This is the _gl_parse_config_line() callback function which reads the * next character from a configuration file. */ static GLC_GETC_FN(glc_file_getc) { return fgetc((FILE *) stream); } /*....................................................................... * This is the _gl_parse_config_line() callback function which reads the * next character from a buffer. Its stream argument is a pointer to a * variable which is, in turn, a pointer into the buffer being read from. */ static GLC_GETC_FN(glc_buff_getc) { const char **lptr = (char const **) stream; return **lptr ? *(*lptr)++ : EOF; } #ifndef HIDE_FILE_SYSTEM /*....................................................................... * When this action is triggered, it arranges to temporarily read command * lines from the regular file whos name precedes the cursor. * The current line is first discarded. */ static KT_KEY_FN(gl_read_from_file) { char *start_path; /* The pointer to the start of the pathname in */ /* gl->line[]. */ FileExpansion *result; /* The results of the filename expansion */ int pathlen; /* The length of the pathname being expanded */ /* * Locate the start of the filename that precedes the cursor position. */ start_path = _pu_start_of_path(gl->line, gl->buff_curpos); if(!start_path) return 1; /* * Get the length of the pathname string. */ pathlen = gl->buff_curpos - (start_path - gl->line); /* * Attempt to expand the pathname. */ result = ef_expand_file(gl->ef, start_path, pathlen); /* * If there was an error, report the error on a new line. */ if(!result) { return gl_print_info(gl, ef_last_error(gl->ef), GL_END_INFO); /* * If no files matched, report this as well. */ } else if(result->nfile == 0 || !result->exists) { return gl_print_info(gl, "No files match.", GL_END_INFO); /* * Complain if more than one file matches. */ } else if(result->nfile > 1) { return gl_print_info(gl, "More than one file matches.", GL_END_INFO); /* * Disallow input from anything but normal files. In principle we could * also support input from named pipes. Terminal files would be a problem * since we wouldn't know the terminal type, and other types of files * might cause the library to lock up. */ } else if(!_pu_path_is_file(result->files[0])) { return gl_print_info(gl, "Not a normal file.", GL_END_INFO); } else { /* * Attempt to open and install the specified file for reading. */ gl->file_fp = fopen(result->files[0], "r"); if(!gl->file_fp) { return gl_print_info(gl, "Unable to open: ", result->files[0], GL_END_INFO); }; /* * If needed, expand the record of the maximum file-descriptor that might * need to be monitored with select(). */ #ifdef HAVE_SELECT if(fileno(gl->file_fp) > gl->max_fd) gl->max_fd = fileno(gl->file_fp); #endif /* * Is non-blocking I/O needed? */ if(gl->raw_mode && gl->io_mode==GL_SERVER_MODE && gl_nonblocking_io(gl, fileno(gl->file_fp))) { gl_revert_input(gl); return gl_print_info(gl, "Can't read file %s with non-blocking I/O", result->files[0]); }; /* * Inform the user what is happening. */ if(gl_print_info(gl, "files[0], ">", GL_END_INFO)) return 1; }; return 0; } #endif /*....................................................................... * Close any temporary file that is being used for input. * * Input: * gl GetLine * The getline resource object. */ static void gl_revert_input(GetLine *gl) { if(gl->file_fp) fclose(gl->file_fp); gl->file_fp = NULL; gl->endline = 1; } /*....................................................................... * This is the action function that recalls the oldest line in the * history buffer. */ static KT_KEY_FN(gl_beginning_of_history) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Forget any previous recall session. */ gl->preload_id = 0; /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * Recall the next oldest line in the history list. */ if(_glh_oldest_line(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange to have the cursor placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * If a history session is currently in progress, this action function * recalls the line that was being edited when the session started. If * no history session is in progress, it does nothing. */ static KT_KEY_FN(gl_end_of_history) { /* * In vi mode, switch to command mode, since the user is very * likely to want to move around newly recalled lines. */ gl_vi_command_mode(gl); /* * Forget any previous recall session. */ gl->preload_id = 0; /* * Record the key sequence number of this search action. */ gl->last_search = gl->keyseq_count; /* * Recall the next oldest line in the history list. */ if(_glh_current_line(gl->glh, gl->line, gl->linelen+1) == NULL) return 0; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Arrange to have the cursor placed at the end of the new line. */ gl->buff_curpos = gl->ntotal; /* * Erase and display the new line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * This action function is treated specially, in that its count argument * is set to the end keystroke of the keysequence that activated it. * It accumulates a numeric argument, adding one digit on each call in * which the last keystroke was a numeric digit. */ static KT_KEY_FN(gl_digit_argument) { /* * Was the last keystroke a digit? */ int is_digit = isdigit((int)(unsigned char) count); /* * In vi command mode, a lone '0' means goto-start-of-line. */ if(gl->vi.command && gl->number < 0 && count == '0') return gl_beginning_of_line(gl, count, NULL); /* * Are we starting to accumulate a new number? */ if(gl->number < 0 || !is_digit) gl->number = 0; /* * Was the last keystroke a digit? */ if(is_digit) { /* * Read the numeric value of the digit, without assuming ASCII. */ int n; char s[2]; s[0] = count; s[1] = '\0'; n = atoi(s); /* * Append the new digit. */ gl->number = gl->number * 10 + n; }; return 0; } /*....................................................................... * The newline action function sets gl->endline to tell * gl_get_input_line() that the line is now complete. */ static KT_KEY_FN(gl_newline) { GlhLineID id; /* The last history line recalled while entering this line */ /* * Flag the line as ended. */ gl->endline = 1; /* * Record the next position in the history buffer, for potential * recall by an action function on the next call to gl_get_line(). */ id = _glh_line_id(gl->glh, 1); if(id) gl->preload_id = id; return 0; } /*....................................................................... * The 'repeat' action function sets gl->endline to tell * gl_get_input_line() that the line is now complete, and records the * ID of the next history line in gl->preload_id so that the next call * to gl_get_input_line() will preload the line with that history line. */ static KT_KEY_FN(gl_repeat_history) { gl->endline = 1; gl->preload_id = _glh_line_id(gl->glh, 1); gl->preload_history = 1; return 0; } /*....................................................................... * Flush unwritten characters to the terminal. * * Input: * gl GetLine * The getline resource object. * Output: * return int 0 - OK. * 1 - Either an error occured, or the output * blocked and non-blocking I/O is being used. * See gl->rtn_status for details. */ static int gl_flush_output(GetLine *gl) { /* * Record the fact that we are about to write to the terminal. */ gl->pending_io = GLP_WRITE; /* * Attempt to flush the output to the terminal. */ errno = 0; switch(_glq_flush_queue(gl->cq, gl->flush_fn, gl)) { case GLQ_FLUSH_DONE: return gl->redisplay && !gl->postpone && gl_redisplay(gl, 1, NULL); break; case GLQ_FLUSH_AGAIN: /* Output blocked */ gl_record_status(gl, GLR_BLOCKED, BLOCKED_ERRNO); return 1; break; default: /* Abort the line if an error occurs */ gl_record_status(gl, errno==EINTR ? GLR_SIGNAL : GLR_ERROR, errno); return 1; break; }; } /*....................................................................... * This is the callback which _glq_flush_queue() uses to write buffered * characters to the terminal. */ static GL_WRITE_FN(gl_flush_terminal) { int ndone = 0; /* The number of characters written so far */ /* * Get the line-editor resource object. */ GetLine *gl = (GetLine *) data; /* * Transfer the latest array of characters to stdio. */ while(ndone < n) { int nnew = write(gl->output_fd, s, n-ndone); /* * If the write was successful, add to the recorded number of bytes * that have now been written. */ if(nnew > 0) { ndone += nnew; /* * If a signal interrupted the call, restart the write(), since all of * the signals that gl_get_line() has been told to watch for are * currently blocked. */ } else if(errno == EINTR) { continue; /* * If we managed to write something before an I/O error occurred, or * output blocked before anything was written, report the number of * bytes that were successfully written before this happened. */ } else if(ndone > 0 #if defined(EAGAIN) || errno==EAGAIN #endif #if defined(EWOULDBLOCK) || errno==EWOULDBLOCK #endif ) { return ndone; /* * To get here, an error must have occurred before anything new could * be written. */ } else { return -1; }; }; /* * To get here, we must have successfully written the number of * bytes that was specified. */ return n; } /*....................................................................... * Change the style of editing to emulate a given editor. * * Input: * gl GetLine * The getline resource object. * editor GlEditor The type of editor to emulate. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_change_editor(GetLine *gl, GlEditor editor) { /* * Install the default key-bindings of the requested editor. */ switch(editor) { case GL_EMACS_MODE: _kt_clear_bindings(gl->bindings, KTB_NORM); _kt_clear_bindings(gl->bindings, KTB_TERM); (void) _kt_add_bindings(gl->bindings, KTB_NORM, gl_emacs_bindings, sizeof(gl_emacs_bindings)/sizeof(gl_emacs_bindings[0])); break; case GL_VI_MODE: _kt_clear_bindings(gl->bindings, KTB_NORM); _kt_clear_bindings(gl->bindings, KTB_TERM); (void) _kt_add_bindings(gl->bindings, KTB_NORM, gl_vi_bindings, sizeof(gl_vi_bindings)/sizeof(gl_vi_bindings[0])); break; case GL_NO_EDITOR: break; default: _err_record_msg(gl->err, "Unknown editor", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Record the new editing mode. */ gl->editor = editor; gl->vi.command = 0; /* Start in input mode */ gl->insert_curpos = 0; /* * Reinstate terminal-specific bindings. */ if(gl->editor != GL_NO_EDITOR && gl->input_fp) (void) gl_bind_terminal_keys(gl); return 0; } /*....................................................................... * This is an action function that switches to editing using emacs bindings */ static KT_KEY_FN(gl_emacs_editing_mode) { return gl_change_editor(gl, GL_EMACS_MODE); } /*....................................................................... * This is an action function that switches to editing using vi bindings */ static KT_KEY_FN(gl_vi_editing_mode) { return gl_change_editor(gl, GL_VI_MODE); } /*....................................................................... * This is the action function that switches to insert mode. */ static KT_KEY_FN(gl_vi_insert) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Switch to vi insert mode. */ gl->insert = 1; gl->vi.command = 0; gl->insert_curpos = gl->buff_curpos; return 0; } /*....................................................................... * This is an action function that switches to overwrite mode. */ static KT_KEY_FN(gl_vi_overwrite) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Switch to vi overwrite mode. */ gl->insert = 0; gl->vi.command = 0; gl->insert_curpos = gl->buff_curpos; return 0; } /*....................................................................... * This action function toggles the case of the character under the * cursor. */ static KT_KEY_FN(gl_change_case) { int i; /* * Keep a record of the current insert mode and the cursor position. */ int insert = gl->insert; /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * We want to overwrite the modified word. */ gl->insert = 0; /* * Toggle the case of 'count' characters. */ for(i=0; ibuff_curpos < gl->ntotal; i++) { char *cptr = gl->line + gl->buff_curpos++; /* * Convert the character to upper case? */ if(islower((int)(unsigned char) *cptr)) gl_buffer_char(gl, toupper((int) *cptr), cptr - gl->line); else if(isupper((int)(unsigned char) *cptr)) gl_buffer_char(gl, tolower((int) *cptr), cptr - gl->line); /* * Write the possibly modified character back. Note that for non-modified * characters we want to do this as well, so as to advance the cursor. */ if(gl_print_char(gl, *cptr, cptr[1])) return 1; }; /* * Restore the insertion mode. */ gl->insert = insert; return gl_place_cursor(gl, gl->buff_curpos); /* bounds check */ } /*....................................................................... * This is the action function which implements the vi-style action which * moves the cursor to the start of the line, then switches to insert mode. */ static KT_KEY_FN(gl_vi_insert_at_bol) { gl_save_for_undo(gl); return gl_beginning_of_line(gl, 0, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * This is the action function which implements the vi-style action which * moves the cursor to the end of the line, then switches to insert mode * to allow text to be appended to the line. */ static KT_KEY_FN(gl_vi_append_at_eol) { gl_save_for_undo(gl); gl->vi.command = 0; /* Allow cursor at EOL */ return gl_end_of_line(gl, 0, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * This is the action function which implements the vi-style action which * moves the cursor to right one then switches to insert mode, thus * allowing text to be appended after the next character. */ static KT_KEY_FN(gl_vi_append) { gl_save_for_undo(gl); gl->vi.command = 0; /* Allow cursor at EOL */ return gl_cursor_right(gl, 1, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * This action function moves the cursor to the column specified by the * numeric argument. Column indexes start at 1. */ static KT_KEY_FN(gl_goto_column) { return gl_place_cursor(gl, count - 1); } /*....................................................................... * Starting with the character under the cursor, replace 'count' * characters with the next character that the user types. */ static KT_KEY_FN(gl_vi_replace_char) { char c; /* The replacement character */ int i; /* * Keep a record of the current insert mode. */ int insert = gl->insert; /* * Get the replacement character. */ if(gl->vi.repeat.active) { c = gl->vi.repeat.input_char; } else { if(gl_read_terminal(gl, 1, &c)) return 1; gl->vi.repeat.input_char = c; }; /* * Are there 'count' characters to be replaced? */ if(gl->ntotal - gl->buff_curpos >= count) { /* * If in vi command mode, preserve the current line for potential * use by vi-undo. */ gl_save_for_undo(gl); /* * Temporarily switch to overwrite mode. */ gl->insert = 0; /* * Overwrite the current character plus count-1 subsequent characters * with the replacement character. */ for(i=0; iinsert = insert; }; return gl_place_cursor(gl, gl->buff_curpos); /* bounds check */ } /*....................................................................... * This is an action function which changes all characters between the * current cursor position and the end of the line. */ static KT_KEY_FN(gl_vi_change_rest_of_line) { gl_save_for_undo(gl); gl->vi.command = 0; /* Allow cursor at EOL */ return gl_kill_line(gl, count, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * This is an action function which changes all characters between the * start of the line and the current cursor position. */ static KT_KEY_FN(gl_vi_change_to_bol) { return gl_backward_kill_line(gl,count,NULL) || gl_vi_insert(gl,0,NULL); } /*....................................................................... * This is an action function which deletes the entire contents of the * current line and switches to insert mode. */ static KT_KEY_FN(gl_vi_change_line) { return gl_delete_line(gl,count,NULL) || gl_vi_insert(gl,0,NULL); } /*....................................................................... * Starting from the cursor position and looking towards the end of the * line, copy 'count' characters to the cut buffer. */ static KT_KEY_FN(gl_forward_copy_char) { /* * Limit the count to the number of characters available. */ if(gl->buff_curpos + count >= gl->ntotal) count = gl->ntotal - gl->buff_curpos; if(count < 0) count = 0; /* * Copy the characters to the cut buffer. */ memcpy(gl->cutbuf, gl->line + gl->buff_curpos, count); gl->cutbuf[count] = '\0'; return 0; } /*....................................................................... * Starting from the character before the cursor position and looking * backwards towards the start of the line, copy 'count' characters to * the cut buffer. */ static KT_KEY_FN(gl_backward_copy_char) { /* * Limit the count to the number of characters available. */ if(count > gl->buff_curpos) count = gl->buff_curpos; if(count < 0) count = 0; gl_place_cursor(gl, gl->buff_curpos - count); /* * Copy the characters to the cut buffer. */ memcpy(gl->cutbuf, gl->line + gl->buff_curpos, count); gl->cutbuf[count] = '\0'; return 0; } /*....................................................................... * Starting from the cursor position copy to the specified column into the * cut buffer. */ static KT_KEY_FN(gl_copy_to_column) { if (--count >= gl->buff_curpos) return gl_forward_copy_char(gl, count - gl->buff_curpos, NULL); else return gl_backward_copy_char(gl, gl->buff_curpos - count, NULL); } /*....................................................................... * Starting from the cursor position copy characters up to a matching * parenthesis into the cut buffer. */ static KT_KEY_FN(gl_copy_to_parenthesis) { int curpos = gl_index_of_matching_paren(gl); if(curpos >= 0) { gl_save_for_undo(gl); if(curpos >= gl->buff_curpos) return gl_forward_copy_char(gl, curpos - gl->buff_curpos + 1, NULL); else return gl_backward_copy_char(gl, ++gl->buff_curpos - curpos + 1, NULL); }; return 0; } /*....................................................................... * Starting from the cursor position copy the rest of the line into the * cut buffer. */ static KT_KEY_FN(gl_copy_rest_of_line) { /* * Copy the characters to the cut buffer. */ memcpy(gl->cutbuf, gl->line + gl->buff_curpos, gl->ntotal - gl->buff_curpos); gl->cutbuf[gl->ntotal - gl->buff_curpos] = '\0'; return 0; } /*....................................................................... * Copy from the beginning of the line to the cursor position into the * cut buffer. */ static KT_KEY_FN(gl_copy_to_bol) { /* * Copy the characters to the cut buffer. */ memcpy(gl->cutbuf, gl->line, gl->buff_curpos); gl->cutbuf[gl->buff_curpos] = '\0'; gl_place_cursor(gl, 0); return 0; } /*....................................................................... * Copy the entire line into the cut buffer. */ static KT_KEY_FN(gl_copy_line) { /* * Copy the characters to the cut buffer. */ memcpy(gl->cutbuf, gl->line, gl->ntotal); gl->cutbuf[gl->ntotal] = '\0'; return 0; } /*....................................................................... * Search forwards for the next character that the user enters. */ static KT_KEY_FN(gl_forward_find_char) { int pos = gl_find_char(gl, count, 1, 1, '\0'); return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Search backwards for the next character that the user enters. */ static KT_KEY_FN(gl_backward_find_char) { int pos = gl_find_char(gl, count, 0, 1, '\0'); return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Search forwards for the next character that the user enters. Move up to, * but not onto, the found character. */ static KT_KEY_FN(gl_forward_to_char) { int pos = gl_find_char(gl, count, 1, 0, '\0'); return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Search backwards for the next character that the user enters. Move back to, * but not onto, the found character. */ static KT_KEY_FN(gl_backward_to_char) { int pos = gl_find_char(gl, count, 0, 0, '\0'); return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Searching in a given direction, return the index of a given (or * read) character in the input line, or the character that precedes * it in the specified search direction. Return -1 if not found. * * Input: * gl GetLine * The getline resource object. * count int The number of times to search. * forward int True if searching forward. * onto int True if the search should end on top of the * character, false if the search should stop * one character before the character in the * specified search direction. * c char The character to be sought, or '\0' if the * character should be read from the user. * Output: * return int The index of the character in gl->line[], or * -1 if not found. */ static int gl_find_char(GetLine *gl, int count, int forward, int onto, char c) { int pos; /* The index reached in searching the input line */ int i; /* * Get a character from the user? */ if(!c) { /* * If we are in the process of repeating a previous change command, substitute * the last find character. */ if(gl->vi.repeat.active) { c = gl->vi.find_char; } else { if(gl_read_terminal(gl, 1, &c)) return -1; /* * Record the details of the new search, for use by repeat finds. */ gl->vi.find_forward = forward; gl->vi.find_onto = onto; gl->vi.find_char = c; }; }; /* * Which direction should we search? */ if(forward) { /* * Search forwards 'count' times for the character, starting with the * character that follows the cursor. */ for(i=0, pos=gl->buff_curpos; intotal; i++) { /* * Advance past the last match (or past the current cursor position * on the first search). */ pos++; /* * Search for the next instance of c. */ for( ; posntotal && c!=gl->line[pos]; pos++) ; }; /* * If the character was found and we have been requested to return the * position of the character that precedes the desired character, then * we have gone one character too far. */ if(!onto && posntotal) pos--; } else { /* * Search backwards 'count' times for the character, starting with the * character that precedes the cursor. */ for(i=0, pos=gl->buff_curpos; i= gl->insert_curpos; i++) { /* * Step back one from the last match (or from the current cursor * position on the first search). */ pos--; /* * Search for the next instance of c. */ for( ; pos>=gl->insert_curpos && c!=gl->line[pos]; pos--) ; }; /* * If the character was found and we have been requested to return the * position of the character that precedes the desired character, then * we have gone one character too far. */ if(!onto && pos>=gl->insert_curpos) pos++; }; /* * If found, return the cursor position of the count'th match. * Otherwise ring the terminal bell. */ if(pos >= gl->insert_curpos && pos < gl->ntotal) { return pos; } else { (void) gl_ring_bell(gl, 1, NULL); return -1; } } /*....................................................................... * Repeat the last character search in the same direction as the last * search. */ static KT_KEY_FN(gl_repeat_find_char) { int pos = gl->vi.find_char ? gl_find_char(gl, count, gl->vi.find_forward, gl->vi.find_onto, gl->vi.find_char) : -1; return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Repeat the last character search in the opposite direction as the last * search. */ static KT_KEY_FN(gl_invert_refind_char) { int pos = gl->vi.find_char ? gl_find_char(gl, count, !gl->vi.find_forward, gl->vi.find_onto, gl->vi.find_char) : -1; return pos >= 0 && gl_place_cursor(gl, pos); } /*....................................................................... * Search forward from the current position of the cursor for 'count' * word endings, returning the index of the last one found, or the end of * the line if there were less than 'count' words. * * Input: * gl GetLine * The getline resource object. * n int The number of word boundaries to search for. * Output: * return int The buffer index of the located position. */ static int gl_nth_word_end_forward(GetLine *gl, int n) { int bufpos; /* The buffer index being checked. */ int i; /* * In order to guarantee forward motion to the next word ending, * we need to start from one position to the right of the cursor * position, since this may already be at the end of a word. */ bufpos = gl->buff_curpos + 1; /* * If we are at the end of the line, return the index of the last * real character on the line. Note that this will be -1 if the line * is empty. */ if(bufpos >= gl->ntotal) return gl->ntotal - 1; /* * Search 'n' times, unless the end of the input line is reached first. */ for(i=0; intotal; i++) { /* * If we are not already within a word, skip to the start of the next word. */ for( ; bufposntotal && !gl_is_word_char((int)gl->line[bufpos]); bufpos++) ; /* * Find the end of the next word. */ for( ; bufposntotal && gl_is_word_char((int)gl->line[bufpos]); bufpos++) ; }; /* * We will have overshot. */ return bufpos > 0 ? bufpos-1 : bufpos; } /*....................................................................... * Search forward from the current position of the cursor for 'count' * word starts, returning the index of the last one found, or the end of * the line if there were less than 'count' words. * * Input: * gl GetLine * The getline resource object. * n int The number of word boundaries to search for. * Output: * return int The buffer index of the located position. */ static int gl_nth_word_start_forward(GetLine *gl, int n) { int bufpos; /* The buffer index being checked. */ int i; /* * Get the current cursor position. */ bufpos = gl->buff_curpos; /* * Search 'n' times, unless the end of the input line is reached first. */ for(i=0; intotal; i++) { /* * Find the end of the current word. */ for( ; bufposntotal && gl_is_word_char((int)gl->line[bufpos]); bufpos++) ; /* * Skip to the start of the next word. */ for( ; bufposntotal && !gl_is_word_char((int)gl->line[bufpos]); bufpos++) ; }; return bufpos; } /*....................................................................... * Search backward from the current position of the cursor for 'count' * word starts, returning the index of the last one found, or the start * of the line if there were less than 'count' words. * * Input: * gl GetLine * The getline resource object. * n int The number of word boundaries to search for. * Output: * return int The buffer index of the located position. */ static int gl_nth_word_start_backward(GetLine *gl, int n) { int bufpos; /* The buffer index being checked. */ int i; /* * Get the current cursor position. */ bufpos = gl->buff_curpos; /* * Search 'n' times, unless the beginning of the input line (or vi insertion * point) is reached first. */ for(i=0; i gl->insert_curpos; i++) { /* * Starting one character back from the last search, so as not to keep * settling on the same word-start, search backwards until finding a * word character. */ while(--bufpos >= gl->insert_curpos && !gl_is_word_char((int)gl->line[bufpos])) ; /* * Find the start of the word. */ while(--bufpos >= gl->insert_curpos && gl_is_word_char((int)gl->line[bufpos])) ; /* * We will have gone one character too far. */ bufpos++; }; return bufpos >= gl->insert_curpos ? bufpos : gl->insert_curpos; } /*....................................................................... * Copy one or more words into the cut buffer without moving the cursor * or deleting text. */ static KT_KEY_FN(gl_forward_copy_word) { /* * Find the location of the count'th start or end of a word * after the cursor, depending on whether in emacs or vi mode. */ int next = gl->editor == GL_EMACS_MODE ? gl_nth_word_end_forward(gl, count) : gl_nth_word_start_forward(gl, count); /* * How many characters are to be copied into the cut buffer? */ int n = next - gl->buff_curpos; /* * Copy the specified segment and terminate the string. */ memcpy(gl->cutbuf, gl->line + gl->buff_curpos, n); gl->cutbuf[n] = '\0'; return 0; } /*....................................................................... * Copy one or more words preceding the cursor into the cut buffer, * without moving the cursor or deleting text. */ static KT_KEY_FN(gl_backward_copy_word) { /* * Find the location of the count'th start of word before the cursor. */ int next = gl_nth_word_start_backward(gl, count); /* * How many characters are to be copied into the cut buffer? */ int n = gl->buff_curpos - next; gl_place_cursor(gl, next); /* * Copy the specified segment and terminate the string. */ memcpy(gl->cutbuf, gl->line + next, n); gl->cutbuf[n] = '\0'; return 0; } /*....................................................................... * Copy the characters between the cursor and the count'th instance of * a specified character in the input line, into the cut buffer. * * Input: * gl GetLine * The getline resource object. * count int The number of times to search. * c char The character to be searched for, or '\0' if * the character should be read from the user. * forward int True if searching forward. * onto int True if the search should end on top of the * character, false if the search should stop * one character before the character in the * specified search direction. * Output: * return int 0 - OK. * 1 - Error. * */ static int gl_copy_find(GetLine *gl, int count, char c, int forward, int onto) { int n; /* The number of characters in the cut buffer */ /* * Search for the character, and abort the operation if not found. */ int pos = gl_find_char(gl, count, forward, onto, c); if(pos < 0) return 0; /* * Copy the specified segment. */ if(forward) { n = pos + 1 - gl->buff_curpos; memcpy(gl->cutbuf, gl->line + gl->buff_curpos, n); } else { n = gl->buff_curpos - pos; memcpy(gl->cutbuf, gl->line + pos, n); if(gl->editor == GL_VI_MODE) gl_place_cursor(gl, pos); } /* * Terminate the copy. */ gl->cutbuf[n] = '\0'; return 0; } /*....................................................................... * Copy a section up to and including a specified character into the cut * buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_forward_copy_find) { return gl_copy_find(gl, count, '\0', 1, 1); } /*....................................................................... * Copy a section back to and including a specified character into the cut * buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_backward_copy_find) { return gl_copy_find(gl, count, '\0', 0, 1); } /*....................................................................... * Copy a section up to and not including a specified character into the cut * buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_forward_copy_to) { return gl_copy_find(gl, count, '\0', 1, 0); } /*....................................................................... * Copy a section back to and not including a specified character into the cut * buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_backward_copy_to) { return gl_copy_find(gl, count, '\0', 0, 0); } /*....................................................................... * Copy to a character specified in a previous search into the cut * buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_copy_refind) { return gl_copy_find(gl, count, gl->vi.find_char, gl->vi.find_forward, gl->vi.find_onto); } /*....................................................................... * Copy to a character specified in a previous search, but in the opposite * direction, into the cut buffer without moving the cursor or deleting text. */ static KT_KEY_FN(gl_copy_invert_refind) { return gl_copy_find(gl, count, gl->vi.find_char, !gl->vi.find_forward, gl->vi.find_onto); } /*....................................................................... * Set the position of the cursor in the line input buffer and the * terminal. * * Input: * gl GetLine * The getline resource object. * buff_curpos int The new buffer cursor position. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_place_cursor(GetLine *gl, int buff_curpos) { /* * Don't allow the cursor position to go out of the bounds of the input * line. */ if(buff_curpos >= gl->ntotal) buff_curpos = gl->vi.command ? gl->ntotal-1 : gl->ntotal; if(buff_curpos < 0) buff_curpos = 0; /* * Record the new buffer position. */ gl->buff_curpos = buff_curpos; /* * Move the terminal cursor to the corresponding character. */ return gl_set_term_curpos(gl, gl->prompt_len + gl_displayed_string_width(gl, gl->line, buff_curpos, gl->prompt_len)); } /*....................................................................... * In vi command mode, this function saves the current line to the * historical buffer needed by the undo command. In emacs mode it does * nothing. In order to allow action functions to call other action * functions, gl_interpret_char() sets gl->vi.undo.saved to 0 before * invoking an action, and thereafter once any call to this function * has set it to 1, further calls are ignored. * * Input: * gl GetLine * The getline resource object. */ static void gl_save_for_undo(GetLine *gl) { if(gl->vi.command && !gl->vi.undo.saved) { strcpy(gl->vi.undo.line, gl->line); gl->vi.undo.buff_curpos = gl->buff_curpos; gl->vi.undo.ntotal = gl->ntotal; gl->vi.undo.saved = 1; }; if(gl->vi.command && !gl->vi.repeat.saved && gl->current_action.fn != gl_vi_repeat_change) { gl->vi.repeat.action = gl->current_action; gl->vi.repeat.count = gl->current_count; gl->vi.repeat.saved = 1; }; return; } /*....................................................................... * In vi mode, restore the line to the way it was before the last command * mode operation, storing the current line in the buffer so that the * undo operation itself can subsequently be undone. */ static KT_KEY_FN(gl_vi_undo) { /* * Get pointers into the two lines. */ char *undo_ptr = gl->vi.undo.line; char *line_ptr = gl->line; /* * Swap the characters of the two buffers up to the length of the shortest * line. */ while(*undo_ptr && *line_ptr) { char c = *undo_ptr; *undo_ptr++ = *line_ptr; *line_ptr++ = c; }; /* * Copy the rest directly. */ if(gl->ntotal > gl->vi.undo.ntotal) { strcpy(undo_ptr, line_ptr); *line_ptr = '\0'; } else { strcpy(line_ptr, undo_ptr); *undo_ptr = '\0'; }; /* * Record the length of the stored string. */ gl->vi.undo.ntotal = gl->ntotal; /* * Accomodate the new contents of gl->line[]. */ gl_update_buffer(gl); /* * Set both cursor positions to the leftmost of the saved and current * cursor positions to emulate what vi does. */ if(gl->buff_curpos < gl->vi.undo.buff_curpos) gl->vi.undo.buff_curpos = gl->buff_curpos; else gl->buff_curpos = gl->vi.undo.buff_curpos; /* * Since we have bipassed calling gl_save_for_undo(), record repeat * information inline. */ gl->vi.repeat.action.fn = gl_vi_undo; gl->vi.repeat.action.data = NULL; gl->vi.repeat.count = 1; /* * Display the restored line. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * Delete the following word and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_forward_change_word) { gl_save_for_undo(gl); gl->vi.command = 0; /* Allow cursor at EOL */ return gl_forward_delete_word(gl, count, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * Delete the preceding word and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_backward_change_word) { return gl_backward_delete_word(gl, count, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * Delete the following section and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_forward_change_find) { return gl_delete_find(gl, count, '\0', 1, 1, 1); } /*....................................................................... * Delete the preceding section and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_backward_change_find) { return gl_delete_find(gl, count, '\0', 0, 1, 1); } /*....................................................................... * Delete the following section and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_forward_change_to) { return gl_delete_find(gl, count, '\0', 1, 0, 1); } /*....................................................................... * Delete the preceding section and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_backward_change_to) { return gl_delete_find(gl, count, '\0', 0, 0, 1); } /*....................................................................... * Delete to a character specified by a previous search and leave the user * in vi insert mode. */ static KT_KEY_FN(gl_vi_change_refind) { return gl_delete_find(gl, count, gl->vi.find_char, gl->vi.find_forward, gl->vi.find_onto, 1); } /*....................................................................... * Delete to a character specified by a previous search, but in the opposite * direction, and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_change_invert_refind) { return gl_delete_find(gl, count, gl->vi.find_char, !gl->vi.find_forward, gl->vi.find_onto, 1); } /*....................................................................... * Delete the following character and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_forward_change_char) { gl_save_for_undo(gl); gl->vi.command = 0; /* Allow cursor at EOL */ return gl_delete_chars(gl, count, 1) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * Delete the preceding character and leave the user in vi insert mode. */ static KT_KEY_FN(gl_vi_backward_change_char) { return gl_backward_delete_char(gl, count, NULL) || gl_vi_insert(gl, 0, NULL); } /*....................................................................... * Starting from the cursor position change characters to the specified column. */ static KT_KEY_FN(gl_vi_change_to_column) { if (--count >= gl->buff_curpos) return gl_vi_forward_change_char(gl, count - gl->buff_curpos, NULL); else return gl_vi_backward_change_char(gl, gl->buff_curpos - count, NULL); } /*....................................................................... * Starting from the cursor position change characters to a matching * parenthesis. */ static KT_KEY_FN(gl_vi_change_to_parenthesis) { int curpos = gl_index_of_matching_paren(gl); if(curpos >= 0) { gl_save_for_undo(gl); if(curpos >= gl->buff_curpos) return gl_vi_forward_change_char(gl, curpos - gl->buff_curpos + 1, NULL); else return gl_vi_backward_change_char(gl, ++gl->buff_curpos - curpos + 1, NULL); }; return 0; } /*....................................................................... * If in vi mode, switch to vi command mode. * * Input: * gl GetLine * The getline resource object. */ static void gl_vi_command_mode(GetLine *gl) { if(gl->editor == GL_VI_MODE && !gl->vi.command) { gl->insert = 1; gl->vi.command = 1; gl->vi.repeat.input_curpos = gl->insert_curpos; gl->vi.repeat.command_curpos = gl->buff_curpos; gl->insert_curpos = 0; /* unrestrict left motion boundary */ gl_cursor_left(gl, 1, NULL); /* Vi moves 1 left on entering command mode */ }; } /*....................................................................... * This is an action function which rings the terminal bell. */ static KT_KEY_FN(gl_ring_bell) { return gl->silence_bell ? 0 : gl_print_control_sequence(gl, 1, gl->sound_bell); } /*....................................................................... * This is the action function which implements the vi-repeat-change * action. */ static KT_KEY_FN(gl_vi_repeat_change) { int status; /* The return status of the repeated action function */ int i; /* * Nothing to repeat? */ if(!gl->vi.repeat.action.fn) return gl_ring_bell(gl, 1, NULL); /* * Provide a way for action functions to know whether they are being * called by us. */ gl->vi.repeat.active = 1; /* * Re-run the recorded function. */ status = gl->vi.repeat.action.fn(gl, gl->vi.repeat.count, gl->vi.repeat.action.data); /* * Mark the repeat as completed. */ gl->vi.repeat.active = 0; /* * Is we are repeating a function that has just switched to input * mode to allow the user to type, re-enter the text that the user * previously entered. */ if(status==0 && !gl->vi.command) { /* * Make sure that the current line has been saved. */ gl_save_for_undo(gl); /* * Repeat a previous insertion or overwrite? */ if(gl->vi.repeat.input_curpos >= 0 && gl->vi.repeat.input_curpos <= gl->vi.repeat.command_curpos && gl->vi.repeat.command_curpos <= gl->vi.undo.ntotal) { /* * Using the current line which is saved in the undo buffer, plus * the range of characters therein, as recorded by gl_vi_command_mode(), * add the characters that the user previously entered, to the input * line. */ for(i=gl->vi.repeat.input_curpos; ivi.repeat.command_curpos; i++) { if(gl_add_char_to_line(gl, gl->vi.undo.line[i])) return 1; }; }; /* * Switch back to command mode, now that the insertion has been repeated. */ gl_vi_command_mode(gl); }; return status; } /*....................................................................... * If the cursor is currently over a parenthesis character, return the * index of its matching parenthesis. If not currently over a parenthesis * character, return the next close parenthesis character to the right of * the cursor. If the respective parenthesis character isn't found, * ring the terminal bell and return -1. * * Input: * gl GetLine * The getline resource object. * Output: * return int Either the index of the matching parenthesis, * or -1 if not found. */ static int gl_index_of_matching_paren(GetLine *gl) { int i; /* * List the recognized parentheses, and their matches. */ const char *o_paren = "([{"; const char *c_paren = ")]}"; const char *cptr; /* * Get the character that is currently under the cursor. */ char c = gl->line[gl->buff_curpos]; /* * If the character under the cursor is an open parenthesis, look forward * for the matching close parenthesis. */ if((cptr=strchr(o_paren, c))) { char match = c_paren[cptr - o_paren]; int matches_needed = 1; for(i=gl->buff_curpos+1; intotal; i++) { if(gl->line[i] == c) matches_needed++; else if(gl->line[i] == match && --matches_needed==0) return i; }; /* * If the character under the cursor is an close parenthesis, look forward * for the matching open parenthesis. */ } else if((cptr=strchr(c_paren, c))) { char match = o_paren[cptr - c_paren]; int matches_needed = 1; for(i=gl->buff_curpos-1; i>=0; i--) { if(gl->line[i] == c) matches_needed++; else if(gl->line[i] == match && --matches_needed==0) return i; }; /* * If not currently over a parenthesis character, search forwards for * the first close parenthesis (this is what the vi % binding does). */ } else { for(i=gl->buff_curpos+1; intotal; i++) if(strchr(c_paren, gl->line[i]) != NULL) return i; }; /* * Not found. */ (void) gl_ring_bell(gl, 1, NULL); return -1; } /*....................................................................... * If the cursor is currently over a parenthesis character, this action * function moves the cursor to its matching parenthesis. */ static KT_KEY_FN(gl_find_parenthesis) { int curpos = gl_index_of_matching_paren(gl); if(curpos >= 0) return gl_place_cursor(gl, curpos); return 0; } /*....................................................................... * Handle the receipt of the potential start of a new key-sequence from * the user. * * Input: * gl GetLine * The resource object of this library. * first_char char The first character of the sequence. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_interpret_char(GetLine *gl, char first_char) { char keyseq[GL_KEY_MAX+1]; /* A special key sequence being read */ int nkey=0; /* The number of characters in the key sequence */ int count; /* The repeat count of an action function */ int ret; /* The return value of an action function */ int i; /* * Get the first character. */ char c = first_char; /* * If editing is disabled, just add newly entered characters to the * input line buffer, and watch for the end of the line. */ if(gl->editor == GL_NO_EDITOR) { gl_discard_chars(gl, 1); if(gl->ntotal >= gl->linelen) return 0; if(c == '\n' || c == '\r') return gl_newline(gl, 1, NULL); gl_buffer_char(gl, c, gl->ntotal); return 0; }; /* * If the user is in the process of specifying a repeat count and the * new character is a digit, increment the repeat count accordingly. */ if(gl->number >= 0 && isdigit((int)(unsigned char) c)) { gl_discard_chars(gl, 1); return gl_digit_argument(gl, c, NULL); /* * In vi command mode, all key-sequences entered need to be * either implicitly or explicitly prefixed with an escape character. */ } else if(gl->vi.command && c != GL_ESC_CHAR) { keyseq[nkey++] = GL_ESC_CHAR; /* * If the first character of the sequence is a printable character, * then to avoid confusion with the special "up", "down", "left" * or "right" cursor key bindings, we need to prefix the * printable character with a backslash escape before looking it up. */ } else if(!IS_META_CHAR(c) && !IS_CTRL_CHAR(c)) { keyseq[nkey++] = '\\'; }; /* * Compose a potentially multiple key-sequence in gl->keyseq. */ while(nkey < GL_KEY_MAX) { KtAction *action; /* An action function */ KeySym *keysym; /* The symbol-table entry of a key-sequence */ int nsym; /* The number of ambiguously matching key-sequences */ /* * If the character is an unprintable meta character, split it * into two characters, an escape character and the character * that was modified by the meta key. */ if(IS_META_CHAR(c)) { keyseq[nkey++] = GL_ESC_CHAR; c = META_TO_CHAR(c); continue; }; /* * Append the latest character to the key sequence. */ keyseq[nkey++] = c; /* * When doing vi-style editing, an escape at the beginning of any binding * switches to command mode. */ if(keyseq[0] == GL_ESC_CHAR && !gl->vi.command) gl_vi_command_mode(gl); /* * Lookup the key sequence. */ switch(_kt_lookup_keybinding(gl->bindings, keyseq, nkey, &keysym, &nsym)) { case KT_EXACT_MATCH: /* * Get the matching action function. */ action = keysym->actions + keysym->binder; /* * Get the repeat count, passing the last keystroke if executing the * digit-argument action. */ if(action->fn == gl_digit_argument) { count = c; } else { count = gl->number >= 0 ? gl->number : 1; }; /* * Record the function that is being invoked. */ gl->current_action = *action; gl->current_count = count; /* * Mark the current line as not yet preserved for use by the vi undo command. */ gl->vi.undo.saved = 0; gl->vi.repeat.saved = 0; /* * Execute the action function. Note the action function can tell * whether the provided repeat count was defaulted or specified * explicitly by looking at whether gl->number is -1 or not. If * it is negative, then no repeat count was specified by the user. */ ret = action->fn(gl, count, action->data); /* * In server mode, the action will return immediately if it tries to * read input from the terminal, and no input is currently available. * If this happens, abort. Note that gl_get_input_line() will rewind * the read-ahead buffer to allow the next call to redo the function * from scratch. */ if(gl->rtn_status == GLR_BLOCKED && gl->pending_io==GLP_READ) return 1; /* * Discard the now processed characters from the key sequence buffer. */ gl_discard_chars(gl, gl->nread); /* * If the latest action function wasn't a history action, cancel any * current history search. */ if(gl->last_search != gl->keyseq_count) _glh_cancel_search(gl->glh); /* * Reset the repeat count after running action functions. */ if(action->fn != gl_digit_argument) gl->number = -1; return ret ? 1 : 0; break; case KT_AMBIG_MATCH: /* Ambiguous match - so read the next character */ if(gl_read_terminal(gl, 1, &c)) return 1; break; case KT_NO_MATCH: /* * If the first character looked like it might be a prefix of a key-sequence * but it turned out not to be, ring the bell to tell the user that it * wasn't recognised. */ if(keyseq[0] != '\\' && keyseq[0] != '\t') { gl_ring_bell(gl, 1, NULL); } else { /* * The user typed a single printable character that doesn't match * the start of any keysequence, so add it to the line in accordance * with the current repeat count. */ count = gl->number >= 0 ? gl->number : 1; for(i=0; inumber = -1; }; gl_discard_chars(gl, 1); _glh_cancel_search(gl->glh); return 0; break; case KT_BAD_MATCH: gl_ring_bell(gl, 1, NULL); gl_discard_chars(gl, gl->nread); _glh_cancel_search(gl->glh); return 1; break; }; }; /* * If the key sequence was too long to match, ring the bell, then * discard the first character, so that the next attempt to match a * key-sequence continues with the next key press. In practice this * shouldn't happen, since one isn't allowed to bind action functions * to keysequences that are longer than GL_KEY_MAX. */ gl_ring_bell(gl, 1, NULL); gl_discard_chars(gl, 1); return 0; } /*....................................................................... * Configure the application and/or user-specific behavior of * gl_get_line(). * * Note that calling this function between calling new_GetLine() and * the first call to gl_get_line(), disables the otherwise automatic * reading of ~/.teclarc on the first call to gl_get_line(). * * Input: * gl GetLine * The resource object of this library. * app_string const char * Either NULL, or a string containing one * or more .teclarc command lines, separated * by newline characters. This can be used to * establish an application-specific * configuration, without the need for an external * file. This is particularly useful in embedded * environments where there is no filesystem. * app_file const char * Either NULL, or the pathname of an * application-specific .teclarc file. The * contents of this file, if provided, are * read after the contents of app_string[]. * user_file const char * Either NULL, or the pathname of a * user-specific .teclarc file. Except in * embedded applications, this should * usually be "~/.teclarc". * Output: * return int 0 - OK. * 1 - Bad argument(s). */ int gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_configure_getline() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_configure_getline(gl, app_string, app_file, user_file); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_configure_getline() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file) { /* * Mark getline as having been explicitly configured. */ gl->configured = 1; /* * Start by parsing the configuration string, if provided. */ if(app_string) (void) _gl_read_config_string(gl, app_string, KTB_NORM); /* * Now parse the application-specific configuration file, if provided. */ if(app_file) (void) _gl_read_config_file(gl, app_file, KTB_NORM); /* * Finally, parse the user-specific configuration file, if provided. */ if(user_file) (void) _gl_read_config_file(gl, user_file, KTB_USER); /* * Record the names of the configuration files to allow them to * be re-read if requested at a later time. */ if(gl_record_string(&gl->app_file, app_file) || gl_record_string(&gl->user_file, user_file)) { errno = ENOMEM; _err_record_msg(gl->err, "Insufficient memory to record tecla configuration file names", END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Replace a malloc'd string (or NULL), with another malloc'd copy of * a string (or NULL). * * Input: * sptr char ** On input if *sptr!=NULL, *sptr will be * free'd and *sptr will be set to NULL. Then, * on output, if string!=NULL a malloc'd copy * of this string will be assigned to *sptr. * string const char * The string to be copied, or NULL to simply * discard any existing string. * Output: * return int 0 - OK. * 1 - Malloc failure (no error message is generated). */ static int gl_record_string(char **sptr, const char *string) { /* * If the original string is the same string, don't do anything. */ if(*sptr == string || (*sptr && string && strcmp(*sptr, string)==0)) return 0; /* * Discard any existing cached string. */ if(*sptr) { free(*sptr); *sptr = NULL; }; /* * Allocate memory for a copy of the specified string. */ if(string) { *sptr = (char *) malloc(strlen(string) + 1); if(!*sptr) return 1; /* * Copy the string. */ strcpy(*sptr, string); }; return 0; } #ifndef HIDE_FILE_SYSTEM /*....................................................................... * Re-read any application-specific and user-specific files previously * specified via the gl_configure_getline() function. */ static KT_KEY_FN(gl_read_init_files) { return _gl_configure_getline(gl, NULL, gl->app_file, gl->user_file); } #endif /*....................................................................... * Save the contents of the history buffer to a given new file. * * Input: * gl GetLine * The resource object of this library. * filename const char * The name of the new file to write to. * comment const char * Extra information such as timestamps will * be recorded on a line started with this * string, the idea being that the file can * double as a command file. Specify "" if * you don't care. * max_lines int The maximum number of lines to save, or -1 * to save all of the lines in the history * list. * Output: * return int 0 - OK. * 1 - Error. */ int gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_save_history() */ /* * Check the arguments. */ if(!gl || !filename || !comment) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_save_history(gl, filename, comment, max_lines); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_save_history() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines) { /* * If filesystem access is to be excluded, then history files can't * be written. */ #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(gl->err, "Can't save history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FileExpansion *expansion; /* The expansion of the filename */ /* * Expand the filename. */ expansion = ef_expand_file(gl->ef, filename, -1); if(!expansion) { gl_print_info(gl, "Unable to expand ", filename, " (", ef_last_error(gl->ef), ").", GL_END_INFO); return 1; }; /* * Attempt to save to the specified file. */ if(_glh_save_history(gl->glh, expansion->files[0], comment, max_lines)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; return 0; #endif } /*....................................................................... * Restore the contents of the history buffer from a given new file. * * Input: * gl GetLine * The resource object of this library. * filename const char * The name of the new file to write to. * comment const char * This must be the same string that was * passed to gl_save_history() when the file * was written. * Output: * return int 0 - OK. * 1 - Error. */ int gl_load_history(GetLine *gl, const char *filename, const char *comment) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_load_history() */ /* * Check the arguments. */ if(!gl || !filename || !comment) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_load_history(gl, filename, comment); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_load_history() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_load_history(GetLine *gl, const char *filename, const char *comment) { /* * If filesystem access is to be excluded, then history files can't * be read. */ #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(gl->err, "Can't load history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FileExpansion *expansion; /* The expansion of the filename */ /* * Expand the filename. */ expansion = ef_expand_file(gl->ef, filename, -1); if(!expansion) { gl_print_info(gl, "Unable to expand ", filename, " (", ef_last_error(gl->ef), ").", GL_END_INFO); return 1; }; /* * Attempt to load from the specified file. */ if(_glh_load_history(gl->glh, expansion->files[0], comment, gl->cutbuf, gl->linelen+1)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); gl->cutbuf[0] = '\0'; return 1; }; gl->cutbuf[0] = '\0'; return 0; #endif } /*....................................................................... * Where possible, register a function and associated data to be called * whenever a specified event is seen on a file descriptor. * * Input: * gl GetLine * The resource object of the command-line input * module. * fd int The file descriptor to watch. * event GlFdEvent The type of activity to watch for. * callback GlFdEventFn * The function to call when the specified * event occurs. Setting this to 0 removes * any existing callback. * data void * A pointer to arbitrary data to pass to the * callback function. * Output: * return int 0 - OK. * 1 - Either gl==NULL, or this facility isn't * available on the the host system * (ie. select() isn't available). No * error message is generated in the latter * case. */ int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_watch_fd() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; if(fd < 0) { _err_record_msg(gl->err, "Error: fd < 0", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_watch_fd(gl, fd, event, callback, data); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_watch_fd() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data) #if !defined(HAVE_SELECT) {return 1;} /* The facility isn't supported on this system */ #else { GlFdNode *prev; /* The node that precedes 'node' in gl->fd_nodes */ GlFdNode *node; /* The file-descriptor node being checked */ /* * Search the list of already registered fd activity nodes for the specified * file descriptor. */ for(prev=NULL,node=gl->fd_nodes; node && node->fd != fd; prev=node, node=node->next) ; /* * Hasn't a node been allocated for this fd yet? */ if(!node) { /* * If there is no callback to record, just ignore the call. */ if(!callback) return 0; /* * Allocate the new node. */ node = (GlFdNode *) _new_FreeListNode(gl->fd_node_mem); if(!node) { errno = ENOMEM; _err_record_msg(gl->err, "Insufficient memory", END_ERR_MSG); return 1; }; /* * Prepend the node to the list. */ node->next = gl->fd_nodes; gl->fd_nodes = node; /* * Initialize the node. */ node->fd = fd; node->rd.fn = 0; node->rd.data = NULL; node->ur = node->wr = node->rd; }; /* * Record the new callback. */ switch(event) { case GLFD_READ: node->rd.fn = callback; node->rd.data = data; if(callback) FD_SET(fd, &gl->rfds); else FD_CLR(fd, &gl->rfds); break; case GLFD_WRITE: node->wr.fn = callback; node->wr.data = data; if(callback) FD_SET(fd, &gl->wfds); else FD_CLR(fd, &gl->wfds); break; case GLFD_URGENT: node->ur.fn = callback; node->ur.data = data; if(callback) FD_SET(fd, &gl->ufds); else FD_CLR(fd, &gl->ufds); break; }; /* * Keep a record of the largest file descriptor being watched. */ if(fd > gl->max_fd) gl->max_fd = fd; /* * If we are deleting an existing callback, also delete the parent * activity node if no callbacks are registered to the fd anymore. */ if(!callback) { if(!node->rd.fn && !node->wr.fn && !node->ur.fn) { if(prev) prev->next = node->next; else gl->fd_nodes = node->next; node = (GlFdNode *) _del_FreeListNode(gl->fd_node_mem, node); }; }; return 0; } #endif /*....................................................................... * On systems with the select() system call, the gl_inactivity_timeout() * function provides the option of setting (or cancelling) an * inactivity timeout. Inactivity, in this case, refers both to * terminal input received from the user, and to I/O on any file * descriptors registered by calls to gl_watch_fd(). If at any time, * no activity is seen for the requested time period, the specified * timeout callback function is called. On returning, this callback * returns a code which tells gl_get_line() what to do next. Note that * each call to gl_inactivity_timeout() replaces any previously installed * timeout callback, and that specifying a callback of 0, turns off * inactivity timing. * * Beware that although the timeout argument includes a nano-second * component, few computer clocks presently have resolutions finer * than a few milliseconds, so asking for less than a few milliseconds * is equivalent to zero on a lot of systems. * * Input: * gl GetLine * The resource object of the command-line input * module. * callback GlTimeoutFn * The function to call when the inactivity * timeout is exceeded. To turn off * inactivity timeouts altogether, send 0. * data void * A pointer to arbitrary data to pass to the * callback function. * sec unsigned long The number of whole seconds in the timeout. * nsec unsigned long The fractional number of seconds in the * timeout, expressed in nano-seconds (see * the caveat above). * Output: * return int 0 - OK. * 1 - Either gl==NULL, or this facility isn't * available on the the host system * (ie. select() isn't available). No * error message is generated in the latter * case. */ int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *timeout_fn, void *data, unsigned long sec, unsigned long nsec) #if !defined(HAVE_SELECT) {return 1;} /* The facility isn't supported on this system */ #else { sigset_t oldset; /* The signals that were blocked on entry to this function */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Install a new timeout? */ if(timeout_fn) { gl->timer.dt.tv_sec = sec; gl->timer.dt.tv_usec = nsec / 1000; gl->timer.fn = timeout_fn; gl->timer.data = data; } else { gl->timer.fn = 0; gl->timer.data = NULL; }; /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return 0; } #endif /*....................................................................... * When select() is available, this is a private function of * gl_read_input() which responds to file-descriptor events registered by * the caller. Note that it assumes that it is being called from within * gl_read_input()'s sigsetjump() clause. * * Input: * gl GetLine * The resource object of this module. * fd int The file descriptor to be watched for user input. * Output: * return int 0 - OK. * 1 - An error occurred. */ static int gl_event_handler(GetLine *gl, int fd) #if !defined(HAVE_SELECT) {return 0;} #else { /* * Set up a zero-second timeout. */ struct timeval zero; zero.tv_sec = zero.tv_usec = 0; /* * If at any time no external callbacks remain, quit the loop return, * so that we can simply wait in read(). This is designed as an * optimization for when no callbacks have been registered on entry to * this function, but since callbacks can delete themselves, it can * also help later. */ while(gl->fd_nodes || gl->timer.fn) { int nready; /* The number of file descriptors that are ready for I/O */ /* * Get the set of descriptors to be watched. */ fd_set rfds = gl->rfds; fd_set wfds = gl->wfds; fd_set ufds = gl->ufds; /* * Get the appropriate timeout. */ struct timeval dt = gl->timer.fn ? gl->timer.dt : zero; /* * Add the specified user-input file descriptor tot he set that is to * be watched. */ FD_SET(fd, &rfds); /* * Unblock the signals that we are watching, while select is blocked * waiting for I/O. */ gl_catch_signals(gl); /* * Wait for activity on any of the file descriptors. */ nready = select(gl->max_fd+1, &rfds, &wfds, &ufds, (gl->timer.fn || gl->io_mode==GL_SERVER_MODE) ? &dt : NULL); /* * We don't want to do a longjmp in the middle of a callback that * might be modifying global or heap data, so block all the signals * that we are trapping before executing callback functions. Note that * the caller will unblock them again when it needs to, so there is * no need to undo this before returning. */ gl_mask_signals(gl, NULL); /* * If select() returns but none of the file descriptors are reported * to have activity, then select() timed out. */ if(nready == 0) { /* * Note that in non-blocking server mode, the inactivity timer is used * to allow I/O to block for a specified amount of time, so in this * mode we return the postponed blocked status when an abort is * requested. */ if(gl_call_timeout_handler(gl)) { return 1; } else if(gl->io_mode == GL_SERVER_MODE) { gl_record_status(gl, GLR_BLOCKED, BLOCKED_ERRNO); return 1; }; /* * If nready < 0, this means an error occurred. */ } else if(nready < 0) { if(errno != EINTR) { gl_record_status(gl, GLR_ERROR, errno); return 1; }; /* * If the user-input file descriptor has data available, return. */ } else if(FD_ISSET(fd, &rfds)) { return 0; /* * Check for activity on any of the file descriptors registered by the * calling application, and call the associated callback functions. */ } else { GlFdNode *node; /* The fd event node being checked */ /* * Search the list for the file descriptor that caused select() to return. */ for(node=gl->fd_nodes; node; node=node->next) { /* * Is there urgent out of band data waiting to be read on fd? */ if(node->ur.fn && FD_ISSET(node->fd, &ufds)) { if(gl_call_fd_handler(gl, &node->ur, node->fd, GLFD_URGENT)) return 1; break; /* The callback may have changed the list of nodes */ /* * Is the fd readable? */ } else if(node->rd.fn && FD_ISSET(node->fd, &rfds)) { if(gl_call_fd_handler(gl, &node->rd, node->fd, GLFD_READ)) return 1; break; /* The callback may have changed the list of nodes */ /* * Is the fd writable? */ } else if(node->wr.fn && FD_ISSET(node->fd, &wfds)) { if(gl_call_fd_handler(gl, &node->wr, node->fd, GLFD_WRITE)) return 1; break; /* The callback may have changed the list of nodes */ }; }; }; /* * Just in case the above event handlers asked for the input line to * be redrawn, flush any pending output. */ if(gl_flush_output(gl)) return 1; }; return 0; } #endif #if defined(HAVE_SELECT) /*....................................................................... * This is a private function of gl_event_handler(), used to call a * file-descriptor callback. * * Input: * gl GetLine * The resource object of gl_get_line(). * gfh GlFdHandler * The I/O handler. * fd int The file-descriptor being reported. * event GlFdEvent The I/O event being reported. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_call_fd_handler(GetLine *gl, GlFdHandler *gfh, int fd, GlFdEvent event) { Termios attr; /* The terminal attributes */ int waserr = 0; /* True after any error */ /* * Re-enable conversion of newline characters to carriage-return/linefeed, * so that the callback can write to the terminal without having to do * anything special. */ if(tcgetattr(gl->input_fd, &attr)) { _err_record_msg(gl->err, "tcgetattr error", END_ERR_MSG); return 1; }; attr.c_oflag |= OPOST; while(tcsetattr(gl->input_fd, TCSADRAIN, &attr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); return 1; }; }; /* * Invoke the application's callback function. */ switch(gfh->fn(gl, gfh->data, fd, event)) { default: case GLFD_ABORT: gl_record_status(gl, GLR_FDABORT, 0); waserr = 1; break; case GLFD_REFRESH: gl_queue_redisplay(gl); break; case GLFD_CONTINUE: break; }; /* * If the callback function called gl_normal_io(), restore raw mode, * and queue a redisplay of the input line. */ if(!gl->raw_mode) waserr = waserr || _gl_raw_io(gl, 1); /* * Disable conversion of newline characters to carriage-return/linefeed. */ attr.c_oflag &= ~(OPOST); while(tcsetattr(gl->input_fd, TCSADRAIN, &attr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); return 1; }; }; return waserr; } /*....................................................................... * This is a private function of gl_event_handler(), used to call a * inactivity timer callbacks. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_call_timeout_handler(GetLine *gl) { Termios attr; /* The terminal attributes */ int waserr = 0; /* True after any error */ /* * Make sure that there is an inactivity timeout callback. */ if(!gl->timer.fn) return 0; /* * Re-enable conversion of newline characters to carriage-return/linefeed, * so that the callback can write to the terminal without having to do * anything special. */ if(tcgetattr(gl->input_fd, &attr)) { _err_record_msg(gl->err, "tcgetattr error", END_ERR_MSG); return 1; }; attr.c_oflag |= OPOST; while(tcsetattr(gl->input_fd, TCSADRAIN, &attr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); return 1; }; }; /* * Invoke the application's callback function. */ switch(gl->timer.fn(gl, gl->timer.data)) { default: case GLTO_ABORT: gl_record_status(gl, GLR_TIMEOUT, 0); waserr = 1; break; case GLTO_REFRESH: gl_queue_redisplay(gl); break; case GLTO_CONTINUE: break; }; /* * If the callback function called gl_normal_io(), restore raw mode, * and queue a redisplay of the input line. */ if(!gl->raw_mode) waserr = waserr || _gl_raw_io(gl, 1); /* * Disable conversion of newline characters to carriage-return/linefeed. */ attr.c_oflag &= ~(OPOST); while(tcsetattr(gl->input_fd, TCSADRAIN, &attr)) { if(errno != EINTR) { _err_record_msg(gl->err, "tcsetattr error", END_ERR_MSG); return 1; }; }; return waserr; } #endif /* HAVE_SELECT */ /*....................................................................... * Switch history groups. History groups represent separate history * lists recorded within a single history buffer. Different groups * are distinguished by integer identifiers chosen by the calling * appplicaton. Initially new_GetLine() sets the group identifier to * 0. Whenever a new line is appended to the history list, the current * group identifier is recorded with it, and history lookups only * consider lines marked with the current group identifier. * * Input: * gl GetLine * The resource object of gl_get_line(). * id unsigned The new history group identifier. * Output: * return int 0 - OK. * 1 - Error. */ int gl_group_history(GetLine *gl, unsigned id) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of this function */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals while we install the new configuration. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * If the group isn't being changed, do nothing. */ if(_glh_get_group(gl->glh) == id) { status = 0; /* * Establish the new group. */ } else if(_glh_set_group(gl->glh, id)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); status = 1; /* * Prevent history information from the previous group being * inappropriately used by the next call to gl_get_line(). */ } else { gl->preload_history = 0; gl->last_search = -1; status = 0; }; /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * Display the contents of the history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * fp FILE * The stdio output stream to write to. * fmt const char * A format string. This containing characters to be * written verbatim, plus any of the following * format directives: * %D - The date, formatted like 2001-11-20 * %T - The time of day, formatted like 23:59:59 * %N - The sequential entry number of the * line in the history buffer. * %G - The number of the history group that * the line belongs to. * %% - A literal % character. * %H - The history line itself. * Note that a '\n' newline character is not * appended by default. * all_groups int If true, display history lines from all * history groups. Otherwise only display * those of the current history group. * max_lines int If max_lines is < 0, all available lines * are displayed. Otherwise only the most * recent max_lines lines will be displayed. * Output: * return int 0 - OK. * 1 - Error. */ int gl_show_history(GetLine *gl, FILE *fp, const char *fmt, int all_groups, int max_lines) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of this function */ /* * Check the arguments. */ if(!gl || !fp || !fmt) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Display the specified history group(s) while signals are blocked. */ status = _glh_show_history(gl->glh, _io_write_stdio, fp, fmt, all_groups, max_lines) || fflush(fp)==EOF; if(!status) _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * Update if necessary, and return the current size of the terminal. * * Input: * gl GetLine * The resource object of gl_get_line(). * def_ncolumn int If the number of columns in the terminal * can't be determined, substitute this number. * def_nline int If the number of lines in the terminal can't * be determined, substitute this number. * Output: * return GlTerminalSize The current terminal size. */ GlTerminalSize gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline) { GlTerminalSize size; /* The object to be returned */ sigset_t oldset; /* The signals that were blocked on entry */ /* to this function */ /* * Block all signals while accessing gl. */ gl_mask_signals(gl, &oldset); /* * Lookup/configure the terminal size. */ _gl_terminal_size(gl, def_ncolumn, def_nline, &size); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return size; } /*....................................................................... * This is the private body of the gl_terminal_size() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static void _gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline, GlTerminalSize *size) { const char *env; /* The value of an environment variable */ int n; /* A number read from env[] */ /* * Set the number of lines and columns to non-sensical values so that * we know later if they have been set. */ gl->nline = 0; gl->ncolumn = 0; /* * Are we reading from a terminal? */ if(gl->is_term) { /* * Ask the terminal directly if possible. */ gl_query_size(gl, &gl->ncolumn, &gl->nline); /* * If gl_query_size() couldn't ask the terminal, it will have * left gl->nrow and gl->ncolumn unchanged. If these values haven't * been changed from their initial values of zero, we need to find * a different method to get the terminal size. * * If the number of lines isn't known yet, first see if the * LINES environment ariable exists and specifies a believable number. * If this doesn't work, look up the default size in the terminal * information database. */ if(gl->nline < 1) { if((env = getenv("LINES")) && (n=atoi(env)) > 0) gl->nline = n; #ifdef USE_TERMINFO else gl->nline = tigetnum((char *)"lines"); #elif defined(USE_TERMCAP) else gl->nline = tgetnum("li"); #endif }; /* * If the number of lines isn't known yet, first see if the COLUMNS * environment ariable exists and specifies a believable number. If * this doesn't work, look up the default size in the terminal * information database. */ if(gl->ncolumn < 1) { if((env = getenv("COLUMNS")) && (n=atoi(env)) > 0) gl->ncolumn = n; #ifdef USE_TERMINFO else gl->ncolumn = tigetnum((char *)"cols"); #elif defined(USE_TERMCAP) else gl->ncolumn = tgetnum("co"); #endif }; }; /* * If we still haven't been able to acquire reasonable values, substitute * the default values specified by the caller. */ if(gl->nline <= 0) gl->nline = def_nline; if(gl->ncolumn <= 0) gl->ncolumn = def_ncolumn; /* * Copy the new size into the return value. */ if(size) { size->nline = gl->nline; size->ncolumn = gl->ncolumn; }; return; } /*....................................................................... * Resize or delete the history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * bufsize size_t The number of bytes in the history buffer, or 0 * to delete the buffer completely. * Output: * return int 0 - OK. * 1 - Insufficient memory (the previous buffer * will have been retained). No error message * will be displayed. */ int gl_resize_history(GetLine *gl, size_t bufsize) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of this function */ /* * Check the arguments. */ if(!gl) return 1; /* * Block all signals while modifying the contents of gl. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Perform the resize while signals are blocked. */ status = _glh_resize_history(gl->glh, bufsize); if(status) _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * Set an upper limit to the number of lines that can be recorded in the * history list, or remove a previously specified limit. * * Input: * gl GetLine * The resource object of gl_get_line(). * max_lines int The maximum number of lines to allow, or -1 to * cancel a previous limit and allow as many lines * as will fit in the current history buffer size. */ void gl_limit_history(GetLine *gl, int max_lines) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Apply the limit while signals are blocked. */ _glh_limit_history(gl->glh, max_lines); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Discard either all historical lines, or just those associated with the * current history group. * * Input: * gl GetLine * The resource object of gl_get_line(). * all_groups int If true, clear all of the history. If false, * clear only the stored lines associated with the * currently selected history group. */ void gl_clear_history(GetLine *gl, int all_groups) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Clear the history buffer while signals are blocked. */ _glh_clear_history(gl->glh, all_groups); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Temporarily enable or disable the gl_get_line() history mechanism. * * Input: * gl GetLine * The resource object of gl_get_line(). * enable int If true, turn on the history mechanism. If * false, disable it. */ void gl_toggle_history(GetLine *gl, int enable) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Change the history recording mode while signals are blocked. */ _glh_toggle_history(gl->glh, enable); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Lookup a history line by its sequential number of entry in the * history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * id unsigned long The identification number of the line to * be returned, where 0 denotes the first line * that was entered in the history list, and * each subsequently added line has a number * one greater than the previous one. For * the range of lines currently in the list, * see the gl_range_of_history() function. * Input/Output: * line GlHistoryLine * A pointer to the variable in which to * return the details of the line. * Output: * return int 0 - The line is no longer in the history * list, and *line has not been changed. * 1 - The requested line can be found in * *line. Note that line->line is part * of the history buffer, so a * private copy should be made if you * wish to use it after subsequent calls * to any functions that take *gl as an * argument. */ int gl_lookup_history(GetLine *gl, unsigned long id, GlHistoryLine *line) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of this function */ /* * Check the arguments. */ if(!gl) return 0; /* * Block all signals while modifying the contents of gl. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Perform the lookup while signals are blocked. */ status = _glh_lookup_history(gl->glh, (GlhLineID) id, &line->line, &line->group, &line->timestamp); if(status) _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * Query the state of the history list. Note that any of the input/output * pointers can be specified as NULL. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * state GlHistoryState * A pointer to the variable in which to record * the return values. */ void gl_state_of_history(GetLine *gl, GlHistoryState *state) { if(gl && state) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Lookup the status while signals are blocked. */ _glh_state_of_history(gl->glh, &state->enabled, &state->group, &state->max_lines); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Query the number and range of lines in the history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * range GlHistoryRange * A pointer to the variable in which to record * the return values. If range->nline=0, the * range of lines will be given as 0-0. */ void gl_range_of_history(GetLine *gl, GlHistoryRange *range) { if(gl && range) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Lookup the information while signals are blocked. */ _glh_range_of_history(gl->glh, &range->oldest, &range->newest, &range->nlines); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Return the size of the history buffer and the amount of the * buffer that is currently in use. * * Input: * gl GetLine * The gl_get_line() resource object. * Input/Output: * GlHistorySize size * A pointer to the variable in which to return * the results. */ void gl_size_of_history(GetLine *gl, GlHistorySize *size) { if(gl && size) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Lookup the information while signals are blocked. */ _glh_size_of_history(gl->glh, &size->size, &size->used); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * This is the action function that lists the contents of the history * list. */ static KT_KEY_FN(gl_list_history) { /* * Start a new line. */ if(gl_start_newline(gl, 1)) return 1; /* * List history lines that belong to the current group. */ _glh_show_history(gl->glh, gl_write_fn, gl, "%N %T %H\r\n", 0, count<=1 ? -1 : count); /* * Arrange for the input line to be redisplayed. */ gl_queue_redisplay(gl); return 0; } /*....................................................................... * Specify whether text that users type should be displayed or hidden. * In the latter case, only the prompt is displayed, and the final * input line is not archived in the history list. * * Input: * gl GetLine * The gl_get_line() resource object. * enable int 0 - Disable echoing. * 1 - Enable echoing. * -1 - Just query the mode without changing it. * Output: * return int The echoing disposition that was in effect * before this function was called: * 0 - Echoing was disabled. * 1 - Echoing was enabled. */ int gl_echo_mode(GetLine *gl, int enable) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ int was_echoing; /* The echoing disposition on entry to this function */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Install the new disposition while signals are blocked. */ was_echoing = gl->echo; if(enable >= 0) gl->echo = enable; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); /* * Return the original echoing disposition. */ return was_echoing; }; return 1; } /*....................................................................... * Display the prompt. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_display_prompt(GetLine *gl) { const char *pptr; /* A pointer into gl->prompt[] */ unsigned old_attr=0; /* The current text display attributes */ unsigned new_attr=0; /* The requested text display attributes */ /* * Temporarily switch to echoing output characters. */ int kept_echo = gl->echo; gl->echo = 1; /* * In case the screen got messed up, send a carriage return to * put the cursor at the beginning of the current terminal line. */ if(gl_print_control_sequence(gl, 1, gl->bol)) return 1; /* * Mark the line as partially displayed. */ gl->displayed = 1; /* * Write the prompt, using the currently selected prompt style. */ switch(gl->prompt_style) { case GL_LITERAL_PROMPT: if(gl_print_string(gl, gl->prompt, '\0')) return 1; break; case GL_FORMAT_PROMPT: for(pptr=gl->prompt; *pptr; pptr++) { /* * Does the latest character appear to be the start of a directive? */ if(*pptr == '%') { /* * Check for and act on attribute changing directives. */ switch(pptr[1]) { /* * Add or remove a text attribute from the new set of attributes. */ case 'B': case 'U': case 'S': case 'P': case 'F': case 'V': case 'b': case 'u': case 's': case 'p': case 'f': case 'v': switch(*++pptr) { case 'B': /* Switch to a bold font */ new_attr |= GL_TXT_BOLD; break; case 'b': /* Switch to a non-bold font */ new_attr &= ~GL_TXT_BOLD; break; case 'U': /* Start underlining */ new_attr |= GL_TXT_UNDERLINE; break; case 'u': /* Stop underlining */ new_attr &= ~GL_TXT_UNDERLINE; break; case 'S': /* Start highlighting */ new_attr |= GL_TXT_STANDOUT; break; case 's': /* Stop highlighting */ new_attr &= ~GL_TXT_STANDOUT; break; case 'P': /* Switch to a pale font */ new_attr |= GL_TXT_DIM; break; case 'p': /* Switch to a non-pale font */ new_attr &= ~GL_TXT_DIM; break; case 'F': /* Switch to a flashing font */ new_attr |= GL_TXT_BLINK; break; case 'f': /* Switch to a steady font */ new_attr &= ~GL_TXT_BLINK; break; case 'V': /* Switch to reverse video */ new_attr |= GL_TXT_REVERSE; break; case 'v': /* Switch out of reverse video */ new_attr &= ~GL_TXT_REVERSE; break; }; continue; /* * A literal % is represented by %%. Skip the leading %. */ case '%': pptr++; break; }; }; /* * Many terminals, when asked to turn off a single text attribute, turn * them all off, so the portable way to turn one off individually is to * explicitly turn them all off, then specify those that we want from * scratch. */ if(old_attr & ~new_attr) { if(gl_print_control_sequence(gl, 1, gl->text_attr_off)) return 1; old_attr = 0; }; /* * Install new text attributes? */ if(new_attr != old_attr) { if(new_attr & GL_TXT_BOLD && !(old_attr & GL_TXT_BOLD) && gl_print_control_sequence(gl, 1, gl->bold)) return 1; if(new_attr & GL_TXT_UNDERLINE && !(old_attr & GL_TXT_UNDERLINE) && gl_print_control_sequence(gl, 1, gl->underline)) return 1; if(new_attr & GL_TXT_STANDOUT && !(old_attr & GL_TXT_STANDOUT) && gl_print_control_sequence(gl, 1, gl->standout)) return 1; if(new_attr & GL_TXT_DIM && !(old_attr & GL_TXT_DIM) && gl_print_control_sequence(gl, 1, gl->dim)) return 1; if(new_attr & GL_TXT_REVERSE && !(old_attr & GL_TXT_REVERSE) && gl_print_control_sequence(gl, 1, gl->reverse)) return 1; if(new_attr & GL_TXT_BLINK && !(old_attr & GL_TXT_BLINK) && gl_print_control_sequence(gl, 1, gl->blink)) return 1; old_attr = new_attr; }; /* * Display the latest character. */ if(gl_print_char(gl, *pptr, pptr[1])) return 1; }; /* * Turn off all text attributes now that we have finished drawing * the prompt. */ if(gl_print_control_sequence(gl, 1, gl->text_attr_off)) return 1; break; }; /* * Restore the original echo mode. */ gl->echo = kept_echo; /* * The prompt has now been displayed at least once. */ gl->prompt_changed = 0; return 0; } /*....................................................................... * This function can be called from gl_get_line() callbacks to have * the prompt changed when they return. It has no effect if gl_get_line() * is not currently being invoked. * * Input: * gl GetLine * The resource object of gl_get_line(). * prompt const char * The new prompt. */ void gl_replace_prompt(GetLine *gl, const char *prompt) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Replace the prompt. */ _gl_replace_prompt(gl, prompt); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * This is the private body of the gl_replace_prompt() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static void _gl_replace_prompt(GetLine *gl, const char *prompt) { /* * Substitute an empty prompt? */ if(!prompt) prompt = ""; /* * Gaurd against aliasing between prompt and gl->prompt. */ if(gl->prompt != prompt) { /* * Get the length of the new prompt string. */ size_t slen = strlen(prompt); /* * If needed, allocate a new buffer for the prompt string. */ if(!gl->prompt || slen > strlen(gl->prompt)) { size_t size = sizeof(char) * (slen + 1); char *new_prompt = gl->prompt ? realloc(gl->prompt, size) : malloc(size); if(!new_prompt) return; gl->prompt = new_prompt; }; /* * Make a copy of the new prompt. */ strcpy(gl->prompt, prompt); }; /* * Record the statistics of the new prompt. */ gl->prompt_len = gl_displayed_prompt_width(gl); gl->prompt_changed = 1; gl_queue_redisplay(gl); return; } /*....................................................................... * Work out the length of the current prompt on the terminal, according * to the current prompt formatting style. * * Input: * gl GetLine * The resource object of this library. * Output: * return int The number of displayed characters. */ static int gl_displayed_prompt_width(GetLine *gl) { int slen=0; /* The displayed number of characters */ const char *pptr; /* A pointer into prompt[] */ /* * The length differs according to the prompt display style. */ switch(gl->prompt_style) { case GL_LITERAL_PROMPT: return gl_displayed_string_width(gl, gl->prompt, -1, 0); break; case GL_FORMAT_PROMPT: /* * Add up the length of the displayed string, while filtering out * attribute directives. */ for(pptr=gl->prompt; *pptr; pptr++) { /* * Does the latest character appear to be the start of a directive? */ if(*pptr == '%') { /* * Check for and skip attribute changing directives. */ switch(pptr[1]) { case 'B': case 'b': case 'U': case 'u': case 'S': case 's': pptr++; continue; /* * A literal % is represented by %%. Skip the leading %. */ case '%': pptr++; break; }; }; slen += gl_displayed_char_width(gl, *pptr, slen); }; break; }; return slen; } /*....................................................................... * Specify whether to heed text attribute directives within prompt * strings. * * Input: * gl GetLine * The resource object of gl_get_line(). * style GlPromptStyle The style of prompt (see the definition of * GlPromptStyle in libtecla.h for details). */ void gl_prompt_style(GetLine *gl, GlPromptStyle style) { if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Install the new style in gl while signals are blocked. */ if(style != gl->prompt_style) { gl->prompt_style = style; gl->prompt_len = gl_displayed_prompt_width(gl); gl->prompt_changed = 1; gl_queue_redisplay(gl); }; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; } /*....................................................................... * Tell gl_get_line() how to respond to a given signal. This can be used * both to override the default responses to signals that gl_get_line() * normally catches and to add new signals to the list that are to be * caught. * * Input: * gl GetLine * The resource object of gl_get_line(). * signo int The number of the signal to be caught. * flags unsigned A bitwise union of GlSignalFlags enumerators. * after GlAfterSignal What to do after the application's signal * handler has been called. * errno_value int The value to set errno to. * Output: * return int 0 - OK. * 1 - Error. */ int gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of this function */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals while modifying the contents of gl. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Perform the modification while signals are blocked. */ status = _gl_trap_signal(gl, signo, flags, after, errno_value); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_trap_signal() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value) { GlSignalNode *sig; /* * Complain if an attempt is made to trap untrappable signals. * These would otherwise cause errors later in gl_mask_signals(). */ if(0 #ifdef SIGKILL || signo==SIGKILL #endif #ifdef SIGBLOCK || signo==SIGBLOCK #endif ) { return 1; }; /* * See if the signal has already been registered. */ for(sig=gl->sigs; sig && sig->signo != signo; sig = sig->next) ; /* * If the signal hasn't already been registered, allocate a node for * it. */ if(!sig) { sig = (GlSignalNode *) _new_FreeListNode(gl->sig_mem); if(!sig) return 1; /* * Add the new node to the head of the list. */ sig->next = gl->sigs; gl->sigs = sig; /* * Record the signal number. */ sig->signo = signo; /* * Create a signal set that includes just this signal. */ sigemptyset(&sig->proc_mask); if(sigaddset(&sig->proc_mask, signo) == -1) { _err_record_msg(gl->err, "sigaddset error", END_ERR_MSG); sig = (GlSignalNode *) _del_FreeListNode(gl->sig_mem, sig); return 1; }; /* * Add the signal to the bit-mask of signals being trapped. */ sigaddset(&gl->all_signal_set, signo); }; /* * Record the new signal attributes. */ sig->flags = flags; sig->after = after; sig->errno_value = errno_value; return 0; } /*....................................................................... * Remove a signal from the list of signals that gl_get_line() traps. * * Input: * gl GetLine * The resource object of gl_get_line(). * signo int The number of the signal to be ignored. * Output: * return int 0 - OK. * 1 - Error. */ int gl_ignore_signal(GetLine *gl, int signo) { GlSignalNode *sig; /* The gl->sigs list node of the specified signal */ GlSignalNode *prev; /* The node that precedes sig in the list */ sigset_t oldset; /* The signals that were blocked on entry to this */ /* function. */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals while modifying the contents of gl. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Find the node of the gl->sigs list which records the disposition * of the specified signal. */ for(prev=NULL,sig=gl->sigs; sig && sig->signo != signo; prev=sig,sig=sig->next) ; if(sig) { /* * Remove the node from the list. */ if(prev) prev->next = sig->next; else gl->sigs = sig->next; /* * Return the node to the freelist. */ sig = (GlSignalNode *) _del_FreeListNode(gl->sig_mem, sig); /* * Remove the signal from the bit-mask union of signals being trapped. */ sigdelset(&gl->all_signal_set, signo); }; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return 0; } /*....................................................................... * This function is called when an input line has been completed. It * appends the specified newline character, terminates the line, * records the line in the history buffer if appropriate, and positions * the terminal cursor at the start of the next line. * * Input: * gl GetLine * The resource object of gl_get_line(). * newline_char int The newline character to add to the end * of the line. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_line_ended(GetLine *gl, int newline_char) { /* * If the newline character is printable, display it at the end of * the line, and add it to the input line buffer. */ if(isprint((int)(unsigned char) newline_char)) { if(gl_end_of_line(gl, 1, NULL) || gl_add_char_to_line(gl, newline_char)) return 1; } else { /* * Otherwise just append a newline character to the input line buffer. */ newline_char = '\n'; gl_buffer_char(gl, newline_char, gl->ntotal); }; /* * Add the line to the history buffer if it was entered with a * newline character. */ if(gl->echo && gl->automatic_history && newline_char=='\n') (void) _gl_append_history(gl, gl->line); /* * Except when depending on the system-provided line editing, start a new * line after the end of the line that has just been entered. */ if(gl->editor != GL_NO_EDITOR && gl_start_newline(gl, 1)) return 1; /* * Record the successful return status. */ gl_record_status(gl, GLR_NEWLINE, 0); /* * Attempt to flush any pending output. */ (void) gl_flush_output(gl); /* * The next call to gl_get_line() will write the prompt for a new line * (or continue the above flush if incomplete), so if we manage to * flush the terminal now, report that we are waiting to write to the * terminal. */ gl->pending_io = GLP_WRITE; return 0; } /*....................................................................... * Return the last signal that was caught by the most recent call to * gl_get_line(), or -1 if no signals were caught. This is useful if * gl_get_line() returns errno=EINTR and you need to find out what signal * caused it to abort. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int The last signal caught by the most recent * call to gl_get_line(), or -1 if no signals * were caught. */ int gl_last_signal(GetLine *gl) { int signo = -1; /* The requested signal number */ if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Access gl now that signals are blocked. */ signo = gl->last_signal; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; return signo; } /*....................................................................... * Prepare to edit a new line. * * Input: * gl GetLine * The resource object of this library. * prompt char * The prompt to prefix the line with, or NULL to * use the same prompt that was used by the previous * line. * start_line char * The initial contents of the input line, or NULL * if it should start out empty. * start_pos int If start_line isn't NULL, this specifies the * index of the character over which the cursor * should initially be positioned within the line. * If you just want it to follow the last character * of the line, send -1. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_present_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos) { /* * Prepare the line-editing properties for a new editing session. */ gl_reset_editor(gl); /* * Record the new prompt and its displayed width. */ if(prompt) _gl_replace_prompt(gl, prompt); /* * Reset the history search pointers. */ if(_glh_cancel_search(gl->glh)) { _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return 1; }; /* * If the previous line was entered via the repeat-history action, * preload the specified history line. */ if(gl->preload_history) { gl->preload_history = 0; if(_glh_recall_line(gl->glh, gl->preload_id, gl->line, gl->linelen+1)) { gl_update_buffer(gl); /* Compute gl->ntotal etc.. */ gl->buff_curpos = gl->ntotal; } else { gl_truncate_buffer(gl, 0); }; gl->preload_id = 0; /* * Present a specified initial line? */ } else if(start_line) { char *cptr; /* A pointer into gl->line[] */ /* * Measure the length of the starting line. */ int start_len = strlen(start_line); /* * If the length of the line is greater than the available space, * truncate it. */ if(start_len > gl->linelen) start_len = gl->linelen; /* * Load the line into the buffer. */ if(start_line != gl->line) gl_buffer_string(gl, start_line, start_len, 0); /* * Strip off any trailing newline and carriage return characters. */ for(cptr=gl->line + gl->ntotal - 1; cptr >= gl->line && (*cptr=='\n' || *cptr=='\r'); cptr--,gl->ntotal--) ; gl_truncate_buffer(gl, gl->ntotal < 0 ? 0 : gl->ntotal); /* * Where should the cursor be placed within the line? */ if(start_pos < 0 || start_pos > gl->ntotal) { if(gl_place_cursor(gl, gl->ntotal)) return 1; } else { if(gl_place_cursor(gl, start_pos)) return 1; }; /* * Clear the input line? */ } else { gl_truncate_buffer(gl, 0); }; /* * Arrange for the line to be displayed by gl_flush_output(). */ gl_queue_redisplay(gl); /* * Update the display. */ return gl_flush_output(gl); } /*....................................................................... * Reset all line-editing parameters for a new editing session. Note * that this does not empty the input line, since that would prevent a * gl_get_line() caller from specifying the returned line buffer as * the start_line argument of the next call to gl_get_line(). * * Input: * gl GetLine * The line editor resource object. */ static void gl_reset_editor(GetLine *gl) { /* * Warning: Don't clear gl->line[] and gl->ntotal here (see above). */ gl->buff_curpos = 0; gl->term_curpos = 0; gl->term_len = 0; gl->insert_curpos = 0; gl->number = -1; gl->displayed = 0; gl->endline = 0; gl->redisplay = 0; gl->postpone = 0; gl->nbuf = 0; gl->nread = 0; gl->vi.command = 0; gl->vi.undo.line[0] = '\0'; gl->vi.undo.ntotal = 0; gl->vi.undo.buff_curpos = 0; gl->vi.repeat.action.fn = 0; gl->vi.repeat.action.data = 0; gl->last_signal = -1; } /*....................................................................... * Print an informational message to the terminal, after starting a new * line. * * Input: * gl GetLine * The line editor resource object. * ... const char * Zero or more strings to be printed. * ... void * The last argument must always be GL_END_INFO. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_print_info(GetLine *gl, ...) { va_list ap; /* The variable argument list */ const char *s; /* The string being printed */ int waserr = 0; /* True after an error */ /* * Only display output when echoing is on. */ if(gl->echo) { /* * Skip to the start of the next empty line before displaying the message. */ if(gl_start_newline(gl, 1)) return 1; /* * Display the list of provided messages. */ va_start(ap, gl); while(!waserr && (s = va_arg(ap, const char *)) != GL_END_INFO) waserr = gl_print_raw_string(gl, 1, s, -1); va_end(ap); /* * Start a newline. */ waserr = waserr || gl_print_raw_string(gl, 1, "\n\r", -1); /* * Arrange for the input line to be redrawn. */ gl_queue_redisplay(gl); }; return waserr; } /*....................................................................... * Go to the start of the next empty line, ready to output miscellaneous * text to the screen. * * Note that when async-signal safety is required, the 'buffered' * argument must be 0. * * Input: * gl GetLine * The line editor resource object. * buffered int If true, used buffered I/O when writing to * the terminal. Otherwise use async-signal-safe * unbuffered I/O. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_start_newline(GetLine *gl, int buffered) { int waserr = 0; /* True after any I/O error */ /* * Move the cursor to the start of the terminal line that follows the * last line of the partially enterred line. In order that this * function remain async-signal safe when write_fn is signal safe, we * can't call our normal output functions, since they call tputs(), * who's signal saftey isn't defined. Fortunately, we can simply use * \r and \n to move the cursor to the right place. */ if(gl->displayed) { /* Is an input line currently displayed? */ /* * On which terminal lines are the cursor and the last character of the * input line? */ int curs_line = gl->term_curpos / gl->ncolumn; int last_line = gl->term_len / gl->ncolumn; /* * Move the cursor to the start of the line that follows the last * terminal line that is occupied by the input line. */ for( ; curs_line < last_line + 1; curs_line++) waserr = waserr || gl_print_raw_string(gl, buffered, "\n", 1); waserr = waserr || gl_print_raw_string(gl, buffered, "\r", 1); /* * Mark the line as no longer displayed. */ gl_line_erased(gl); }; return waserr; } /*....................................................................... * The callback through which all terminal output is routed. * This simply appends characters to a queue buffer, which is * subsequently flushed to the output channel by gl_flush_output(). * * Input: * data void * The pointer to a GetLine line editor resource object * cast to (void *). * s const char * The string to be written. * n int The number of characters to write from s[]. * Output: * return int The number of characters written. This will always * be equal to 'n' unless an error occurs. */ static GL_WRITE_FN(gl_write_fn) { GetLine *gl = (GetLine *) data; int ndone = _glq_append_chars(gl->cq, s, n, gl->flush_fn, gl); if(ndone != n) _err_record_msg(gl->err, _glq_last_error(gl->cq), END_ERR_MSG); return ndone; } /*....................................................................... * Ask gl_get_line() what caused it to return. * * Input: * gl GetLine * The line editor resource object. * Output: * return GlReturnStatus The return status of the last call to * gl_get_line(). */ GlReturnStatus gl_return_status(GetLine *gl) { GlReturnStatus rtn_status = GLR_ERROR; /* The requested status */ if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Access gl while signals are blocked. */ rtn_status = gl->rtn_status; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; return rtn_status; } /*....................................................................... * In non-blocking server-I/O mode, this function should be called * from the application's external event loop to see what type of * terminal I/O is being waited for by gl_get_line(), and thus what * direction of I/O to wait for with select() or poll(). * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return GlPendingIO The type of pending I/O being waited for. */ GlPendingIO gl_pending_io(GetLine *gl) { GlPendingIO pending_io = GLP_WRITE; /* The requested information */ if(gl) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Access gl while signals are blocked. */ pending_io = gl->pending_io; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); }; return pending_io; } /*....................................................................... * In server mode, this function configures the terminal for non-blocking * raw terminal I/O. In normal I/O mode it does nothing. * * Callers of this function must be careful to trap all signals that * terminate or suspend the program, and call gl_normal_io() * from the corresponding signal handlers in order to restore the * terminal to its original settings before the program is terminated * or suspended. They should also trap the SIGCONT signal to detect * when the program resumes, and ensure that its signal handler * call gl_raw_io() to redisplay the line and resume editing. * * This function is async signal safe. * * Input: * gl GetLine * The line editor resource object. * Output: * return int 0 - OK. * 1 - Error. */ int gl_raw_io(GetLine *gl) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_raw_io() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Don't allow applications to switch into raw mode unless in server mode. */ if(gl->io_mode != GL_SERVER_MODE) { _err_record_msg(gl->err, "Can't switch to raw I/O unless in server mode", END_ERR_MSG); errno = EPERM; status = 1; } else { /* * Execute the private body of the function while signals are blocked. */ status = _gl_raw_io(gl, 1); }; /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_raw_io(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. * * This function is async signal safe. */ static int _gl_raw_io(GetLine *gl, int redisplay) { /* * If we are already in the correct mode, do nothing. */ if(gl->raw_mode) return 0; /* * Switch the terminal to raw mode. */ if(gl->is_term && gl_raw_terminal_mode(gl)) return 1; /* * Switch to non-blocking I/O mode? */ if(gl->io_mode==GL_SERVER_MODE && (gl_nonblocking_io(gl, gl->input_fd) || gl_nonblocking_io(gl, gl->output_fd) || (gl->file_fp && gl_nonblocking_io(gl, fileno(gl->file_fp))))) { if(gl->is_term) gl_restore_terminal_attributes(gl); return 1; }; /* * If an input line is being entered, arrange for it to be * displayed. */ if(redisplay) { gl->postpone = 0; gl_queue_redisplay(gl); }; return 0; } /*....................................................................... * Restore the terminal to the state that it had when * gl_raw_io() was last called. After calling * gl_raw_io(), this function must be called before * terminating or suspending the program, and before attempting other * uses of the terminal from within the program. See gl_raw_io() * for more details. * * Input: * gl GetLine * The line editor resource object. * Output: * return int 0 - OK. * 1 - Error. */ int gl_normal_io(GetLine *gl) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_normal_io() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_normal_io(gl); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_normal_io(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_normal_io(GetLine *gl) { /* * If we are already in normal mode, do nothing. */ if(!gl->raw_mode) return 0; /* * Postpone subsequent redisplays until after _gl_raw_io(gl, 1) * is next called. */ gl->postpone = 1; /* * Switch back to blocking I/O. Note that this is essential to do * here, because when using non-blocking I/O, the terminal output * buffering code can't always make room for new output without calling * malloc(), and a call to malloc() would mean that this function * couldn't safely be called from signal handlers. */ if(gl->io_mode==GL_SERVER_MODE && (gl_blocking_io(gl, gl->input_fd) || gl_blocking_io(gl, gl->output_fd) || (gl->file_fp && gl_blocking_io(gl, fileno(gl->file_fp))))) return 1; /* * Move the cursor to the next empty terminal line. Note that * unbuffered I/O is requested, to ensure that gl_start_newline() be * async-signal-safe. */ if(gl->is_term && gl_start_newline(gl, 0)) return 1; /* * Switch the terminal to normal mode. */ if(gl->is_term && gl_restore_terminal_attributes(gl)) { /* * On error, revert to non-blocking I/O if needed, so that on failure * we remain in raw mode. */ if(gl->io_mode==GL_SERVER_MODE) { gl_nonblocking_io(gl, gl->input_fd); gl_nonblocking_io(gl, gl->output_fd); if(gl->file_fp) gl_nonblocking_io(gl, fileno(gl->file_fp)); }; return 1; }; return 0; } /*....................................................................... * This function allows you to install an additional completion * action, or to change the completion function of an existing * one. This should be called before the first call to gl_get_line() * so that the name of the action be defined before the user's * configuration file is read. * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * This is passed to match_fn() whenever it is * called. It could, for example, point to a * symbol table that match_fn() would look up * matches in. * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * report matching symbols. * list_only int If non-zero, install an action that only lists * possible completions, rather than attempting * to perform the completion. * name const char * The name with which users can refer to the * binding in tecla configuration files. * keyseq const char * Either NULL, or a key sequence with which * to invoke the binding. This should be * specified in the same manner as key-sequences * in tecla configuration files (eg. "M-^I"). * Output: * return int 0 - OK. * 1 - Error. */ int gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_completion_action() */ /* * Check the arguments. */ if(!gl || !name || !match_fn) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Install the new action while signals are blocked. */ status = _gl_completion_action(gl, data, match_fn, list_only, name, keyseq); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_completion_action(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq) { KtKeyFn *current_fn; /* An existing action function */ void *current_data; /* The action-function callback data */ /* * Which action function is desired? */ KtKeyFn *action_fn = list_only ? gl_list_completions : gl_complete_word; /* * Is there already an action of the specified name? */ if(_kt_lookup_action(gl->bindings, name, ¤t_fn, ¤t_data) == 0) { /* * If the action has the same type as the one being requested, * simply change the contents of its GlCplCallback callback data. */ if(current_fn == action_fn) { GlCplCallback *cb = (GlCplCallback *) current_data; cb->fn = match_fn; cb->data = data; } else { errno = EINVAL; _err_record_msg(gl->err, "Illegal attempt to change the type of an existing completion action", END_ERR_MSG); return 1; }; /* * No existing action has the specified name. */ } else { /* * Allocate a new GlCplCallback callback object. */ GlCplCallback *cb = (GlCplCallback *) _new_FreeListNode(gl->cpl_mem); if(!cb) { errno = ENOMEM; _err_record_msg(gl->err, "Insufficient memory to add completion action", END_ERR_MSG); return 1; }; /* * Record the completion callback data. */ cb->fn = match_fn; cb->data = data; /* * Attempt to register the new action. */ if(_kt_set_action(gl->bindings, name, action_fn, cb)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); _del_FreeListNode(gl->cpl_mem, (void *) cb); return 1; }; }; /* * Bind the action to a given key-sequence? */ if(keyseq && _kt_set_keybinding(gl->bindings, KTB_NORM, keyseq, name)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Register an application-provided function as an action function. * This should preferably be called before the first call to gl_get_line() * so that the name of the action becomes defined before the user's * configuration file is read. * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * Arbitrary application-specific callback * data to be passed to the callback * function, fn(). * fn GlActionFn * The application-specific function that * implements the action. This will be invoked * whenever the user presses any * key-sequence which is bound to this action. * name const char * The name with which users can refer to the * binding in tecla configuration files. * keyseq const char * The key sequence with which to invoke * the binding. This should be specified in the * same manner as key-sequences in tecla * configuration files (eg. "M-^I"). * Output: * return int 0 - OK. * 1 - Error. */ int gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_register_action() */ /* * Check the arguments. */ if(!gl || !name || !fn) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Install the new action while signals are blocked. */ status = _gl_register_action(gl, data, fn, name, keyseq); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_register_action(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq) { KtKeyFn *current_fn; /* An existing action function */ void *current_data; /* The action-function callback data */ /* * Get the action function which actually runs the application-provided * function. */ KtKeyFn *action_fn = gl_run_external_action; /* * Is there already an action of the specified name? */ if(_kt_lookup_action(gl->bindings, name, ¤t_fn, ¤t_data) == 0) { /* * If the action has the same type as the one being requested, * simply change the contents of its GlCplCallback callback data. */ if(current_fn == action_fn) { GlExternalAction *a = (GlExternalAction *) current_data; a->fn = fn; a->data = data; } else { errno = EINVAL; _err_record_msg(gl->err, "Illegal attempt to change the type of an existing action", END_ERR_MSG); return 1; }; /* * No existing action has the specified name. */ } else { /* * Allocate a new GlCplCallback callback object. */ GlExternalAction *a = (GlExternalAction *) _new_FreeListNode(gl->ext_act_mem); if(!a) { errno = ENOMEM; _err_record_msg(gl->err, "Insufficient memory to add completion action", END_ERR_MSG); return 1; }; /* * Record the completion callback data. */ a->fn = fn; a->data = data; /* * Attempt to register the new action. */ if(_kt_set_action(gl->bindings, name, action_fn, a)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); _del_FreeListNode(gl->cpl_mem, (void *) a); return 1; }; }; /* * Bind the action to a given key-sequence? */ if(keyseq && _kt_set_keybinding(gl->bindings, KTB_NORM, keyseq, name)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Invoke an action function previously registered by a call to * gl_register_action(). */ static KT_KEY_FN(gl_run_external_action) { GlAfterAction status; /* The return value of the action function */ /* * Get the container of the action function and associated callback data. */ GlExternalAction *a = (GlExternalAction *) data; /* * Invoke the action function. */ status = a->fn(gl, a->data, count, gl->buff_curpos, gl->line); /* * If the callback took us out of raw (possibly non-blocking) input * mode, restore this mode, and queue a redisplay of the input line. */ if(_gl_raw_io(gl, 1)) return 1; /* * Finally, check to see what the action function wants us to do next. */ switch(status) { default: case GLA_ABORT: gl_record_status(gl, GLR_ERROR, errno); return 1; break; case GLA_RETURN: return gl_newline(gl, 1, NULL); break; case GLA_CONTINUE: break; }; return 0; } /*....................................................................... * In server-I/O mode the terminal is left in raw mode between calls * to gl_get_line(), so it is necessary for the application to install * terminal restoring signal handlers for signals that could terminate * or suspend the process, plus a terminal reconfiguration handler to * be called when a process resumption signal is received, and finally * a handler to be called when a terminal-resize signal is received. * * Since there are many signals that by default terminate or suspend * processes, and different systems support different sub-sets of * these signals, this function provides a convenient wrapper around * sigaction() for assigning the specified handlers to all appropriate * signals. It also arranges that when any one of these signals is * being handled, all other catchable signals are blocked. This is * necessary so that the specified signal handlers can safely call * gl_raw_io(), gl_normal_io() and gl_update_size() without * reentrancy issues. * * Input: * term_handler void (*)(int) The signal handler to invoke when * a process-terminating signal is * received. * susp_handler void (*)(int) The signal handler to invoke when * a process-suspending signal is * received. * cont_handler void (*)(int) The signal handler to invoke when * a process-resumption signal is * received (ie. SIGCONT). * size_handler void (*)(int) The signal handler to invoke when * a terminal-resize signal (ie. SIGWINCH) * is received. * Output: * return int 0 - OK. * 1 - Error. */ int gl_tty_signals(void (*term_handler)(int), void (*susp_handler)(int), void (*cont_handler)(int), void (*size_handler)(int)) { int i; /* * Search for signals of the specified classes, and assign the * associated signal handler to them. */ for(i=0; iattr & GLSA_SUSP) { if(gl_set_tty_signal(sig->signo, susp_handler)) return 1; } else if(sig->attr & GLSA_TERM) { if(gl_set_tty_signal(sig->signo, term_handler)) return 1; } else if(sig->attr & GLSA_CONT) { if(gl_set_tty_signal(sig->signo, cont_handler)) return 1; } else if(sig->attr & GLSA_SIZE) { if(gl_set_tty_signal(sig->signo, size_handler)) return 1; }; }; return 0; } /*....................................................................... * This is a private function of gl_tty_signals(). It installs a given * signal handler, and arranges that when that signal handler is being * invoked other signals are blocked. The latter is important to allow * functions like gl_normal_io(), gl_raw_io() and gl_update_size() * to be called from signal handlers. * * Input: * signo int The signal to be trapped. * handler void (*)(int) The signal handler to assign to the signal. */ static int gl_set_tty_signal(int signo, void (*handler)(int)) { SigAction act; /* The signal handler configuation */ /* * Arrange to block all trappable signals except the one that is being * assigned (the trapped signal will be blocked automatically by the * system). */ gl_list_trappable_signals(&act.sa_mask); sigdelset(&act.sa_mask, signo); /* * Assign the signal handler. */ act.sa_handler = handler; /* * There is only one portable signal handling flag, and it isn't * relevant to us, so don't specify any flags. */ act.sa_flags = 0; /* * Register the signal handler. */ if(sigaction(signo, &act, NULL)) return 1; return 0; } /*....................................................................... * Display a left-justified string over multiple terminal lines, * taking account of the current width of the terminal. Optional * indentation and an optional prefix string can be specified to be * displayed at the start of each new terminal line used. Similarly, * an optional suffix can be specified to be displayed at the end of * each terminal line. If needed, a single paragraph can be broken * across multiple calls. Note that literal newlines in the input * string can be used to force a newline at any point and that you * should use this feature to explicitly end all paragraphs, including * at the end of the last string that you write. Note that when a new * line is started between two words that are separated by spaces, * those spaces are not output, whereas when a new line is started * because a newline character was found in the string, only the * spaces before the newline character are discarded. * * Input: * gl GetLine * The resource object of gl_get_line(). * indentation int The number of spaces of indentation to write * at the beginning of each new terminal line. * prefix const char * An optional prefix string to write after the * indentation margin at the start of each new * terminal line. You can specify NULL if no * prefix is required. * suffix const char * An optional suffix string to draw at the end * of the terminal line. Spaces will be added * where necessary to ensure that the suffix ends * in the last column of the terminal line. If * no suffix is desired, specify NULL. * fill_char int The padding character to use when indenting * the line or padding up to the suffix. * def_width int If the terminal width isn't known, such as when * writing to a pipe or redirecting to a file, * this number specifies what width to assume. * start int The number of characters already written to * the start of the current terminal line. This * is primarily used to allow individual * paragraphs to be written over multiple calls * to this function, but can also be used to * allow you to start the first line of a * paragraph with a different prefix or * indentation than those specified above. * string const char * The string to be written. * Output: * return int On error -1 is returned. Otherwise the * return value is the terminal column index at * which the cursor was left after writing the * final word in the string. Successful return * values can thus be passed verbatim to the * 'start' arguments of subsequent calls to * gl_display_text() to allow the printing of a * paragraph to be broken across multiple calls * to gl_display_text(). */ int gl_display_text(GetLine *gl, int indentation, const char *prefix, const char *suffix, int fill_char, int def_width, int start, const char *string) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_completion_action() */ /* * Check the arguments? */ if(!gl || !string) { errno = EINVAL; return -1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return -1; /* * Display the text while signals are blocked. */ status = _io_display_text(_io_write_stdio, gl->output_fp, indentation, prefix, suffix, fill_char, gl->ncolumn > 0 ? gl->ncolumn : def_width, start, string); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * Block all of the signals that we are currently trapping. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * oldset sigset_t * The superseded process signal mask * will be return in *oldset unless oldset is * NULL. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_mask_signals(GetLine *gl, sigset_t *oldset) { /* * Block all signals in all_signal_set, along with any others that are * already blocked by the application. */ if(sigprocmask(SIG_BLOCK, &gl->all_signal_set, oldset) >= 0) { gl->signals_masked = 1; return 0; }; /* * On error attempt to query the current process signal mask, so * that oldset be the correct process signal mask to restore later * if the caller of this function ignores the error return value. */ if(oldset) (void) sigprocmask(SIG_SETMASK, NULL, oldset); gl->signals_masked = 0; return 1; } /*....................................................................... * Restore a process signal mask that was previously returned via the * oldset argument of gl_mask_signals(). * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * oldset sigset_t * The process signal mask to be restored. * Output: * return int 0 - OK. * 1 - Error. */ static int gl_unmask_signals(GetLine *gl, sigset_t *oldset) { gl->signals_masked = 0; return sigprocmask(SIG_SETMASK, oldset, NULL) < 0; } /*....................................................................... * Arrange to temporarily catch the signals marked in gl->use_signal_set. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_catch_signals(GetLine *gl) { return sigprocmask(SIG_UNBLOCK, &gl->use_signal_set, NULL) < 0; } /*....................................................................... * Select the I/O mode to be used by gl_get_line(). * * Input: * gl GetLine * The resource object of gl_get_line(). * mode GlIOMode The I/O mode to establish. * Output: * return int 0 - OK. * 1 - Error. */ int gl_io_mode(GetLine *gl, GlIOMode mode) { sigset_t oldset; /* The signals that were blocked on entry to this function */ int status; /* The return status of _gl_io_mode() */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Check that the requested mode is known. */ switch(mode) { case GL_NORMAL_MODE: case GL_SERVER_MODE: break; default: errno = EINVAL; _err_record_msg(gl->err, "Unknown gl_get_line() I/O mode requested.", END_ERR_MSG); return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Invoke the private body of this function. */ status = _gl_io_mode(gl, mode); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_io_mode(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_io_mode(GetLine *gl, GlIOMode mode) { /* * Are we already in the specified mode? */ if(mode == gl->io_mode) return 0; /* * First revert to normal I/O in the current I/O mode. */ _gl_normal_io(gl); /* * Record the new mode. */ gl->io_mode = mode; /* * Perform any actions needed by the new mode. */ if(mode==GL_SERVER_MODE) { if(_gl_raw_io(gl, 1)) return 1; }; return 0; } /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in either gl_get_line() or its * associated public functions. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * buff char * An optional output buffer. Note that if the * calling application calls any gl_*() * functions from signal handlers, it should * provide a buffer here, so that a copy of * the latest error message can safely be made * while signals are blocked. * n size_t The allocated size of buff[]. * Output: * return const char * A pointer to the error message. This will * be the buff argument, unless buff==NULL, in * which case it will be a pointer to an * internal error buffer. In the latter case, * note that the contents of the returned buffer * will change on subsequent calls to any gl_*() * functions. */ const char *gl_error_message(GetLine *gl, char *buff, size_t n) { if(!gl) { static const char *msg = "NULL GetLine argument"; if(buff) { strncpy(buff, msg, n); buff[n-1] = '\0'; } else { return msg; }; } else if(buff) { sigset_t oldset; /* The signals that were blocked on entry to this block */ /* * Temporarily block all signals. */ gl_mask_signals(gl, &oldset); /* * Copy the error message into the specified buffer. */ if(buff && n > 0) { strncpy(buff, _err_get_msg(gl->err), n); buff[n-1] = '\0'; }; /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); } else { return _err_get_msg(gl->err); }; return buff; } /*....................................................................... * Return the signal mask used by gl_get_line(). This is the set of * signals that gl_get_line() is currently configured to trap. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * set sigset_t * The set of signals will be returned in *set, * in the form of a signal process mask, as * used by sigaction(), sigprocmask(), * sigpending(), sigsuspend(), sigsetjmp() and * other standard POSIX signal-aware * functions. * Output: * return int 0 - OK. * 1 - Error (examine errno for reason). */ int gl_list_signals(GetLine *gl, sigset_t *set) { /* * Check the arguments. */ if(!gl || !set) { if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Copy the signal mask into *set. */ memcpy(set, &gl->all_signal_set, sizeof(*set)); return 0; } /*....................................................................... * By default, gl_get_line() doesn't trap signals that are blocked * when it is called. This default can be changed either on a * per-signal basis by calling gl_trap_signal(), or on a global basis * by calling this function. What this function does is add the * GLS_UNBLOCK_SIG flag to all signals that are currently configured * to be trapped by gl_get_line(), such that when subsequent calls to * gl_get_line() wait for I/O, these signals are temporarily * unblocked. This behavior is useful in non-blocking server-I/O mode, * where it is used to avoid race conditions related to handling these * signals externally to gl_get_line(). See the demonstration code in * demo3.c, or the gl_handle_signal() man page for further * information. * * Input: * gl GetLine * The resource object of gl_get_line(). */ void gl_catch_blocked(GetLine *gl) { sigset_t oldset; /* The process signal mask to restore */ GlSignalNode *sig; /* A signal node in gl->sigs */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return; }; /* * Temporarily block all signals while we modify the contents of gl. */ gl_mask_signals(gl, &oldset); /* * Add the GLS_UNBLOCK_SIG flag to all configured signals. */ for(sig=gl->sigs; sig; sig=sig->next) sig->flags |= GLS_UNBLOCK_SIG; /* * Restore the process signal mask that was superseded by the call * to gl_mask_signals(). */ gl_unmask_signals(gl, &oldset); return; } /*....................................................................... * Respond to signals who's default effects have important * consequences to gl_get_line(). This is intended for use in * non-blocking server mode, where the external event loop is * responsible for catching signals. Signals that are handled include * those that by default terminate or suspend the process, and the * signal that indicates that the terminal size has changed. Note that * this function is not signal safe and should thus not be called from * a signal handler itself. See the gl_io_mode() man page for how it * should be used. * * In the case of signals that by default terminate or suspend * processes, command-line editing will be suspended, the terminal * returned to a usable state, then the default disposition of the * signal restored and the signal resent, in order to suspend or * terminate the process. If the process subsequently resumes, * command-line editing is resumed. * * In the case of signals that indicate that the terminal has been * resized, the new size will be queried, and any input line that is * being edited will be redrawn to fit the new dimensions of the * terminal. * * Input: * signo int The number of the signal to respond to. * gl GetLine * The first element of an array of 'ngl' GetLine * objects. * ngl int The number of elements in the gl[] array. Normally * this will be one. */ void gl_handle_signal(int signo, GetLine *gl, int ngl) { int attr; /* The attributes of the specified signal */ sigset_t all_signals; /* The set of trappable signals */ sigset_t oldset; /* The process signal mask to restore */ int i; /* * NULL operation? */ if(ngl < 1 || !gl) return; /* * Look up the default attributes of the specified signal. */ attr = gl_classify_signal(signo); /* * If the signal isn't known, we are done. */ if(!attr) return; /* * Temporarily block all signals while we modify the gl objects. */ gl_list_trappable_signals(&all_signals); sigprocmask(SIG_BLOCK, &all_signals, &oldset); /* * Suspend or terminate the process? */ if(attr & (GLSA_SUSP | GLSA_TERM)) { gl_suspend_process(signo, gl, ngl); /* * Resize the terminal? Note that ioctl() isn't defined as being * signal safe, so we can't call gl_update_size() here. However, * gl_get_line() checks for resizes on each call, so simply arrange * for the application's event loop to call gl_get_line() as soon as * it becomes possible to write to the terminal. Note that if the * caller is calling select() or poll when this happens, these functions * get interrupted, since a signal has been caught. */ } else if(attr & GLSA_SIZE) { for(i=0; iraw_mode) { _gl_normal_io(obj); if(!obj->raw_mode) /* Check that gl_normal_io() succeded */ obj->raw_mode = -1; /* Flag raw mode as needing to be restored */ }; }; /* * Restore the system default disposition of the signal that we * caught. Note that this signal is currently blocked. Note that we * don't use memcpy() to copy signal sets here, because the signal safety * of memcpy() is undefined. */ def_action.sa_handler = SIG_DFL; { char *orig = (char *) &all_signals; char *dest = (char *) &def_action.sa_mask; for(i=0; iraw_mode == -1) { /* Did we flag the need to restore raw mode? */ obj->raw_mode = 0; /* gl_raw_io() does nothing unless raw_mode==0 */ _gl_raw_io(obj, 1); }; }; /* * Restore the process signal mask to the way it was when this function * was called. */ sigprocmask(SIG_SETMASK, &oldset, NULL); return; } /*....................................................................... * Return the information about the default attributes of a given signal. * The attributes that are returned are as defined by the standards that * created them, including POSIX, SVR4 and 4.3+BSD, and are taken from a * table in Richard Steven's book, "Advanced programming in the UNIX * environment". * * Input: * signo int The signal to be characterized. * Output: * return int A bitwise union of GlSigAttr enumerators, or 0 * if the signal isn't known. */ static int gl_classify_signal(int signo) { int i; /* * Search for the specified signal in the gl_signal_list[] table. */ for(i=0; isigno == signo) return sig->attr; }; /* * Signal not known. */ return 0; } /*....................................................................... * When in non-blocking server mode, this function can be used to abandon * the current incompletely entered input line, and prepare to start * editing a new line on the next call to gl_get_line(). * * Input: * gl GetLine * The line editor resource object. */ void gl_abandon_line(GetLine *gl) { sigset_t oldset; /* The process signal mask to restore */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return; }; /* * Temporarily block all signals while we modify the contents of gl. */ gl_mask_signals(gl, &oldset); /* * Mark the input line as discarded. */ _gl_abandon_line(gl); /* * Restore the process signal mask that was superseded by the call * to gl_mask_signals(). */ gl_unmask_signals(gl, &oldset); return; } /*....................................................................... * This is the private body of the gl_abandon_line() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ void _gl_abandon_line(GetLine *gl) { gl->endline = 1; gl->pending_io = GLP_WRITE; } /*....................................................................... * How many characters are needed to write a number as an octal string? * * Input: * num unsigned The to be measured. * Output: * return int The number of characters needed. */ static int gl_octal_width(unsigned num) { int n; /* The number of characters needed to render the number */ for(n=1; num /= 8; n++) ; return n; } /*....................................................................... * Tell gl_get_line() the current terminal size. Note that this is only * necessary on systems where changes in terminal size aren't reported * via SIGWINCH. * * Input: * gl GetLine * The resource object of gl_get_line(). * ncolumn int The number of columns in the terminal. * nline int The number of lines in the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int gl_set_term_size(GetLine *gl, int ncolumn, int nline) { sigset_t oldset; /* The signals that were blocked on entry */ /* to this function */ int status; /* The return status */ /* * Block all signals while accessing gl. */ gl_mask_signals(gl, &oldset); /* * Install the new terminal size. */ status = _gl_set_term_size(gl, ncolumn, nline); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the gl_set_term_size() function. It * assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_set_term_size(GetLine *gl, int ncolumn, int nline) { /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Reject non-sensical dimensions. */ if(ncolumn <= 0 || nline <= 0) { _err_record_msg(gl->err, "Invalid terminal size", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Install the new dimensions in the terminal driver if possible, so * that future calls to gl_query_size() get the new value. */ #ifdef TIOCSWINSZ if(gl->is_term) { struct winsize size; size.ws_row = nline; size.ws_col = ncolumn; size.ws_xpixel = 0; size.ws_ypixel = 0; if(ioctl(gl->output_fd, TIOCSWINSZ, &size) == -1) { _err_record_msg(gl->err, "Can't change terminal size", END_ERR_MSG); return 1; }; }; #endif /* * If an input line is in the process of being edited, redisplay it to * accomodate the new dimensions, and record the new dimensions in * gl->nline and gl->ncolumn. */ return gl_handle_tty_resize(gl, ncolumn, nline); } /*....................................................................... * Record a character in the input line buffer at a given position. * * Input: * gl GetLine * The resource object of gl_get_line(). * c char The character to be recorded. * bufpos int The index in the buffer at which to record the * character. * Output: * return int 0 - OK. * 1 - Insufficient room. */ static int gl_buffer_char(GetLine *gl, char c, int bufpos) { /* * Guard against buffer overruns. */ if(bufpos >= gl->linelen) return 1; /* * Record the new character. */ gl->line[bufpos] = c; /* * If the new character was placed beyond the end of the current input * line, update gl->ntotal to reflect the increased number of characters * that are in gl->line, and terminate the string. */ if(bufpos >= gl->ntotal) { gl->ntotal = bufpos+1; gl->line[gl->ntotal] = '\0'; }; return 0; } /*....................................................................... * Copy a given string into the input buffer, overwriting the current * contents. * * Input: * gl GetLine * The resource object of gl_get_line(). * s const char * The string to be recorded. * n int The number of characters to be copied from the * string. * bufpos int The index in the buffer at which to place the * the first character of the string. * Output: * return int 0 - OK. * 1 - String truncated to fit. */ static int gl_buffer_string(GetLine *gl, const char *s, int n, int bufpos) { int nnew; /* The number of characters actually recorded */ int i; /* * How many of the characters will fit within the buffer? */ nnew = bufpos + n <= gl->linelen ? n : (gl->linelen - bufpos); /* * Record the first nnew characters of s[] in the buffer. */ for(i=0; intotal + n > gl->linelen) return 1; /* * Move everything including and beyond the character at 'start' * towards the end of the string. */ memmove(gl->line + start + n, gl->line + start, gl->ntotal - start + 1); /* * Update the recorded size of the line. */ gl->ntotal += n; return 1; } /*....................................................................... * Remove a given number of characters from the input buffer. This * involves moving the characters that follow the removed characters to * where the removed sub-string started in the input buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * start int The first character to be removed. * n int The number of characters to remove. */ static void gl_remove_from_buffer(GetLine *gl, int start, int n) { memmove(gl->line + start, gl->line + start + n, gl->ntotal - start - n + 1); /* * Update the recorded size of the line. */ gl->ntotal -= n; } /*....................................................................... * Truncate the string in the input line buffer after a given number of * characters. * * Input: * gl GetLine * The resource object of gl_get_line(). * n int The new length of the line. * Output: * return int 0 - OK. * 1 - n > gl->linelen. */ static int gl_truncate_buffer(GetLine *gl, int n) { if(n > gl->linelen) return 1; gl->line[n] = '\0'; gl->ntotal = n; return 0; } /*....................................................................... * When the contents of gl->line[] are changed without calling any of the * gl_ buffer manipulation functions, this function must be called to * compute the length of this string, and ancillary information. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void gl_update_buffer(GetLine *gl) { int len; /* The length of the line */ /* * Measure the length of the input line. */ for(len=0; len <= gl->linelen && gl->line[len]; len++) ; /* * Just in case the string wasn't correctly terminated, do so here. */ gl->line[len] = '\0'; /* * Record the number of characters that are now in gl->line[]. */ gl->ntotal = len; /* * Ensure that the cursor stays within the bounds of the modified * input line. */ if(gl->buff_curpos > gl->ntotal) gl->buff_curpos = gl->ntotal; /* * Arrange for the input line to be redrawn. */ gl_queue_redisplay(gl); return; } /*....................................................................... * Erase the displayed input line, including its prompt, and leave the * cursor where the erased line started. Note that to allow this * function to be used when responding to a terminal resize, this * function is designed to work even if the horizontal cursor position * doesn't match the internally recorded position. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_erase_line(GetLine *gl) { /* * Is a line currently displayed? */ if(gl->displayed) { /* * Relative the the start of the input line, which terminal line of * the current input line is the cursor currently on? */ int cursor_line = gl->term_curpos / gl->ncolumn; /* * Move the cursor to the start of the line. */ for( ; cursor_line > 0; cursor_line--) { if(gl_print_control_sequence(gl, 1, gl->up)) return 1; }; if(gl_print_control_sequence(gl, 1, gl->bol)) return 1; /* * Clear from the start of the line to the end of the terminal. */ if(gl_print_control_sequence(gl, gl->nline, gl->clear_eod)) return 1; /* * Mark the line as no longer displayed. */ gl_line_erased(gl); }; return 0; } /*....................................................................... * Arrange for the input line to be redisplayed by gl_flush_output(), * as soon as the output queue becomes empty. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void gl_queue_redisplay(GetLine *gl) { gl->redisplay = 1; gl->pending_io = GLP_WRITE; } /*....................................................................... * Truncate the displayed input line starting from the current * terminal cursor position, and leave the cursor at the end of the * truncated line. The input-line buffer is not affected. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ static int gl_truncate_display(GetLine *gl) { /* * Keep a record of the current terminal cursor position. */ int term_curpos = gl->term_curpos; /* * First clear from the cursor to the end of the current input line. */ if(gl_print_control_sequence(gl, 1, gl->clear_eol)) return 1; /* * If there is more than one line displayed, go to the start of the * next line and clear from there to the end of the display. Note that * we can't use clear_eod to do the whole job of clearing from the * current cursor position to the end of the terminal because * clear_eod is only defined when used at the start of a terminal line * (eg. with gnome terminals, clear_eod clears from the start of the * current terminal line, rather than from the current cursor * position). */ if(gl->term_len / gl->ncolumn > gl->term_curpos / gl->ncolumn) { if(gl_print_control_sequence(gl, 1, gl->down) || gl_print_control_sequence(gl, 1, gl->bol) || gl_print_control_sequence(gl, gl->nline, gl->clear_eod)) return 1; /* * Where is the cursor now? */ gl->term_curpos = gl->ncolumn * (term_curpos / gl->ncolumn + 1); /* * Restore the cursor position. */ gl_set_term_curpos(gl, term_curpos); }; /* * Update the recorded position of the final character. */ gl->term_len = gl->term_curpos; return 0; } /*....................................................................... * Return the set of all trappable signals. * * Input: * signals sigset_t * The set of signals will be recorded in * *signals. */ static void gl_list_trappable_signals(sigset_t *signals) { /* * Start with the set of all signals. */ sigfillset(signals); /* * Remove un-trappable signals from this set. */ #ifdef SIGKILL sigdelset(signals, SIGKILL); #endif #ifdef SIGSTOP sigdelset(signals, SIGSTOP); #endif } /*....................................................................... * Read an input line from a non-interactive input stream. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK * 1 - Error. */ static int gl_read_stream_line(GetLine *gl) { char c = '\0'; /* The latest character read from fp */ /* * Record the fact that we are about to read input. */ gl->pending_io = GLP_READ; /* * If we are starting a new line, reset the line-editing parameters, * and discard the previous input line. */ if(gl->endline) { gl_reset_editor(gl); gl_truncate_buffer(gl, 0); }; /* * Read one character at a time. */ while(gl->ntotal < gl->linelen && c != '\n') { /* * Attempt to read one more character. */ switch(gl_read_input(gl, &c)) { case GL_READ_OK: break; case GL_READ_EOF: /* Reached end-of-file? */ /* * If any characters were read before the end-of-file condition, * interpolate a newline character, so that the caller sees a * properly terminated line. Otherwise return an end-of-file * condition. */ if(gl->ntotal > 0) { c = '\n'; } else { gl_record_status(gl, GLR_EOF, 0); return 1; }; break; case GL_READ_BLOCKED: /* Input blocked? */ gl_record_status(gl, GLR_BLOCKED, BLOCKED_ERRNO); return 1; break; case GL_READ_ERROR: /* I/O error? */ return 1; break; }; /* * Append the character to the line buffer. */ if(gl_buffer_char(gl, c, gl->ntotal)) return 1; }; /* * Was the end of the input line reached before running out of buffer space? */ gl->endline = (c == '\n'); return 0; } /*....................................................................... * Read a single character from a non-interactive input stream. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int The character, or EOF on error. */ static int gl_read_stream_char(GetLine *gl) { char c = '\0'; /* The latest character read from fp */ int retval = EOF; /* The return value of this function */ /* * Arrange to discard any incomplete input line. */ _gl_abandon_line(gl); /* * Record the fact that we are about to read input. */ gl->pending_io = GLP_READ; /* * Attempt to read one more character. */ switch(gl_read_input(gl, &c)) { case GL_READ_OK: /* Success */ retval = c; break; case GL_READ_BLOCKED: /* The read blocked */ gl_record_status(gl, GLR_BLOCKED, BLOCKED_ERRNO); retval = EOF; /* Failure */ break; case GL_READ_EOF: /* End of file reached */ gl_record_status(gl, GLR_EOF, 0); retval = EOF; /* Failure */ break; case GL_READ_ERROR: retval = EOF; /* Failure */ break; }; return retval; } /*....................................................................... * Bind a key sequence to a given action. * * Input: * gl GetLine * The resource object of gl_get_line(). * origin GlKeyOrigin The originator of the key binding. * key const char * The key-sequence to be bound (or unbound). * action const char * The name of the action to bind the key to, * or either NULL or "" to unbind the * key-sequence. * Output: * return int 0 - OK * 1 - Error. */ int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin, const char *keyseq, const char *action) { KtBinder binder; /* The private internal equivalent of 'origin' */ /* * Check the arguments. */ if(!gl || !keyseq) { errno = EINVAL; if(gl) _err_record_msg(gl->err, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * An empty action string requests that the key-sequence be unbound. * This is indicated to _kt_set_keybinding() by passing a NULL action * string, so convert an empty string to a NULL action pointer. */ if(action && *action=='\0') action = NULL; /* * Translate the public originator enumeration to the private equivalent. */ binder = origin==GL_USER_KEY ? KTB_USER : KTB_NORM; /* * Bind the action to a given key-sequence? */ if(keyseq && _kt_set_keybinding(gl->bindings, binder, keyseq, action)) { _err_record_msg(gl->err, _kt_last_error(gl->bindings), END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * This is the public wrapper around the gl_clear_termina() function. * It clears the terminal and leaves the cursor at the home position. * In server I/O mode, the next call to gl_get_line() will also * redisplay the current input line. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ int gl_erase_terminal(GetLine *gl) { sigset_t oldset; /* The signals that were blocked on entry */ /* to this function */ int status; /* The return status */ /* * Block all signals while accessing gl. */ gl_mask_signals(gl, &oldset); /* * Clear the terminal. */ status = gl_clear_screen(gl, 1, NULL); /* * Attempt to flush the clear-screen control codes to the terminal. * If this doesn't complete the job, the next call to gl_get_line() * will. */ (void) gl_flush_output(gl); /* * Restore the process signal mask before returning. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This function must be called by any function that erases the input * line. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void gl_line_erased(GetLine *gl) { gl->displayed = 0; gl->term_curpos = 0; gl->term_len = 0; } /*....................................................................... * Append a specified line to the history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * line const char * The line to be added. * Output: * return int 0 - OK. * 1 - Error. */ int gl_append_history(GetLine *gl, const char *line) { sigset_t oldset; /* The signals that were blocked on entry */ /* to this function */ int status; /* The return status */ /* * Check the arguments. */ if(!gl || !line) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ status = _gl_append_history(gl, line); /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return status; } /*....................................................................... * This is the private body of the public function, gl_append_history(). * It assumes that the caller has checked its arguments and blocked the * delivery of signals. */ static int _gl_append_history(GetLine *gl, const char *line) { int status =_glh_add_history(gl->glh, line, 0); if(status) _err_record_msg(gl->err, _glh_last_error(gl->glh), END_ERR_MSG); return status; } /*....................................................................... * Enable or disable the automatic addition of newly entered lines to the * history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * enable int If true, subsequently entered lines will * automatically be added to the history list * before they are returned to the caller of * gl_get_line(). If 0, the choice of how and * when to archive lines in the history list, * is left up to the calling application, which * can do so via calls to gl_append_history(). * Output: * return int 0 - OK. * 1 - Error. */ int gl_automatic_history(GetLine *gl, int enable) { sigset_t oldset; /* The signals that were blocked on entry */ /* to this function */ /* * Check the arguments. */ if(!gl) { errno = EINVAL; return 1; }; /* * Block all signals. */ if(gl_mask_signals(gl, &oldset)) return 1; /* * Execute the private body of the function while signals are blocked. */ gl->automatic_history = enable; /* * Restore the process signal mask. */ gl_unmask_signals(gl, &oldset); return 0; } /*....................................................................... * This is a public function that reads a single uninterpretted * character from the user, without displaying anything. * * Input: * gl GetLine * A resource object previously returned by * new_GetLine(). * Output: * return int The character that was read, or EOF if the read * had to be aborted (in which case you can call * gl_return_status() to find out why). */ int gl_read_char(GetLine *gl) { int retval; /* The return value of _gl_read_char() */ /* * This function can be called from application callback functions, * so check whether signals have already been masked, so that we don't * do it again, and overwrite gl->old_signal_set. */ int was_masked = gl->signals_masked; /* * Check the arguments. */ if(!gl) { errno = EINVAL; return EOF; }; /* * Temporarily block all of the signals that we have been asked to trap. */ if(!was_masked && gl_mask_signals(gl, &gl->old_signal_set)) return EOF; /* * Perform the character reading task. */ retval = _gl_read_char(gl); /* * Restore the process signal mask to how it was when this function was * first called. */ if(!was_masked) gl_unmask_signals(gl, &gl->old_signal_set); return retval; } /*....................................................................... * This is the main body of the public function gl_read_char(). */ static int _gl_read_char(GetLine *gl) { int retval = EOF; /* The return value */ int waserr = 0; /* True if an error occurs */ char c; /* The character read */ /* * This function can be called from application callback functions, * so check whether signals have already been overriden, so that we don't * overwrite the preserved signal handlers with gl_get_line()s. Also * record whether we are currently in raw I/O mode or not, so that this * can be left in the same state on leaving this function. */ int was_overriden = gl->signals_overriden; int was_raw = gl->raw_mode; /* * Also keep a record of the direction of any I/O that gl_get_line() * is awaiting, so that we can restore this status on return. */ GlPendingIO old_pending_io = gl->pending_io; /* * Assume that this call will successfully complete the input operation * until proven otherwise. */ gl_clear_status(gl); /* * If this is the first call to this function or gl_get_line(), * since new_GetLine(), complete any postponed configuration. */ if(!gl->configured) { (void) _gl_configure_getline(gl, NULL, NULL, TECLA_CONFIG_FILE); gl->configured = 1; }; /* * Before installing our signal handler functions, record the fact * that there are no pending signals. */ gl_pending_signal = -1; /* * Temporarily override the signal handlers of the calling program, * so that we can intercept signals that would leave the terminal * in a bad state. */ if(!was_overriden) waserr = gl_override_signal_handlers(gl); /* * After recording the current terminal settings, switch the terminal * into raw input mode, without redisplaying any partially entered input * line. */ if(!was_raw) waserr = waserr || _gl_raw_io(gl, 0); /* * Attempt to read the line. This will require more than one attempt if * either a current temporary input file is opened by gl_get_input_line() * or the end of a temporary input file is reached by gl_read_stream_line(). */ while(!waserr) { /* * Read a line from a non-interactive stream? */ if(gl->file_fp || !gl->is_term) { retval = gl_read_stream_char(gl); if(retval != EOF) { /* Success? */ break; } else if(gl->file_fp) { /* End of temporary input file? */ gl_revert_input(gl); gl_record_status(gl, GLR_NEWLINE, 0); } else { /* An error? */ waserr = 1; break; }; }; /* * Read from the terminal? Note that the above if() block may have * changed gl->file_fp, so it is necessary to retest it here, rather * than using an else statement. */ if(!gl->file_fp && gl->is_term) { /* * Flush any pending output to the terminal before waiting * for the user to type a character. */ if(_glq_char_count(gl->cq) > 0 && gl_flush_output(gl)) { retval = EOF; /* * Read one character. Don't append it to the key buffer, since * this would subseuqnely appear as bogus input to the line editor. */ } else if(gl_read_terminal(gl, 0, &c) == 0) { /* * Record the character for return. */ retval = c; /* * In this mode, count each character as being a new key-sequence. */ gl->keyseq_count++; /* * Delete the character that was read, from the key-press buffer. */ gl_discard_chars(gl, 1); }; if(retval==EOF) waserr = 1; else break; }; }; /* * If an error occurred, but gl->rtn_status is still set to * GLR_NEWLINE, change the status to GLR_ERROR. Otherwise * leave it at whatever specific value was assigned by the function * that aborted input. This means that only functions that trap * non-generic errors have to remember to update gl->rtn_status * themselves. */ if(waserr && gl->rtn_status == GLR_NEWLINE) gl_record_status(gl, GLR_ERROR, errno); /* * Restore terminal settings, if they were changed by this function. */ if(!was_raw && gl->io_mode != GL_SERVER_MODE) _gl_normal_io(gl); /* * Restore the signal handlers, if they were overriden by this function. */ if(!was_overriden) gl_restore_signal_handlers(gl); /* * If this function gets aborted early, the errno value associated * with the event that caused this to happen is recorded in * gl->rtn_errno. Since errno may have been overwritten by cleanup * functions after this, restore its value to the value that it had * when the error condition occured, so that the caller can examine it * to find out what happened. */ errno = gl->rtn_errno; /* * Error conditions are signalled to the caller, by setting the returned * character to EOF. */ if(gl->rtn_status != GLR_NEWLINE) retval = EOF; /* * Restore the indication of what direction of I/O gl_get_line() * was awaiting before this call. */ gl->pending_io = old_pending_io; /* * Return the acquired character. */ return retval; } /*....................................................................... * Reset the GetLine completion status. This function should be called * at the start of gl_get_line(), gl_read_char() and gl_query_char() * to discard the completion status and non-zero errno value of any * preceding calls to these functions. * * Input: * gl GetLine * The resource object of this module. */ static void gl_clear_status(GetLine *gl) { gl_record_status(gl, GLR_NEWLINE, 0); } /*....................................................................... * When an error or other event causes gl_get_line() to return, this * function should be called to record information about what * happened, including the value of errno and the value that * gl_return_status() should return. * * Input: * gl GetLine * The resource object of this module. * rtn_status GlReturnStatus The completion status. To clear a * previous abnormal completion status, * specify GLR_NEWLINE (this is what * gl_clear_status() does). * rtn_errno int The associated value of errno. */ static void gl_record_status(GetLine *gl, GlReturnStatus rtn_status, int rtn_errno) { /* * If rtn_status==GLR_NEWLINE, then this resets the completion status, so we * should always heed this. Otherwise, only record the first abnormal * condition that occurs after such a reset. */ if(rtn_status == GLR_NEWLINE || gl->rtn_status == GLR_NEWLINE) { gl->rtn_status = rtn_status; gl->rtn_errno = rtn_errno; }; } ./libtecla/getline.h0100644000076400007640000000602210027466660012740 0ustar mcsmcs#ifndef getline_h #define getline_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Set the name of the getline configuration file. */ #define TECLA_CONFIG_FILE "~/.teclarc" /* * The following macro returns non-zero if a character is * a control character. */ #define IS_CTRL_CHAR(c) ((unsigned char)(c) < ' ' || (unsigned char)(c)=='\177') /* * The following macro returns non-zero if a character is * a meta character. */ #define IS_META_CHAR(c) (((unsigned char)(c) & 0x80) && !isprint((int)(unsigned char)(c))) /* * Return the character that would be produced by pressing the * specified key plus the control key. */ #define MAKE_CTRL(c) ((c)=='?' ? '\177' : ((unsigned char)toupper(c) & ~0x40)) /* * Return the character that would be produced by pressing the * specified key plus the meta key. */ #define MAKE_META(c) ((unsigned char)(c) | 0x80) /* * Given a binary control character, return the character that * had to be pressed at the same time as the control key. */ #define CTRL_TO_CHAR(c) (toupper((unsigned char)(c) | 0x40)) /* * Given a meta character, return the character that was pressed * at the same time as the meta key. */ #define META_TO_CHAR(c) ((unsigned char)(c) & ~0x80) /* * Specify the string of characters other than the alphanumeric characters, * that are to be considered parts of words. */ #define GL_WORD_CHARS "_*\?\\[]" /* * Define the escape character, both as a string and as a character. */ #define GL_ESC_STR "\033" #define GL_ESC_CHAR '\033' #endif ./libtecla/hash.c0100644000076400007640000005761010027466660012240 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include "hash.h" #include "strngmem.h" #include "freelist.h" /* * The following container object contains free-lists to be used * for allocation of HashTable containers and nodes. */ struct HashMemory { FreeList *hash_memory; /* HashTable free-list */ FreeList *node_memory; /* HashNode free-list */ StringMem *string_memory; /* Memory used to allocate hash strings */ }; /* * Define a hash symbol-table entry. * See symbol.h for the definition of the Symbol container type. */ typedef struct HashNode HashNode; struct HashNode { Symbol symbol; /* The symbol stored in the hash-entry */ HashNode *next; /* The next hash-table entry in a bucket list */ }; /* * Each hash-table bucket contains a linked list of entries that * hash to the same bucket. */ typedef struct { HashNode *head; /* The head of the bucket hash-node list */ int count; /* The number of entries in the list */ } HashBucket; /* * A hash-table consists of 'size' hash buckets. * Note that the HashTable typedef for this struct is contained in hash.h. */ struct HashTable { HashMemory *mem; /* HashTable free-list */ int internal_mem; /* True if 'mem' was allocated by _new_HashTable() */ int case_sensitive; /* True if case is significant in lookup keys */ int size; /* The number of hash buckets */ HashBucket *bucket; /* An array of 'size' hash buckets */ int (*keycmp)(const char *, const char *); /* Key comparison function */ void *app_data; /* Application-provided data */ HASH_DEL_FN(*del_fn); /* Application-provided 'app_data' destructor */ }; static HashNode *_del_HashNode(HashTable *hash, HashNode *node); static HashNode *_new_HashNode(HashTable *hash, const char *name, int code, void (*fn)(void), void *data, SYM_DEL_FN(*del_fn)); static HashNode *_find_HashNode(HashTable *hash, HashBucket *bucket, const char *name, HashNode **prev); static HashBucket *_find_HashBucket(HashTable *hash, const char *name); static int _ht_lower_strcmp(const char *node_key, const char *look_key); static int _ht_strcmp(const char *node_key, const char *look_key); /*....................................................................... * Allocate a free-list for use in allocating hash tables and their nodes. * * Input: * list_count int The number of HashTable containers per free-list * block. * node_count int The number of HashTable nodes per free-list block. * Output: * return HashMemory * The new free-list for use in allocating hash tables * and their nodes. */ HashMemory *_new_HashMemory(int hash_count, int node_count) { HashMemory *mem; /* * Allocate the free-list container. */ mem = (HashMemory *) malloc(sizeof(HashMemory)); if(!mem) { errno = ENOMEM; return NULL; }; /* * Initialize the container at least up to the point at which it can * safely be passed to _del_HashMemory(). */ mem->hash_memory = NULL; mem->node_memory = NULL; mem->string_memory = NULL; /* * Allocate the two free-lists. */ mem->hash_memory = _new_FreeList(sizeof(HashTable), hash_count); if(!mem->hash_memory) return _del_HashMemory(mem, 1); mem->node_memory = _new_FreeList(sizeof(HashNode), node_count); if(!mem->node_memory) return _del_HashMemory(mem, 1); mem->string_memory = _new_StringMem(64); if(!mem->string_memory) return _del_HashMemory(mem, 1); /* * Return the free-list container. */ return mem; } /*....................................................................... * Delete a HashTable free-list. An error will be displayed if the list is * still in use and the deletion will be aborted. * * Input: * mem HashMemory * The free-list container to be deleted. * force int If force==0 then _del_HashMemory() will complain * and refuse to delete the free-list if any * of nodes have not been returned to the free-list. * If force!=0 then _del_HashMemory() will not check * whether any nodes are still in use and will * always delete the list. * Output: * return HashMemory * Always NULL (even if the memory could not be * deleted). */ HashMemory *_del_HashMemory(HashMemory *mem, int force) { if(mem) { if(!force && (_busy_FreeListNodes(mem->hash_memory) > 0 || _busy_FreeListNodes(mem->node_memory) > 0)) { errno = EBUSY; return NULL; }; mem->hash_memory = _del_FreeList(mem->hash_memory, force); mem->node_memory = _del_FreeList(mem->node_memory, force); mem->string_memory = _del_StringMem(mem->string_memory, force); free(mem); }; return NULL; } /*....................................................................... * Create a new hash table. * * Input: * mem HashMemory * An optional free-list for use in allocating * HashTable containers and nodes. See explanation * in hash.h. If you are going to allocate more * than one hash table, then it will be more * efficient to allocate a single free-list for * all of them than to force each hash table * to allocate its own private free-list. * size int The size of the hash table. Best performance * will be acheived if this is a prime number. * hcase HashCase Specify how symbol case is considered when * looking up symbols, from: * IGNORE_CASE - Upper and lower case versions * of a letter are treated as * being identical. * HONOUR_CASE - Upper and lower case versions * of a letter are treated as * being distinct. * characters in a lookup name is significant. * app_data void * Optional application data to be registered * to the table. This is presented to user * provided SYM_DEL_FN() symbol destructors along * with the symbol data. * del_fn() HASH_DEL_FN(*) If you want app_data to be free'd when the * hash-table is destroyed, register a suitable * destructor function here. * Output: * return HashTable * The new hash table, or NULL on error. */ HashTable *_new_HashTable(HashMemory *mem, int size, HashCase hcase, void *app_data, HASH_DEL_FN(*del_fn)) { HashTable *hash; /* The table to be returned */ int allocate_mem = !mem; /* True if mem should be internally allocated */ int i; /* * Check arguments. */ if(size <= 0) { errno = EINVAL; return NULL; }; /* * Allocate an internal free-list? */ if(allocate_mem) { mem = _new_HashMemory(1, 100); if(!mem) return NULL; }; /* * Allocate the container. */ hash = (HashTable *) _new_FreeListNode(mem->hash_memory); if(!hash) { errno = ENOMEM; if(allocate_mem) mem = _del_HashMemory(mem, 1); return NULL; }; /* * Before attempting any operation that might fail, initialize * the container at least up to the point at which it can safely * be passed to _del_HashTable(). */ hash->mem = mem; hash->internal_mem = allocate_mem; hash->case_sensitive = hcase==HONOUR_CASE; hash->size = size; hash->bucket = NULL; hash->keycmp = hash->case_sensitive ? _ht_strcmp : _ht_lower_strcmp; hash->app_data = app_data; hash->del_fn = del_fn; /* * Allocate the array of 'size' hash buckets. */ hash->bucket = (HashBucket *) malloc(sizeof(HashBucket) * size); if(!hash->bucket) { errno = ENOMEM; return _del_HashTable(hash); }; /* * Initialize the bucket array. */ for(i=0; ibucket + i; b->head = NULL; b->count = 0; }; /* * The table is ready for use - albeit currently empty. */ return hash; } /*....................................................................... * Delete a hash-table. * * Input: * hash HashTable * The hash table to be deleted. * Output: * return HashTable * The deleted hash table (always NULL). */ HashTable *_del_HashTable(HashTable *hash) { if(hash) { /* * Clear and delete the bucket array. */ if(hash->bucket) { _clear_HashTable(hash); free(hash->bucket); hash->bucket = NULL; }; /* * Delete application data. */ if(hash->del_fn) hash->del_fn(hash->app_data); /* * If the hash table was allocated from an internal free-list, delete * it and the hash table by deleting the free-list. Otherwise just * return the hash-table to the external free-list. */ if(hash->internal_mem) _del_HashMemory(hash->mem, 1); else hash = (HashTable *) _del_FreeListNode(hash->mem->hash_memory, hash); }; return NULL; } /*....................................................................... * Create and install a new entry in a hash table. If an entry with the * same name already exists, replace its contents with the new data. * * Input: * hash HashTable * The hash table to insert the symbol into. * name const char * The name to tag the entry with. * code int An application-specific code to be stored in * the entry. * fn void (*)(void) An application-specific function to be stored * in the entry. * data void * An application-specific pointer to data to be * associated with the entry, or NULL if not * relevant. * del_fn SYM_DEL_FN(*) An optional destructor function. When the * symbol is deleted this function will be called * with the 'code' and 'data' arguments given * above. Any application data that was registered * to the table via the app_data argument of * _new_HashTable() will also be passed. * Output: * return HashNode * The new entry, or NULL if there was insufficient * memory or the arguments were invalid. */ Symbol *_new_HashSymbol(HashTable *hash, const char *name, int code, void (*fn)(void), void *data, SYM_DEL_FN(*del_fn)) { HashBucket *bucket; /* The hash-bucket associated with the name */ HashNode *node; /* The new node */ /* * Check arguments. */ if(!hash || !name) { errno = EINVAL; return NULL; }; /* * Get the hash bucket of the specified name. */ bucket = _find_HashBucket(hash, name); /* * See if a node with the same name already exists. */ node = _find_HashNode(hash, bucket, name, NULL); /* * If found, delete its contents by calling the user-supplied * destructor function, if provided. */ if(node) { if(node->symbol.data && node->symbol.del_fn) { node->symbol.data = node->symbol.del_fn(hash->app_data, node->symbol.code, node->symbol.data); }; /* * Allocate a new node if necessary. */ } else { node = _new_HashNode(hash, name, code, fn, data, del_fn); if(!node) return NULL; }; /* * Install the node at the head of the hash-bucket list. */ node->next = bucket->head; bucket->head = node; bucket->count++; return &node->symbol; } /*....................................................................... * Remove and delete a given hash-table entry. * * Input: * hash HashTable * The hash table to find the symbol in. * name const char * The name of the entry. * Output: * return HashNode * The deleted hash node (always NULL). */ Symbol *_del_HashSymbol(HashTable *hash, const char *name) { if(hash && name) { HashBucket *bucket = _find_HashBucket(hash, name); HashNode *prev; /* The node preceding the located node */ HashNode *node = _find_HashNode(hash, bucket, name, &prev); /* * Node found? */ if(node) { /* * Remove the node from the bucket list. */ if(prev) { prev->next = node->next; } else { bucket->head = node->next; }; /* * Record the loss of a node. */ bucket->count--; /* * Delete the node. */ (void) _del_HashNode(hash, node); }; }; return NULL; } /*....................................................................... * Look up a symbol in the hash table. * * Input: * hash HashTable * The table to look up the string in. * name const char * The name of the symbol to look up. * Output: * return Symbol * The located hash-table symbol, or NULL if not * found. */ Symbol *_find_HashSymbol(HashTable *hash, const char *name) { HashBucket *bucket; /* The hash-table bucket associated with name[] */ HashNode *node; /* The hash-table node of the requested symbol */ /* * Check arguments. */ if(!hash) return NULL; /* * Nothing to lookup? */ if(!name) return NULL; /* * Hash the name to a hash-table bucket. */ bucket = _find_HashBucket(hash, name); /* * Find the bucket entry that exactly matches the name. */ node = _find_HashNode(hash, bucket, name, NULL); if(!node) return NULL; return &node->symbol; } /*....................................................................... * Private function used to allocate a hash-table node. * The caller is responsible for checking that the specified symbol * is unique and for installing the returned entry in the table. * * Input: * hash HashTable * The table to allocate the node for. * name const char * The name of the new entry. * code int A user-supplied context code. * fn void (*)(void) A user-supplied function pointer. * data void * A user-supplied data pointer. * del_fn SYM_DEL_FN(*) An optional 'data' destructor function. * Output: * return HashNode * The new node, or NULL on error. */ static HashNode *_new_HashNode(HashTable *hash, const char *name, int code, void (*fn)(void), void *data, SYM_DEL_FN(*del_fn)) { HashNode *node; /* The new node */ /* * Allocate the new node from the free list. */ node = (HashNode *) _new_FreeListNode(hash->mem->node_memory); if(!node) return NULL; /* * Before attempting any operation that might fail, initialize the * contents of 'node' at least up to the point at which it can be * safely passed to _del_HashNode(). */ node->symbol.name = NULL; node->symbol.code = code; node->symbol.fn = fn; node->symbol.data = data; node->symbol.del_fn = del_fn; node->next = NULL; /* * Allocate a copy of 'name'. */ node->symbol.name = _new_StringMemString(hash->mem->string_memory, strlen(name) + 1); if(!node->symbol.name) return _del_HashNode(hash, node); /* * If character-case is insignificant in the current table, convert the * name to lower case while copying it. */ if(hash->case_sensitive) { strcpy(node->symbol.name, name); } else { const char *src = name; char *dst = node->symbol.name; for( ; *src; src++,dst++) *dst = tolower(*src); *dst = '\0'; }; return node; } /*....................................................................... * Private function used to delete a hash-table node. * The node must have been removed from its list before calling this * function. * * Input: * hash HashTable * The table for which the node was originally * allocated. * node HashNode * The node to be deleted. * Output: * return HashNode * The deleted node (always NULL). */ static HashNode *_del_HashNode(HashTable *hash, HashNode *node) { if(node) { node->symbol.name = _del_StringMemString(hash->mem->string_memory, node->symbol.name); /* * Call the user-supplied data-destructor if provided. */ if(node->symbol.data && node->symbol.del_fn) node->symbol.data = node->symbol.del_fn(hash->app_data, node->symbol.code, node->symbol.data); /* * Return the node to the free-list. */ node->next = NULL; node = (HashNode *) _del_FreeListNode(hash->mem->node_memory, node); }; return NULL; } /*....................................................................... * Private function to locate the hash bucket associated with a given * name. * * This uses a hash-function described in the dragon-book * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and * Ullman; pub. Adison Wesley) page 435. * * Input: * hash HashTable * The table to look up the string in. * name const char * The name of the symbol to look up. * Output: * return HashBucket * The located hash-bucket. */ static HashBucket *_find_HashBucket(HashTable *hash, const char *name) { unsigned const char *kp; unsigned long h = 0L; if(hash->case_sensitive) { for(kp=(unsigned const char *) name; *kp; kp++) h = 65599UL * h + *kp; /* 65599 is a prime close to 2^16 */ } else { for(kp=(unsigned const char *) name; *kp; kp++) h = 65599UL * h + tolower((int)*kp); /* 65599 is a prime close to 2^16 */ }; return hash->bucket + (h % hash->size); } /*....................................................................... * Search for a given name in the entries of a given bucket. * * Input: * hash HashTable * The hash-table being searched. * bucket HashBucket * The bucket to search (use _find_HashBucket()). * name const char * The name to search for. * Output: * prev HashNode ** If prev!=NULL then the pointer to the node * preceding the located node in the list will * be recorded in *prev. This will be NULL either * if the name is not found or the located node is * at the head of the list of entries. * return HashNode * The located hash-table node, or NULL if not * found. */ static HashNode *_find_HashNode(HashTable *hash, HashBucket *bucket, const char *name, HashNode **prev) { HashNode *last; /* The previously searched node */ HashNode *node; /* The node that is being searched */ /* * Search the list for a node containing the specified name. */ for(last=NULL, node=bucket->head; node && hash->keycmp(node->symbol.name, name)!=0; last = node, node=node->next) ; if(prev) *prev = node ? last : NULL; return node; } /*....................................................................... * When hash->case_sensitive is zero this function is called * in place of strcmp(). In such cases the hash-table names are stored * as lower-case versions of the original strings so this function * performs the comparison against lower-case copies of the characters * of the string being compared. * * Input: * node_key const char * The lower-case hash-node key being compared * against. * look_key const char * The lookup key. * Output: * return int <0 if node_key < look_key. * 0 if node_key == look_key. * >0 if node_key > look_key. */ static int _ht_lower_strcmp(const char *node_key, const char *look_key) { int cn; /* The latest character from node_key[] */ int cl; /* The latest character from look_key[] */ do { cn = *node_key++; cl = *look_key++; } while(cn && cn==tolower(cl)); return cn - tolower(cl); } /*....................................................................... * This is a wrapper around strcmp for comparing hash-keys in a case * sensitive manner. The reason for having this wrapper, instead of using * strcmp() directly, is to make some C++ compilers happy. The problem * is that when the library is compiled with a C++ compiler, the * declaration of the comparison function is a C++ declaration, whereas * strcmp() is a pure C function and thus although it appears to have the * same declaration, the compiler disagrees. * * Input: * node_key char * The lower-case hash-node key being compared against. * look_key char * The lookup key. * Output: * return int <0 if node_key < look_key. * 0 if node_key == look_key. * >0 if node_key > look_key. */ static int _ht_strcmp(const char *node_key, const char *look_key) { return strcmp(node_key, look_key); } /*....................................................................... * Empty a hash-table by deleting all of its entries. * * Input: * hash HashTable * The hash table to clear. * Output: * return int 0 - OK. * 1 - Invalid arguments. */ int _clear_HashTable(HashTable *hash) { int i; /* * Check the arguments. */ if(!hash) return 1; /* * Clear the contents of the bucket array. */ for(i=0; isize; i++) { HashBucket *bucket = hash->bucket + i; /* * Delete the list of active hash nodes from the bucket. */ HashNode *node = bucket->head; while(node) { HashNode *next = node->next; (void) _del_HashNode(hash, node); node = next; }; /* * Mark the bucket as empty. */ bucket->head = NULL; bucket->count = 0; }; return 0; } /*....................................................................... * Execute a given function on each entry of a hash table, returning * before completion if the the specified function returns non-zero. * * Input: * hash HashTable * The table to traverse. * scan_fn HASH_SCAN_FN(*) The function to call. * context void * Optional caller-specific context data * to be passed to scan_fn(). * Output: * return int 0 - OK. * 1 - Either the arguments were invalid, or * scan_fn() returned non-zero at some * point. */ int _scan_HashTable(HashTable *hash, HASH_SCAN_FN(*scan_fn), void *context) { int i; /* * Check the arguments. */ if(!hash || !scan_fn) return 1; /* * Iterate through the buckets of the table. */ for(i=0; isize; i++) { HashBucket *bucket = hash->bucket + i; HashNode *node; /* * Iterate through the list of symbols that fall into bucket i, * passing each one to the caller-specified function. */ for(node=bucket->head; node; node=node->next) { if(scan_fn(&node->symbol, context)) return 1; }; }; return 0; } ./libtecla/hash.h0100644000076400007640000001346410027466660012244 0ustar mcsmcs#ifndef hash_h #define hash_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * The following macro can be used to prototype or define a * function that deletes the data of a symbol-table entry. * * Input: * app_data void * The _new_HashTable() app_data argument. * code int The Symbol::code argument. * sym_data void * The Symbol::data argument to be deleted. * Output: * return void * The deleted data (always return NULL). */ #define SYM_DEL_FN(fn) void *(fn)(void *app_data, int code, void *sym_data) /* * The following macro can be used to prototype or define a * function that deletes the application-data of a hash-table. * * Input: * data void * The _new_HashTable() 'app_data' argument to be * deleted. * Output: * return void * The deleted data (always return NULL). */ #define HASH_DEL_FN(fn) void *(fn)(void *app_data) /* * The following is a container for recording the context * of a symbol in a manner that is independant of the particular * symbol-table implementation. Each hash-table entry contains * the following user supplied parameters: * * 1. An optional integral parameter 'code'. This is useful for * enumerating a symbol or for describing what type of data * or function is stored in the symbol. * * 2. An optional generic function pointer. This is useful for * associating functions with names. The user is responsible * for casting between the generic function type and the * actual function type. The code field could be used to * enumerate what type of function to cast to. * * 3. An optional generic pointer to a static or heap-allocated * object. It is up to the user to cast this back to the * appropriate object type. Again, the code field could be used * to describe what type of object is stored there. * If the object is dynamically allocated and should be discarded * when the symbol is deleted from the symbol table, send a * destructor function to have it deleted automatically. */ typedef struct { char *name; /* The name of the symbol */ int code; /* Application supplied integral code */ void (*fn)(void); /* Application supplied generic function */ void *data; /* Application supplied context data */ SYM_DEL_FN(*del_fn); /* Data destructor function */ } Symbol; /* * HashNode's and HashTable's are small objects. Separately allocating * many such objects would normally cause memory fragmentation. To * counter this, HashMemory objects are used. These contain * dedicated free-lists formed from large dynamically allocated arrays * of objects. One HashMemory object can be shared between multiple hash * tables (within a single thread). */ typedef struct HashMemory HashMemory; /* Create a free-list for allocation of hash tables and their nodes */ HashMemory *_new_HashMemory(int hash_count, int node_count); /* Delete a redundant free-list if not being used */ HashMemory *_del_HashMemory(HashMemory *mem, int force); /* * Declare an alias for the private HashTable structure defined in * hash.c. */ typedef struct HashTable HashTable; /* * Enumerate case-sensitivity options. */ typedef enum { IGNORE_CASE, /* Ignore case when looking up symbols */ HONOUR_CASE /* Honor case when looking up symbols */ } HashCase; /* Create a new hash-table */ HashTable *_new_HashTable(HashMemory *mem, int size, HashCase hcase, void *app_data, HASH_DEL_FN(*del_fn)); /* Delete a reference to a hash-table */ HashTable *_del_HashTable(HashTable *hash); /* Add an entry to a hash table */ Symbol *_new_HashSymbol(HashTable *hash, const char *key, int code, void (*fn)(void), void *data, SYM_DEL_FN(*del_fn)); /* Remove and delete all the entries in a given hash table */ int _clear_HashTable(HashTable *hash); /* Remove and delete a given hash-table entry */ Symbol *_del_HashSymbol(HashTable *hash, const char *key); /* Lookup a given hash-table entry */ Symbol *_find_HashSymbol(HashTable *hash, const char *key); /* Execute a given function on each entry of a hash table, returning */ /* before completion if the specified function returns non-zero. */ #define HASH_SCAN_FN(fn) int (fn)(Symbol *sym, void *context) int _scan_HashTable(HashTable *hash, HASH_SCAN_FN(*scan_fn), void *context); #endif ./libtecla/history.c0100644000076400007640000024620410036411312012775 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include #include "ioutil.h" #include "history.h" #include "freelist.h" #include "errmsg.h" /* * History lines are split into sub-strings of GLH_SEG_SIZE * characters. To avoid wasting space in the GlhLineSeg structure, * this should be a multiple of the size of a pointer. */ #define GLH_SEG_SIZE 16 /* * GlhLineSeg structures contain fixed sized segments of a larger * string. These are linked into lists to record strings, with all but * the last segment having GLH_SEG_SIZE characters. The last segment * of a string is terminated within the GLH_SEG_SIZE characters with a * '\0'. */ typedef struct GlhLineSeg GlhLineSeg; struct GlhLineSeg { GlhLineSeg *next; /* The next sub-string of the history line */ char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */ /* substring of a line, as indicated by 'next' */ /* being NULL, is '\0' terminated. */ }; /* * History lines are recorded in a hash table, such that repeated * lines are stored just once. * * Start by defining the size of the hash table. This should be a * prime number. */ #define GLH_HASH_SIZE 113 typedef struct GlhHashBucket GlhHashBucket; /* * Each history line will be represented in the hash table by a * structure of the following type. */ typedef struct GlhHashNode GlhHashNode; struct GlhHashNode { GlhHashBucket *bucket; /* The parent hash-table bucket of this node */ GlhHashNode *next; /* The next in the list of nodes within the */ /* parent hash-table bucket. */ GlhLineSeg *head; /* The list of sub-strings which make up a line */ int len; /* The length of the line, excluding any '\0' */ int used; /* The number of times this string is pointed to by */ /* the time-ordered list of history lines. */ int reported; /* A flag that is used when searching to ensure that */ /* a line isn't reported redundantly. */ }; /* * How many new GlhHashNode elements should be allocated at a time? */ #define GLH_HASH_INCR 50 static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n); static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix); static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim); /* * All history lines which hash to a given bucket in the hash table, are * recorded in a structure of the following type. */ struct GlhHashBucket { GlhHashNode *lines; /* The list of history lines which fall in this bucket */ }; static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, size_t n); static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, size_t n); typedef struct { FreeList *node_mem; /* A free-list of GlhHashNode structures */ GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */ } GlhLineHash; /* * GlhLineNode's are used to record history lines in time order. */ typedef struct GlhLineNode GlhLineNode; struct GlhLineNode { long id; /* The unique identifier of this history line */ time_t timestamp; /* The time at which the line was archived */ unsigned group; /* The identifier of the history group to which the */ /* the line belongs. */ GlhLineNode *next; /* The next youngest line in the list */ GlhLineNode *prev; /* The next oldest line in the list */ GlhHashNode *line; /* The hash-table entry of the history line */ }; /* * The number of GlhLineNode elements per freelist block. */ #define GLH_LINE_INCR 100 /* * Encapsulate the time-ordered list of historical lines. */ typedef struct { FreeList *node_mem; /* A freelist of GlhLineNode objects */ GlhLineNode *head; /* The oldest line in the list */ GlhLineNode *tail; /* The newest line in the list */ } GlhLineList; /* * The _glh_lookup_history() returns copies of history lines in a * dynamically allocated array. This array is initially allocated * GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be * too small, realloc() is used to increase its size to the required * size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to * reduce the number of realloc() operations needed. */ #define GLH_LBUF_SIZE 300 #define GLH_LBUF_MARGIN 100 /* * Encapsulate all of the resources needed to store historical input lines. */ struct GlHistory { ErrMsg *err; /* The error-reporting buffer */ GlhLineSeg *buffer; /* An array of sub-line nodes to be partitioned */ /* into lists of sub-strings recording input lines. */ int nbuff; /* The allocated dimension of buffer[] */ GlhLineSeg *unused; /* The list of free nodes in buffer[] */ GlhLineList list; /* A time ordered list of history lines */ GlhLineNode *recall; /* The last line recalled, or NULL if no recall */ /* session is currently active. */ GlhLineNode *id_node;/* The node at which the last ID search terminated */ GlhLineHash hash; /* A hash-table of reference-counted history lines */ GlhHashNode *prefix; /* A pointer to a line containing the prefix that */ /* is being searched for. Note that if prefix==NULL */ /* and prefix_len>0, this means that no line in */ /* the buffer starts with the requested prefix. */ int prefix_len; /* The length of the prefix being searched for. */ char *lbuf; /* The array in which _glh_lookup_history() returns */ /* history lines */ int lbuf_dim; /* The allocated size of lbuf[] */ int nbusy; /* The number of line segments in buffer[] that are */ /* currently being used to record sub-lines */ int nfree; /* The number of line segments in buffer that are */ /* not currently being used to record sub-lines */ unsigned long seq; /* The next ID to assign to a line node */ unsigned group; /* The identifier of the current history group */ int nline; /* The number of lines currently in the history list */ int max_lines; /* Either -1 or a ceiling on the number of lines */ int enable; /* If false, ignore history additions and lookups */ }; #ifndef WITHOUT_FILE_SYSTEM static int _glh_cant_load_history(GlHistory *glh, const char *filename, int lineno, const char *message, FILE *fp); static int _glh_cant_save_history(GlHistory *glh, const char *message, const char *filename, FILE *fp); static int _glh_write_timestamp(FILE *fp, time_t timestamp); static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp); #endif static void _glh_discard_line(GlHistory *glh, GlhLineNode *node); static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id); static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, size_t n); static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode); static int _glh_prepare_for_recall(GlHistory *glh, char *line); /* * The following structure and functions are used to iterate through * the characters of a segmented history line. */ typedef struct { GlhLineSeg *seg; /* The line segment that the next character will */ /* be returned from. */ int posn; /* The index in the above line segment, containing */ /* the next unread character. */ char c; /* The current character in the input line */ } GlhLineStream; static void glh_init_stream(GlhLineStream *str, GlhHashNode *line); static void glh_step_stream(GlhLineStream *str); /* * See if search prefix contains any globbing characters. */ static int glh_contains_glob(GlhHashNode *prefix); /* * Match a line against a search pattern. */ static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr); static int glh_matches_range(char c, GlhLineStream *pstr); /*....................................................................... * Create a line history maintenance object. * * Input: * buflen size_t The number of bytes to allocate to the * buffer that is used to record all of the * most recent lines of user input that will fit. * If buflen==0, no buffer will be allocated. * Output: * return GlHistory * The new object, or NULL on error. */ GlHistory *_new_GlHistory(size_t buflen) { GlHistory *glh; /* The object to be returned */ int i; /* * Allocate the container. */ glh = (GlHistory *) malloc(sizeof(GlHistory)); if(!glh) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_GlHistory(). */ glh->err = NULL; glh->buffer = NULL; glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; glh->unused = NULL; glh->list.node_mem = NULL; glh->list.head = glh->list.tail = NULL; glh->recall = NULL; glh->id_node = NULL; glh->hash.node_mem = NULL; for(i=0; ihash.bucket[i].lines = NULL; glh->prefix = NULL; glh->lbuf = NULL; glh->lbuf_dim = 0; glh->nbusy = 0; glh->nfree = glh->nbuff; glh->seq = 0; glh->group = 0; glh->nline = 0; glh->max_lines = -1; glh->enable = 1; /* * Allocate a place to record error messages. */ glh->err = _new_ErrMsg(); if(!glh->err) return _del_GlHistory(glh); /* * Allocate the buffer, if required. */ if(glh->nbuff > 0) { glh->nbuff = glh->nfree; glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff); if(!glh->buffer) { errno = ENOMEM; return _del_GlHistory(glh); }; /* * All nodes of the buffer are currently unused, so link them all into * a list and make glh->unused point to the head of this list. */ glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; }; /* * Allocate the GlhLineNode freelist. */ glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR); if(!glh->list.node_mem) return _del_GlHistory(glh); /* * Allocate the GlhHashNode freelist. */ glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR); if(!glh->hash.node_mem) return _del_GlHistory(glh); /* * Allocate the array that _glh_lookup_history() uses to return a * copy of a given history line. This will be resized when necessary. */ glh->lbuf_dim = GLH_LBUF_SIZE; glh->lbuf = (char *) malloc(glh->lbuf_dim); if(!glh->lbuf) { errno = ENOMEM; return _del_GlHistory(glh); }; return glh; } /*....................................................................... * Delete a GlHistory object. * * Input: * glh GlHistory * The object to be deleted. * Output: * return GlHistory * The deleted object (always NULL). */ GlHistory *_del_GlHistory(GlHistory *glh) { if(glh) { /* * Delete the error-message buffer. */ glh->err = _del_ErrMsg(glh->err); /* * Delete the buffer. */ if(glh->buffer) { free(glh->buffer); glh->buffer = NULL; glh->unused = NULL; }; /* * Delete the freelist of GlhLineNode's. */ glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1); /* * The contents of the list were deleted by deleting the freelist. */ glh->list.head = NULL; glh->list.tail = NULL; /* * Delete the freelist of GlhHashNode's. */ glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1); /* * Delete the lookup buffer. */ if(glh->lbuf) free(glh->lbuf); /* * Delete the container. */ free(glh); }; return NULL; } /*....................................................................... * Append a new line to the history list, deleting old lines to make * room, if needed. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The line to be archived. * force int Unless this flag is non-zero, empty lines aren't * archived. This flag requests that the line be * archived regardless. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_add_history(GlHistory *glh, const char *line, int force) { int slen; /* The length of the line to be recorded (minus the '\0') */ int empty; /* True if the string is empty */ const char *nlptr; /* A pointer to a newline character in line[] */ GlhHashNode *hnode; /* The hash-table node of the line */ GlhLineNode *lnode; /* A node in the time-ordered list of lines */ int i; /* * Check the arguments. */ if(!glh || !line) { errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Cancel any ongoing search. */ if(_glh_cancel_search(glh)) return 1; /* * How long is the string to be recorded, being careful not to include * any terminating '\n' character. */ nlptr = strchr(line, '\n'); if(nlptr) slen = (nlptr - line); else slen = strlen(line); /* * Is the line empty? */ empty = 1; for(i=0; imax_lines >= 0) { /* * If necessary, remove old lines until there is room to add one new * line without exceeding the specified line limit. */ while(glh->nline > 0 && glh->nline >= glh->max_lines) _glh_discard_line(glh, glh->list.head); /* * We can't archive the line if the maximum number of lines allowed is * zero. */ if(glh->max_lines == 0) return 0; }; /* * Unless already stored, store a copy of the line in the history buffer, * then return a reference-counted hash-node pointer to this copy. */ hnode = _glh_acquire_copy(glh, line, slen); if(!hnode) { _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); errno = ENOMEM; return 1; }; /* * Allocate a new node in the time-ordered list of lines. */ lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem); /* * If a new line-node couldn't be allocated, discard our copy of the * stored line before reporting the error. */ if(!lnode) { hnode = _glh_discard_copy(glh, hnode); _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); errno = ENOMEM; return 1; }; /* * Record a pointer to the hash-table record of the line in the new * list node. */ lnode->id = glh->seq++; lnode->timestamp = time(NULL); lnode->group = glh->group; lnode->line = hnode; /* * Append the new node to the end of the time-ordered list. */ if(glh->list.head) glh->list.tail->next = lnode; else glh->list.head = lnode; lnode->next = NULL; lnode->prev = glh->list.tail; glh->list.tail = lnode; /* * Record the addition of a line to the list. */ glh->nline++; return 0; } /*....................................................................... * Recall the next oldest line that has the search prefix last recorded * by _glh_search_prefix(). * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, if anything * was found, its contents will have been replaced * with the matching line. * dim size_t The allocated dimension of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ GlhHashNode *old_line; /* The previous recalled line */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * From where should we start the search? */ if(glh->recall) { node = glh->recall->prev; old_line = glh->recall->line; } else { node = glh->list.tail; old_line = NULL; }; /* * Search backwards through the list for the first match with the * prefix string that differs from the last line that was recalled. */ while(node && (node->group != glh->group || node->line == old_line || !_glh_line_matches_prefix(node->line, glh->prefix))) node = node->prev; /* * Was a matching line found? */ if(node) { /* * Recall the found node as the starting point for subsequent * searches. */ glh->recall = node; /* * Copy the matching line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * Return it. */ return line; }; /* * No match was found. */ return NULL; } /*....................................................................... * Recall the next newest line that has the search prefix last recorded * by _glh_search_prefix(). * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, if anything * was found, its contents will have been replaced * with the matching line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * The line requested, or NULL if no matching line * was found. */ char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ GlhHashNode *old_line; /* The previous recalled line */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * From where should we start the search? */ if(glh->recall) { node = glh->recall->next; old_line = glh->recall->line; } else { return NULL; }; /* * Search forwards through the list for the first match with the * prefix string. */ while(node && (node->group != glh->group || node->line == old_line || !_glh_line_matches_prefix(node->line, glh->prefix))) node = node->next; /* * Was a matching line found? */ if(node) { /* * Copy the matching line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * Record the starting point of the next search. */ glh->recall = node; /* * If we just returned the line that was being entered when the search * session first started, cancel the search. */ if(node == glh->list.tail) _glh_cancel_search(glh); /* * Return the matching line to the user. */ return line; }; /* * No match was found. */ return NULL; } /*....................................................................... * If a search is in progress, cancel it. * * This involves discarding the line that was temporarily saved by * _glh_find_backwards() when the search was originally started, * and reseting the search iteration pointer to NULL. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_cancel_search(GlHistory *glh) { /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * If there wasn't a search in progress, do nothing. */ if(!glh->recall) return 0; /* * Reset the search pointers. Note that it is essential to set * glh->recall to NULL before calling _glh_discard_line(), to avoid an * infinite recursion. */ glh->recall = NULL; /* * Delete the node of the preserved line. */ _glh_discard_line(glh, glh->list.tail); return 0; } /*....................................................................... * Set the prefix of subsequent history searches. * * Input: * glh GlHistory * The input-line history maintenance object. * line const char * The command line who's prefix is to be used. * prefix_len int The length of the prefix. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len) { /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Discard any existing prefix. */ glh->prefix = _glh_discard_copy(glh, glh->prefix); /* * Only store a copy of the prefix string if it isn't a zero-length string. */ if(prefix_len > 0) { /* * Get a reference-counted copy of the prefix from the history cache buffer. */ glh->prefix = _glh_acquire_copy(glh, line, prefix_len); /* * Was there insufficient buffer space? */ if(!glh->prefix) { _err_record_msg(glh->err, "The search prefix is too long to store", END_ERR_MSG); errno = ENOMEM; return 1; }; }; return 0; } /*....................................................................... * Recall the oldest recorded line. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the oldest line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * Locate the oldest line that belongs to the current group. */ for(node=glh->list.head; node && node->group != glh->group; node = node->next) ; /* * No line found? */ if(!node) return NULL; /* * Record the above node as the starting point for subsequent * searches. */ glh->recall = node; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * If we just returned the line that was being entered when the search * session first started, cancel the search. */ if(node == glh->list.tail) _glh_cancel_search(glh); return line; } /*....................................................................... * Recall the line that was being entered when the search started. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the line that was * being entered when the search was started. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_current_line(GlHistory *glh, char *line, size_t dim) { /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * If history isn't enabled, or no history search has yet been started, * ignore the call. */ if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(glh->list.tail->line, line, dim); /* * Since we have returned to the starting point of the search, cancel it. */ _glh_cancel_search(glh); return line; } /*....................................................................... * Query the id of a history line offset by a given number of lines from * the one that is currently being recalled. If a recall session isn't * in progress, or the offset points outside the history list, 0 is * returned. * * Input: * glh GlHistory * The input-line history maintenance object. * offset int The line offset (0 for the current line, < 0 * for an older line, > 0 for a newer line. * Output: * return GlhLineID The identifier of the line that is currently * being recalled, or 0 if no recall session is * currently in progress. */ GlhLineID _glh_line_id(GlHistory *glh, int offset) { GlhLineNode *node; /* The line location node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Search forward 'offset' lines to find the required line. */ if(offset >= 0) { for(node=glh->recall; node && offset != 0; node=node->next) { if(node->group == glh->group) offset--; }; } else { for(node=glh->recall; node && offset != 0; node=node->prev) { if(node->group == glh->group) offset++; }; }; return node ? node->id : 0; } /*....................................................................... * Recall a line by its history buffer ID. If the line is no longer * in the buffer, or the id is zero, NULL is returned. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlhLineID The ID of the line to be returned. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the saved line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * Search for the specified line. */ node = _glh_find_id(glh, id); /* * Not found? */ if(!node || node->group != glh->group) return NULL; /* * Record the node of the matching line as the starting point * for subsequent searches. */ glh->recall = node; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(node->line, line, dim); return line; } /*....................................................................... * Save the current history in a specified file. * * Input: * glh GlHistory * The input-line history maintenance object. * filename const char * The name of the new file to record the * history in. * comment const char * Extra information such as timestamps will * be recorded on a line started with this * string, the idea being that the file can * double as a command file. Specify "" if * you don't care. * max_lines int The maximum number of lines to save, or -1 * to save all of the lines in the history * list. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_save_history(GlHistory *glh, const char *filename, const char *comment, int max_lines) { #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(glh->err, "Can't save history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FILE *fp; /* The output file */ GlhLineNode *node; /* The line being saved */ GlhLineNode *head; /* The head of the list of lines to be saved */ GlhLineSeg *seg; /* One segment of a line being saved */ /* * Check the arguments. */ if(!glh || !filename || !comment) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Attempt to open the specified file. */ fp = fopen(filename, "w"); if(!fp) return _glh_cant_save_history(glh, "Can't open", filename, NULL); /* * If a ceiling on the number of lines to save was specified, count * that number of lines backwards, to find the first line to be saved. */ head = NULL; if(max_lines >= 0) { for(head=glh->list.tail; head && --max_lines > 0; head=head->prev) ; }; if(!head) head = glh->list.head; /* * Write the contents of the history buffer to the history file, writing * associated data such as timestamps, to a line starting with the * specified comment string. */ for(node=head; node; node=node->next) { /* * Write peripheral information associated with the line, as a comment. */ if(fprintf(fp, "%s ", comment) < 0 || _glh_write_timestamp(fp, node->timestamp) || fprintf(fp, " %u\n", node->group) < 0) { return _glh_cant_save_history(glh, "Error writing", filename, fp); }; /* * Write the history line. */ for(seg=node->line->head; seg; seg=seg->next) { size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s); if(fwrite(seg->s, sizeof(char), slen, fp) != slen) return _glh_cant_save_history(glh, "Error writing", filename, fp); }; fputc('\n', fp); }; /* * Close the history file. */ if(fclose(fp) == EOF) return _glh_cant_save_history(glh, "Error writing", filename, NULL); return 0; #endif } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * This is a private error return function of _glh_save_history(). It * composes an error report in the error buffer, composed using * sprintf("%s %s (%s)", message, filename, strerror(errno)). It then * closes fp and returns the error return code of _glh_save_history(). * * Input: * glh GlHistory * The input-line history maintenance object. * message const char * A message to be followed by the filename. * filename const char * The name of the offending output file. * fp FILE * The stream to be closed (send NULL if not * open). * Output: * return int Always 1. */ static int _glh_cant_save_history(GlHistory *glh, const char *message, const char *filename, FILE *fp) { _err_record_msg(glh->err, message, filename, " (", strerror(errno), ")", END_ERR_MSG); if(fp) (void) fclose(fp); return 1; } /*....................................................................... * Write a timestamp to a given stdio stream, in the format * yyyymmddhhmmss * * Input: * fp FILE * The stream to write to. * timestamp time_t The timestamp to be written. * Output: * return int 0 - OK. * 1 - Error. */ static int _glh_write_timestamp(FILE *fp, time_t timestamp) { struct tm *t; /* THe broken-down calendar time */ /* * Get the calendar components corresponding to the given timestamp. */ if(timestamp < 0 || (t = localtime(×tamp)) == NULL) { if(fprintf(fp, "?") < 0) return 1; return 0; }; /* * Write the calendar time as yyyymmddhhmmss. */ if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0) return 1; return 0; } #endif /*....................................................................... * Restore previous history lines from a given file. * * Input: * glh GlHistory * The input-line history maintenance object. * filename const char * The name of the file to read from. * comment const char * The same comment string that was passed to * _glh_save_history() when this file was * written. * line char * A buffer into which lines can be read. * dim size_t The allocated dimension of line[]. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_load_history(GlHistory *glh, const char *filename, const char *comment, char *line, size_t dim) { #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(glh->err, "Can't load history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FILE *fp; /* The output file */ size_t comment_len; /* The length of the comment string */ time_t timestamp; /* The timestamp of the history line */ unsigned group; /* The identifier of the history group to which */ /* the line belongs. */ int lineno; /* The line number being read */ /* * Check the arguments. */ if(!glh || !filename || !comment || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Measure the length of the comment string. */ comment_len = strlen(comment); /* * Clear the history list. */ _glh_clear_history(glh, 1); /* * Attempt to open the specified file. Don't treat it as an error * if the file doesn't exist. */ fp = fopen(filename, "r"); if(!fp) return 0; /* * Attempt to read each line and preceding peripheral info, and add these * to the history list. */ for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) { char *lptr; /* A pointer into the input line */ /* * Check that the line starts with the comment string. */ if(strncmp(line, comment, comment_len) != 0) { return _glh_cant_load_history(glh, filename, lineno, "Corrupt history parameter line", fp); }; /* * Skip spaces and tabs after the comment. */ for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++) ; /* * The next word must be a timestamp. */ if(_glh_decode_timestamp(lptr, &lptr, ×tamp)) { return _glh_cant_load_history(glh, filename, lineno, "Corrupt timestamp", fp); }; /* * Skip spaces and tabs. */ while(*lptr==' ' || *lptr=='\t') lptr++; /* * The next word must be an unsigned integer group number. */ group = (int) strtoul(lptr, &lptr, 10); if(*lptr != ' ' && *lptr != '\n') { return _glh_cant_load_history(glh, filename, lineno, "Corrupt group id", fp); }; /* * Skip spaces and tabs. */ while(*lptr==' ' || *lptr=='\t') lptr++; /* * There shouldn't be anything left on the line. */ if(*lptr != '\n') { return _glh_cant_load_history(glh, filename, lineno, "Corrupt parameter line", fp); }; /* * Now read the history line itself. */ lineno++; if(fgets(line, dim, fp) == NULL) return _glh_cant_load_history(glh, filename, lineno, "Read error", fp); /* * Append the line to the history buffer. */ if(_glh_add_history(glh, line, 1)) { return _glh_cant_load_history(glh, filename, lineno, "Insufficient memory to record line", fp); }; /* * Record the group and timestamp information along with the line. */ if(glh->list.tail) { glh->list.tail->timestamp = timestamp; glh->list.tail->group = group; }; }; /* * Close the file. */ (void) fclose(fp); return 0; #endif } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * This is a private error return function of _glh_load_history(). */ static int _glh_cant_load_history(GlHistory *glh, const char *filename, int lineno, const char *message, FILE *fp) { char lnum[20]; /* * Convert the line number to a string. */ sprintf(lnum, "%d", lineno); /* * Render an error message. */ _err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG); /* * Close the file. */ if(fp) (void) fclose(fp); return 1; } /*....................................................................... * Read a timestamp from a string. * * Input: * string char * The string to read from. * Input/Output: * endp char ** On output *endp will point to the next unprocessed * character in string[]. * timestamp time_t * The timestamp will be assigned to *t. * Output: * return int 0 - OK. * 1 - Error. */ static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp) { unsigned year,month,day,hour,min,sec; /* Calendar time components */ struct tm t; /* * There are 14 characters in the date format yyyymmddhhmmss. */ enum {TSLEN=14}; char timestr[TSLEN+1]; /* The timestamp part of the string */ /* * If the time wasn't available at the time that the line was recorded * it will have been written as "?". Check for this before trying * to read the timestamp. */ if(string[0] == '\?') { *endp = string+1; *timestamp = -1; return 0; }; /* * The timestamp is expected to be written in the form yyyymmddhhmmss. */ if(strlen(string) < TSLEN) { *endp = string; return 1; }; /* * Copy the timestamp out of the string. */ strncpy(timestr, string, TSLEN); timestr[TSLEN] = '\0'; /* * Decode the timestamp. */ if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min, &sec) != 6) { *endp = string; return 1; }; /* * Advance the string pointer over the successfully read timestamp. */ *endp = string + TSLEN; /* * Copy the read values into a struct tm. */ t.tm_sec = sec; t.tm_min = min; t.tm_hour = hour; t.tm_mday = day; t.tm_wday = 0; t.tm_yday = 0; t.tm_mon = month - 1; t.tm_year = year - 1900; t.tm_isdst = -1; /* * Convert the contents of the struct tm to a time_t. */ *timestamp = mktime(&t); return 0; } #endif /*....................................................................... * Switch history groups. * * Input: * glh GlHistory * The input-line history maintenance object. * group unsigned The new group identifier. This will be recorded * with subsequent history lines, and subsequent * history searches will only return lines with * this group identifier. This allows multiple * separate history lists to exist within * a single GlHistory object. Note that the * default group identifier is 0. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_set_group(GlHistory *glh, unsigned group) { /* * Check the arguments. */ if(!glh) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Is the group being changed? */ if(group != glh->group) { /* * Cancel any ongoing search. */ if(_glh_cancel_search(glh)) return 1; /* * Record the new group. */ glh->group = group; }; return 0; } /*....................................................................... * Query the current history group. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return unsigned The group identifier. */ int _glh_get_group(GlHistory *glh) { return glh ? glh->group : 0; } /*....................................................................... * Display the contents of the history list. * * Input: * glh GlHistory * The input-line history maintenance object. * write_fn GlWriteFn * The function to call to write the line, or * 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * fmt const char * A format string. This can contain arbitrary * characters, which are written verbatim, plus * any of the following format directives: * %D - The date, like 2001-11-20 * %T - The time of day, like 23:59:59 * %N - The sequential entry number of the * line in the history buffer. * %G - The history group number of the line. * %% - A literal % character. * %H - The history line. * all_groups int If true, display history lines from all * history groups. Otherwise only display * those of the current history group. * max_lines int If max_lines is < 0, all available lines * are displayed. Otherwise only the most * recent max_lines lines will be displayed. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data, const char *fmt, int all_groups, int max_lines) { GlhLineNode *node; /* The line being displayed */ GlhLineNode *oldest; /* The oldest line to display */ GlhLineSeg *seg; /* One segment of a line being displayed */ enum {TSMAX=32}; /* The maximum length of the date and time string */ char buffer[TSMAX+1]; /* The buffer in which to write the date and time */ int idlen; /* The length of displayed ID strings */ unsigned grpmax; /* The maximum group number in the buffer */ int grplen; /* The number of characters needed to print grpmax */ int len; /* The length of a string to be written */ /* * Check the arguments. */ if(!glh || !write_fn || !fmt) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->list.head) return 0; /* * Work out the length to display ID numbers, choosing the length of * the biggest number in the buffer. Smaller numbers will be padded * with leading zeroes if needed. */ sprintf(buffer, "%lu", (unsigned long) glh->list.tail->id); idlen = strlen(buffer); /* * Find the largest group number. */ grpmax = 0; for(node=glh->list.head; node; node=node->next) { if(node->group > grpmax) grpmax = node->group; }; /* * Find out how many characters are needed to display the group number. */ sprintf(buffer, "%u", (unsigned) grpmax); grplen = strlen(buffer); /* * Find the node that follows the oldest line to be displayed. */ if(max_lines < 0) { oldest = glh->list.head; } else if(max_lines==0) { return 0; } else { for(oldest=glh->list.tail; oldest; oldest=oldest->prev) { if((all_groups || oldest->group == glh->group) && --max_lines <= 0) break; }; /* * If the number of lines in the buffer doesn't exceed the specified * maximum, start from the oldest line in the buffer. */ if(!oldest) oldest = glh->list.head; }; /* * List the history lines in increasing time order. */ for(node=oldest; node; node=node->next) { /* * Only display lines from the current history group, unless * told otherwise. */ if(all_groups || node->group == glh->group) { const char *fptr; /* A pointer into the format string */ struct tm *t = NULL; /* The broken time version of the timestamp */ /* * Work out the calendar representation of the node timestamp. */ if(node->timestamp != (time_t) -1) t = localtime(&node->timestamp); /* * Parse the format string. */ fptr = fmt; while(*fptr) { /* * Search for the start of the next format directive or the end of the string. */ const char *start = fptr; while(*fptr && *fptr != '%') fptr++; /* * Display any literal characters that precede the located directive. */ if(fptr > start) { len = (int) (fptr - start); if(write_fn(data, start, len) != len) return 1; }; /* * Did we hit a new directive before the end of the line? */ if(*fptr) { /* * Obey the directive. Ignore unknown directives. */ switch(*++fptr) { case 'D': /* Display the date */ if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) { len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; }; break; case 'T': /* Display the time of day */ if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) { len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; }; break; case 'N': /* Display the sequential entry number */ sprintf(buffer, "%*lu", idlen, (unsigned long) node->id); len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; break; case 'G': sprintf(buffer, "%*u", grplen, (unsigned) node->group); len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; break; case 'H': /* Display the history line */ for(seg=node->line->head; seg; seg=seg->next) { len = seg->next ? GLH_SEG_SIZE : strlen(seg->s); if(write_fn(data, seg->s, len) != len) return 1; }; break; case '%': /* A literal % symbol */ if(write_fn(data, "%", 1) != 1) return 1; break; }; /* * Skip the directive. */ if(*fptr) fptr++; }; }; }; }; return 0; } /*....................................................................... * Change the size of the history buffer. * * Input: * glh GlHistory * The input-line history maintenance object. * bufsize size_t The number of bytes in the history buffer, or 0 * to delete the buffer completely. * Output: * return int 0 - OK. * 1 - Insufficient memory (the previous buffer * will have been retained). No error message * will be displayed. */ int _glh_resize_history(GlHistory *glh, size_t bufsize) { int nbuff; /* The number of segments in the new buffer */ int i; /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * How many buffer segments does the requested buffer size correspond * to? */ nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; /* * Has a different size than the current size been requested? */ if(glh->nbuff != nbuff) { /* * Cancel any ongoing search. */ (void) _glh_cancel_search(glh); /* * Create a wholly new buffer? */ if(glh->nbuff == 0 && nbuff>0) { glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff); if(!glh->buffer) return 1; glh->nbuff = nbuff; glh->nfree = glh->nbuff; glh->nbusy = 0; glh->nline = 0; /* * Link the currently unused nodes of the buffer into a list. */ glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; /* * Delete an existing buffer? */ } else if(nbuff == 0) { _glh_clear_history(glh, 1); free(glh->buffer); glh->buffer = NULL; glh->unused = NULL; glh->nbuff = 0; glh->nfree = 0; glh->nbusy = 0; glh->nline = 0; /* * Change from one finite buffer size to another? */ } else { GlhLineSeg *buffer; /* The resized buffer */ int nbusy; /* The number of used line segments in the new buffer */ /* * Starting from the oldest line in the buffer, discard lines until * the buffer contains at most 'nbuff' used line segments. */ while(glh->list.head && glh->nbusy > nbuff) _glh_discard_line(glh, glh->list.head); /* * Attempt to allocate a new buffer. */ buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg)); if(!buffer) { errno = ENOMEM; return 1; }; /* * Copy the used segments of the old buffer to the start of the new buffer. */ nbusy = 0; for(i=0; ihash.bucket + i; GlhHashNode *hnode; for(hnode=b->lines; hnode; hnode=hnode->next) { GlhLineSeg *seg = hnode->head; hnode->head = buffer + nbusy; for( ; seg; seg=seg->next) { buffer[nbusy] = *seg; buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL; nbusy++; }; }; }; /* * Make a list of the new buffer's unused segments. */ for(i=nbusy; ibuffer); /* * Install the new buffer. */ glh->buffer = buffer; glh->nbuff = nbuff; glh->nbusy = nbusy; glh->nfree = nbuff - nbusy; glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL; }; }; return 0; } /*....................................................................... * Set an upper limit to the number of lines that can be recorded in the * history list, or remove a previously specified limit. * * Input: * glh GlHistory * The input-line history maintenance object. * max_lines int The maximum number of lines to allow, or -1 to * cancel a previous limit and allow as many lines * as will fit in the current history buffer size. */ void _glh_limit_history(GlHistory *glh, int max_lines) { if(!glh) return; /* * Apply a new limit? */ if(max_lines >= 0 && max_lines != glh->max_lines) { /* * Count successively older lines until we reach the start of the * list, or until we have seen max_lines lines (at which point 'node' * will be line number max_lines+1). */ int nline = 0; GlhLineNode *node; for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev) ; /* * Discard any lines that exceed the limit. */ if(node) { GlhLineNode *oldest = node->next; /* The oldest line to be kept */ /* * Delete nodes from the head of the list until we reach the node that * is to be kept. */ while(glh->list.head && glh->list.head != oldest) _glh_discard_line(glh, glh->list.head); }; }; /* * Record the new limit. */ glh->max_lines = max_lines; return; } /*....................................................................... * Discard either all history, or the history associated with the current * history group. * * Input: * glh GlHistory * The input-line history maintenance object. * all_groups int If true, clear all of the history. If false, * clear only the stored lines associated with the * currently selected history group. */ void _glh_clear_history(GlHistory *glh, int all_groups) { int i; /* * Check the arguments. */ if(!glh) return; /* * Cancel any ongoing search. */ (void) _glh_cancel_search(glh); /* * Delete all history lines regardless of group? */ if(all_groups) { /* * Claer the time-ordered list of lines. */ _rst_FreeList(glh->list.node_mem); glh->list.head = glh->list.tail = NULL; glh->nline = 0; glh->id_node = NULL; /* * Clear the hash table. */ for(i=0; ihash.bucket[i].lines = NULL; _rst_FreeList(glh->hash.node_mem); /* * Move all line segment nodes back onto the list of unused segments. */ if(glh->buffer) { glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; glh->nfree = glh->nbuff; glh->nbusy = 0; } else { glh->unused = NULL; glh->nbusy = glh->nfree = 0; }; /* * Just delete lines of the current group? */ } else { GlhLineNode *node; /* The line node being checked */ GlhLineNode *next; /* The line node that follows 'node' */ /* * Search out and delete the line nodes of the current group. */ for(node=glh->list.head; node; node=next) { /* * Keep a record of the following node before we delete the current * node. */ next = node->next; /* * Discard this node? */ if(node->group == glh->group) _glh_discard_line(glh, node); }; }; return; } /*....................................................................... * Temporarily enable or disable the history list. * * Input: * glh GlHistory * The input-line history maintenance object. * enable int If true, turn on the history mechanism. If * false, disable it. */ void _glh_toggle_history(GlHistory *glh, int enable) { if(glh) glh->enable = enable; } /*....................................................................... * Discard a given archived input line. * * Input: * glh GlHistory * The history container object. * node GlhLineNode * The line to be discarded, specified via its * entry in the time-ordered list of historical * input lines. */ static void _glh_discard_line(GlHistory *glh, GlhLineNode *node) { /* * Remove the node from the linked list. */ if(node->prev) node->prev->next = node->next; else glh->list.head = node->next; if(node->next) node->next->prev = node->prev; else glh->list.tail = node->prev; /* * If we are deleting the node that is marked as the start point of the * last ID search, remove the cached starting point. */ if(node == glh->id_node) glh->id_node = NULL; /* * If we are deleting the node that is marked as the start point of the * next prefix search, cancel the search. */ if(node == glh->recall) _glh_cancel_search(glh); /* * Delete our copy of the line. */ node->line = _glh_discard_copy(glh, node->line); /* * Return the node to the freelist. */ (void) _del_FreeListNode(glh->list.node_mem, node); /* * Record the removal of a line from the list. */ glh->nline--; return; } /*....................................................................... * Lookup the details of a given history line, given its id. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlLineID The sequential number of the line. * Input/Output: * line const char ** A pointer to a copy of the history line will be * assigned to *line. Beware that this pointer may * be invalidated by the next call to any public * history function. * group unsigned * The group membership of the line will be assigned * to *group. * timestamp time_t * The timestamp of the line will be assigned to * *timestamp. * Output: * return int 0 - The requested line wasn't found. * 1 - The line was found. */ int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line, unsigned *group, time_t *timestamp) { GlhLineNode *node; /* The located line location node */ /* * Check the arguments. */ if(!glh) return 0; /* * Search for the line that has the specified ID. */ node = _glh_find_id(glh, id); /* * Not found? */ if(!node) return 0; /* * Has the history line been requested? */ if(line) { /* * If necessary, reallocate the lookup buffer to accomodate the size of * a copy of the located line. */ if(node->line->len + 1 > glh->lbuf_dim) { int lbuf_dim = node->line->len + 1; char *lbuf = realloc(glh->lbuf, lbuf_dim); if(!lbuf) { errno = ENOMEM; return 0; }; glh->lbuf_dim = lbuf_dim; glh->lbuf = lbuf; }; /* * Copy the history line into the lookup buffer. */ _glh_return_line(node->line, glh->lbuf, glh->lbuf_dim); /* * Assign the lookup buffer as the returned line pointer. */ *line = glh->lbuf; }; /* * Does the caller want to know the group of the line? */ if(group) *group = node->group; /* * Does the caller want to know the timestamp of the line? */ if(timestamp) *timestamp = node->timestamp; return 1; } /*....................................................................... * Lookup a node in the history list by its ID. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlhLineID The ID of the line to be returned. * Output: * return GlhLIneNode * The located node, or NULL if not found. */ static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id) { GlhLineNode *node; /* The node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->list.head) return NULL; /* * If possible, start at the end point of the last ID search. * Otherwise start from the head of the list. */ node = glh->id_node; if(!node) node = glh->list.head; /* * Search forwards from 'node'? */ if(node->id < id) { while(node && node->id != id) node = node->next; glh->id_node = node ? node : glh->list.tail; /* * Search backwards from 'node'? */ } else { while(node && node->id != id) node = node->prev; glh->id_node = node ? node : glh->list.head; }; /* * Return the located node (this will be NULL if the ID wasn't found). */ return node; } /*....................................................................... * Query the state of the history list. Note that any of the input/output * pointers can be specified as NULL. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * enabled int * If history is enabled, *enabled will be * set to 1. Otherwise it will be assigned 0. * group unsigned * The current history group ID will be assigned * to *group. * max_lines int * The currently requested limit on the number * of history lines in the list, or -1 if * unlimited. */ void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group, int *max_lines) { if(glh) { if(enabled) *enabled = glh->enable; if(group) *group = glh->group; if(max_lines) *max_lines = glh->max_lines; }; } /*....................................................................... * Get the range of lines in the history buffer. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * oldest unsigned long * The sequential entry number of the oldest * line in the history list will be assigned * to *oldest, unless there are no lines, in * which case 0 will be assigned. * newest unsigned long * The sequential entry number of the newest * line in the history list will be assigned * to *newest, unless there are no lines, in * which case 0 will be assigned. * nlines int * The number of lines currently in the history * list. */ void _glh_range_of_history(GlHistory *glh, unsigned long *oldest, unsigned long *newest, int *nlines) { if(glh) { if(oldest) *oldest = glh->list.head ? glh->list.head->id : 0; if(newest) *newest = glh->list.tail ? glh->list.tail->id : 0; if(nlines) *nlines = glh->nline; }; } /*....................................................................... * Return the size of the history buffer and the amount of the * buffer that is currently in use. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * buff_size size_t * The size of the history buffer (bytes). * buff_used size_t * The amount of the history buffer that * is currently occupied (bytes). */ void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used) { if(glh) { if(buff_size) *buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE; /* * Determine the amount of buffer space that is currently occupied. */ if(buff_used) *buff_used = glh->nbusy * GLH_SEG_SIZE; }; } /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in any of the public functions of this * module. * * Input: * glh GlHistory * The container of the history list. * Output: * return const char * A pointer to the internal buffer in which * the error message is temporarily stored. */ const char *_glh_last_error(GlHistory *glh) { return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument"; } /*....................................................................... * Unless already stored, store a copy of the line in the history buffer, * then return a reference-counted hash-node pointer to this copy. * * Input: * glh GlHistory * The history maintenance buffer. * line const char * The history line to be recorded. * n size_t The length of the string, excluding any '\0' * terminator. * Output: * return GlhHashNode * The hash-node containing the stored line, or * NULL on error. */ static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, size_t n) { GlhHashBucket *bucket; /* The hash-table bucket of the line */ GlhHashNode *hnode; /* The hash-table node of the line */ int i; /* * In which bucket should the line be recorded? */ bucket = glh_find_bucket(glh, line, n); /* * Is the line already recorded there? */ hnode = glh_find_hash_node(bucket, line, n); /* * If the line isn't recorded in the buffer yet, make room for it. */ if(!hnode) { GlhLineSeg *seg; /* A line segment */ int offset; /* An offset into line[] */ /* * How many string segments will be needed to record the new line, * including space for a '\0' terminator? */ int nseg = ((n+1) + GLH_SEG_SIZE-1) / GLH_SEG_SIZE; /* * Discard the oldest history lines in the buffer until at least * 'nseg' segments have been freed up, or until we run out of buffer * space. */ while(glh->nfree < nseg && glh->nbusy > 0) _glh_discard_line(glh, glh->list.head); /* * If the buffer is smaller than the new line, don't attempt to truncate * it to fit. Simply don't archive it. */ if(glh->nfree < nseg) return NULL; /* * Record the line in the first 'nseg' segments of the list of unused segments. */ offset = 0; for(i=0,seg=glh->unused; inext, offset+=GLH_SEG_SIZE) memcpy(seg->s, line + offset, GLH_SEG_SIZE); memcpy(seg->s, line + offset, n-offset); seg->s[n-offset] = '\0'; /* * Create a new hash-node for the line. */ hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem); if(!hnode) return NULL; /* * Move the copy of the line from the list of unused segments to * the hash node. */ hnode->head = glh->unused; glh->unused = seg->next; seg->next = NULL; glh->nbusy += nseg; glh->nfree -= nseg; /* * Prepend the new hash node to the list within the associated bucket. */ hnode->next = bucket->lines; bucket->lines = hnode; /* * Initialize the rest of the members of the hash node. */ hnode->len = n; hnode->reported = 0; hnode->used = 0; hnode->bucket = bucket; }; /* * Increment the reference count of the line. */ hnode->used++; return hnode; } /*....................................................................... * Decrement the reference count of the history line of a given hash-node, * and if the count reaches zero, delete both the hash-node and the * buffered copy of the line. * * Input: * glh GlHistory * The history container object. * hnode GlhHashNode * The node to be removed. * Output: * return GlhHashNode * The deleted hash-node (ie. NULL). */ static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode) { if(hnode) { GlhHashBucket *bucket = hnode->bucket; /* * If decrementing the reference count of the hash-node doesn't reduce * the reference count to zero, then the line is still in use in another * object, so don't delete it yet. Return NULL to indicate that the caller's * access to the hash-node copy has been deleted. */ if(--hnode->used >= 1) return NULL; /* * Remove the hash-node from the list in its parent bucket. */ if(bucket->lines == hnode) { bucket->lines = hnode->next; } else { GlhHashNode *prev; /* The node which precedes hnode in the bucket */ for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next) ; if(prev) prev->next = hnode->next; }; hnode->next = NULL; /* * Return the line segments of the hash-node to the list of unused segments. */ if(hnode->head) { GlhLineSeg *tail; /* The last node in the list of line segments */ int nseg; /* The number of segments being discarded */ /* * Get the last node of the list of line segments referenced in the hash-node, * while counting the number of line segments used. */ for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next) ; /* * Prepend the list of line segments used by the hash node to the * list of unused line segments. */ tail->next = glh->unused; glh->unused = hnode->head; glh->nbusy -= nseg; glh->nfree += nseg; }; /* * Return the container of the hash-node to the freelist. */ hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode); }; return NULL; } /*....................................................................... * Private function to locate the hash bucket associated with a given * history line. * * This uses a hash-function described in the dragon-book * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and * Ullman; pub. Adison Wesley) page 435. * * Input: * glh GlHistory * The history container object. * line const char * The historical line to look up. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return GlhHashBucket * The located hash-bucket. */ static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, size_t n) { unsigned long h = 0L; int i; for(i=0; ihash.bucket + (h % GLH_HASH_SIZE); } /*....................................................................... * Find a given history line within a given hash-table bucket. * * Input: * bucket GlhHashBucket * The hash-table bucket in which to search. * line const char * The historical line to lookup. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return GlhHashNode * The hash-table entry of the line, or NULL * if not found. */ static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, size_t n) { GlhHashNode *node; /* A node in the list of lines in the bucket */ /* * Compare each of the lines in the list of lines, against 'line'. */ for(node=bucket->lines; node; node=node->next) { if(_glh_is_line(node, line, n)) return node; }; return NULL; } /*....................................................................... * Return non-zero if a given string is equal to a given segmented line * node. * * Input: * hash GlhHashNode * The hash-table entry of the line. * line const char * The string to be compared to the segmented * line. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return int 0 - The lines differ. * 1 - The lines are the same. */ static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n) { GlhLineSeg *seg; /* A node in the list of line segments */ int i; /* * Do the two lines have the same length? */ if(n != hash->len) return 0; /* * Compare the characters of the segmented and unsegmented versions * of the line. */ for(seg=hash->head; n>0 && seg; seg=seg->next) { const char *s = seg->s; for(i=0; n>0 && ilen > line->len) return 0; /* * Compare the line to the prefix. */ while(pstr.c != '\0' && pstr.c == lstr.c) { glh_step_stream(&lstr); glh_step_stream(&pstr); }; /* * Did we reach the end of the prefix string before finding * any differences? */ return pstr.c == '\0'; } /*....................................................................... * Copy a given history line into a specified output string. * * Input: * hash GlhHashNode The hash-table entry of the history line to * be copied. * line char * A copy of the history line. * dim size_t The allocated dimension of the line buffer. */ static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim) { GlhLineSeg *seg; /* A node in the list of line segments */ int i; for(seg=hash->head; dim>0 && seg; seg=seg->next) { const char *s = seg->s; for(i=0; dim>0 && irecall && glh->recall == glh->list.tail && !_glh_is_line(glh->recall->line, line, strlen(line))) { _glh_cancel_search(glh); }; /* * If this is the first line recall of a new recall session, save the * current line for potential recall later, and mark it as the last * line recalled. */ if(!glh->recall) { if(_glh_add_history(glh, line, 1)) return 1; glh->recall = glh->list.tail; /* * The above call to _glh_add_history() will have incremented the line * sequence number, after adding the line. Since we only want this to * to be incremented for permanently entered lines, decrement it again. */ glh->seq--; }; return 0; } /*....................................................................... * Return non-zero if a history search session is currently in progress. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return int 0 - No search is currently in progress. * 1 - A search is in progress. */ int _glh_search_active(GlHistory *glh) { return glh && glh->recall; } /*....................................................................... * Initialize a character iterator object to point to the start of a * given history line. The first character of the line will be placed * in str->c, and subsequent characters can be placed there by calling * glh_strep_stream(). * * Input: * str GlhLineStream * The iterator object to be initialized. * line GlhHashNode * The history line to be iterated over (a * NULL value here, is interpretted as an * empty string by glh_step_stream()). */ static void glh_init_stream(GlhLineStream *str, GlhHashNode *line) { str->seg = line ? line->head : NULL; str->posn = 0; str->c = str->seg ? str->seg->s[0] : '\0'; } /*....................................................................... * Copy the next unread character in the line being iterated, in str->c. * Once the end of the history line has been reached, all futher calls * set str->c to '\0'. * * Input: * str GlhLineStream * The history-line iterator to read from. */ static void glh_step_stream(GlhLineStream *str) { /* * Get the character from the current iterator position within the line. */ str->c = str->seg ? str->seg->s[str->posn] : '\0'; /* * Unless we have reached the end of the string, move the iterator * to the position of the next character in the line. */ if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) { str->posn = 0; str->seg = str->seg->next; }; } /*....................................................................... * Return non-zero if the specified search prefix contains any glob * wildcard characters. * * Input: * prefix GlhHashNode * The search prefix. * Output: * return int 0 - The prefix doesn't contain any globbing * characters. * 1 - The prefix contains at least one * globbing character. */ static int glh_contains_glob(GlhHashNode *prefix) { GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ /* * Wrap a stream iterator around the prefix, so that we can traverse it * without worrying about line-segmentation. */ glh_init_stream(&pstr, prefix); /* * Search for unescaped wildcard characters. */ while(pstr.c != '\0') { switch(pstr.c) { case '\\': /* Skip escaped characters */ glh_step_stream(&pstr); break; case '*': case '?': case '[': /* A wildcard character? */ return 1; break; }; glh_step_stream(&pstr); }; /* * No wildcard characters were found. */ return 0; } /*....................................................................... * Return non-zero if the history line matches a search prefix containing * a glob pattern. * * Input: * lstr GlhLineStream * The iterator stream being used to traverse * the history line that is being matched. * pstr GlhLineStream * The iterator stream being used to traverse * the pattern. * Output: * return int 0 - Doesn't match. * 1 - The line matches the pattern. */ static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr) { /* * Match each character of the pattern until we reach the end of the * pattern. */ while(pstr->c != '\0') { /* * Handle the next character of the pattern. */ switch(pstr->c) { /* * A match zero-or-more characters wildcard operator. */ case '*': /* * Skip the '*' character in the pattern. */ glh_step_stream(pstr); /* * If the pattern ends with the '*' wildcard, then the * rest of the line matches this. */ if(pstr->c == '\0') return 1; /* * Using the wildcard to match successively longer sections of * the remaining characters of the line, attempt to match * the tail of the line against the tail of the pattern. */ while(lstr->c) { GlhLineStream old_lstr = *lstr; GlhLineStream old_pstr = *pstr; if(glh_line_matches_glob(lstr, pstr)) return 1; /* * Restore the line and pattern iterators for a new try. */ *lstr = old_lstr; *pstr = old_pstr; /* * Prepare to try again, one character further into the line. */ glh_step_stream(lstr); }; return 0; /* The pattern following the '*' didn't match */ break; /* * A match-one-character wildcard operator. */ case '?': /* * If there is a character to be matched, skip it and advance the * pattern pointer. */ if(lstr->c) { glh_step_stream(lstr); glh_step_stream(pstr); /* * If we hit the end of the line, there is no character * matching the operator, so the pattern doesn't match. */ } else { return 0; }; break; /* * A character range operator, with the character ranges enclosed * in matching square brackets. */ case '[': glh_step_stream(pstr); /* Skip the '[' character */ if(!lstr->c || !glh_matches_range(lstr->c, pstr)) return 0; glh_step_stream(lstr); /* Skip the character that matched */ break; /* * A backslash in the pattern prevents the following character as * being seen as a special character. */ case '\\': glh_step_stream(pstr); /* Skip the backslash */ /* Note fallthrough to default */ /* * A normal character to be matched explicitly. */ default: if(lstr->c == pstr->c) { glh_step_stream(lstr); glh_step_stream(pstr); } else { return 0; }; break; }; }; /* * To get here, pattern must have been exhausted. The line only * matches the pattern if the line as also been exhausted. */ return pstr->c == '\0' && lstr->c == '\0'; } /*....................................................................... * Match a character range expression terminated by an unescaped close * square bracket. * * Input: * c char The character to be matched with the range * pattern. * pstr GlhLineStream * The iterator stream being used to traverse * the pattern. * Output: * return int 0 - Doesn't match. * 1 - The character matched. */ static int glh_matches_range(char c, GlhLineStream *pstr) { int invert = 0; /* True to invert the sense of the match */ int matched = 0; /* True if the character matched the pattern */ char lastc = '\0'; /* The previous character in the pattern */ /* * If the first character is a caret, the sense of the match is * inverted and only if the character isn't one of those in the * range, do we say that it matches. */ if(pstr->c == '^') { glh_step_stream(pstr); invert = 1; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret). */ if(pstr->c == '-') { glh_step_stream(pstr); if(c == '-') matched = 1; /* * Skip other leading '-' characters since they make no sense. */ while(pstr->c == '-') glh_step_stream(pstr); }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret or a hyphen). */ if(pstr->c == ']') { glh_step_stream(pstr); if(c == ']') matched = 1; }; /* * Having dealt with the characters that have special meanings at * the beginning of a character range expression, see if the * character matches any of the remaining characters of the range, * up until a terminating ']' character is seen. */ while(!matched && pstr->c && pstr->c != ']') { /* * Is this a range of characters signaled by the two end characters * separated by a hyphen? */ if(pstr->c == '-') { glh_step_stream(pstr); /* Skip the hyphen */ if(pstr->c != ']') { if(c >= lastc && c <= pstr->c) matched = 1; }; /* * A normal character to be compared directly. */ } else if(pstr->c == c) { matched = 1; }; /* * Record and skip the character that we just processed. */ lastc = pstr->c; if(pstr->c != ']') glh_step_stream(pstr); }; /* * Find the terminating ']'. */ while(pstr->c && pstr->c != ']') glh_step_stream(pstr); /* * Did we find a terminating ']'? */ if(pstr->c == ']') { /* * Skip the terminating ']'. */ glh_step_stream(pstr); /* * If the pattern started with a caret, invert the sense of the match. */ if(invert) matched = !matched; /* * If the pattern didn't end with a ']', then it doesn't match, * regardless of the value of the required sense of the match. */ } else { matched = 0; }; return matched; } ./libtecla/history.h0100644000076400007640000001266010027466660013017 0ustar mcsmcs#ifndef history_h #define history_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include /* FILE * */ /*----------------------------------------------------------------------- * This module is used to record and traverse historical lines of user input. */ typedef struct GlHistory GlHistory; /* * Create a new history maintenance object. */ GlHistory *_new_GlHistory(size_t buflen); /* * Delete a history maintenance object. */ GlHistory *_del_GlHistory(GlHistory *glh); int _glh_add_history(GlHistory *glh, const char *line, int force); int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len); char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim); char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim); int _glh_cancel_search(GlHistory *glh); char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim); char *_glh_current_line(GlHistory *glh, char *line, size_t dim); /* * Whenever a new line is added to the history buffer, it is given * a unique ID, recorded in an object of the following type. */ typedef unsigned long GlhLineID; /* * Query the id of a history line offset by a given number of lines from * the one that is currently being recalled. If a recall session isn't * in progress, or the offset points outside the history list, 0 is * returned. */ GlhLineID _glh_line_id(GlHistory *glh, int offset); /* * Recall a line by its history buffer ID. If the line is no longer * in the buffer, or the specified id is zero, NULL is returned. */ char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim); /* * Write the contents of the history buffer to a given file. Note that * ~ and $ expansion are not performed on the filename. */ int _glh_save_history(GlHistory *glh, const char *filename, const char *comment, int max_lines); /* * Restore the contents of the history buffer from a given file. * Note that ~ and $ expansion are not performed on the filename. */ int _glh_load_history(GlHistory *glh, const char *filename, const char *comment, char *line, size_t dim); /* * Set and query the current history group. */ int _glh_set_group(GlHistory *glh, unsigned group); int _glh_get_group(GlHistory *glh); /* * Display the contents of the history list to the specified stdio * output group. */ int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data, const char *fmt, int all_groups, int max_lines); /* * Change the size of the history buffer. */ int _glh_resize_history(GlHistory *glh, size_t bufsize); /* * Set an upper limit to the number of lines that can be recorded in the * history list, or remove a previously specified limit. */ void _glh_limit_history(GlHistory *glh, int max_lines); /* * Discard either all history, or the history associated with the current * history group. */ void _glh_clear_history(GlHistory *glh, int all_groups); /* * Temporarily enable or disable the history facility. */ void _glh_toggle_history(GlHistory *glh, int enable); /* * Lookup a history line by its sequential number of entry in the * history buffer. */ int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line, unsigned *group, time_t *timestamp); /* * Query the state of the history list. */ void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group, int *max_lines); /* * Get the range of lines in the history buffer. */ void _glh_range_of_history(GlHistory *glh, unsigned long *oldest, unsigned long *newest, int *nlines); /* * Return the size of the history buffer and the amount of the * buffer that is currently in use. */ void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used); /* * Get information about the last error in this module. */ const char *_glh_last_error(GlHistory *glh); /* * Return non-zero if a history search session is currently in progress. */ int _glh_search_active(GlHistory *glh); #endif ./libtecla/homedir.c0100644000076400007640000003544410027466660012745 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM #include #include #include #include #include #include #include #include #include "pathutil.h" #include "homedir.h" #include "errmsg.h" /* * Use the reentrant POSIX threads versions of the password lookup functions? */ #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L #define THREAD_COMPATIBLE 1 /* * Under Solaris we can use thr_main() to determine whether * threads are actually running, and thus when it is necessary * to avoid non-reentrant features. */ #if defined __sun && defined __SVR4 #include /* Solaris thr_main() */ #endif #endif /* * Provide a password buffer size fallback in case the max size reported * by sysconf() is said to be indeterminate. */ #define DEF_GETPW_R_SIZE_MAX 1024 /* * The resources needed to lookup and record a home directory are * maintained in objects of the following type. */ struct HomeDir { ErrMsg *err; /* The error message report buffer */ char *buffer; /* A buffer for reading password entries and */ /* directory paths. */ int buflen; /* The allocated size of buffer[] */ #ifdef THREAD_COMPATIBLE struct passwd pwd; /* The password entry of a user */ #endif }; static const char *hd_getpwd(HomeDir *home); /*....................................................................... * Create a new HomeDir object. * * Output: * return HomeDir * The new object, or NULL on error. */ HomeDir *_new_HomeDir(void) { HomeDir *home; /* The object to be returned */ size_t pathlen; /* The estimated maximum size of a pathname */ /* * Allocate the container. */ home = (HomeDir *) malloc(sizeof(HomeDir)); if(!home) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_HomeDir(). */ home->err = NULL; home->buffer = NULL; home->buflen = 0; /* * Allocate a place to record error messages. */ home->err = _new_ErrMsg(); if(!home->err) return _del_HomeDir(home); /* * Allocate the buffer that is used by the reentrant POSIX password-entry * lookup functions. */ #ifdef THREAD_COMPATIBLE /* * Get the length of the buffer needed by the reentrant version * of getpwnam(). */ #ifndef _SC_GETPW_R_SIZE_MAX home->buflen = DEF_GETPW_R_SIZE_MAX; #else errno = 0; home->buflen = sysconf(_SC_GETPW_R_SIZE_MAX); /* * If the limit isn't available, substitute a suitably large fallback value. */ if(home->buflen < 0 || errno) home->buflen = DEF_GETPW_R_SIZE_MAX; #endif #endif /* * If the existing buffer length requirement is too restrictive to record * a pathname, increase its length. */ pathlen = _pu_pathname_dim(); if(pathlen > home->buflen) home->buflen = pathlen; /* * Allocate a work buffer. */ home->buffer = (char *) malloc(home->buflen); if(!home->buffer) { errno = ENOMEM; return _del_HomeDir(home); }; return home; } /*....................................................................... * Delete a HomeDir object. * * Input: * home HomeDir * The object to be deleted. * Output: * return HomeDir * The deleted object (always NULL). */ HomeDir *_del_HomeDir(HomeDir *home) { if(home) { home->err = _del_ErrMsg(home->err); if(home->buffer) free(home->buffer); free(home); }; return NULL; } /*....................................................................... * Lookup the home directory of a given user in the password file. * * Input: * home HomeDir * The resources needed to lookup the home directory. * user const char * The name of the user to lookup, or "" to lookup * the home directory of the person running the * program. * Output: * return const char * The home directory. If the library was compiled * with threads, this string is part of the HomeDir * object and will change on subsequent calls. If * the library wasn't compiled to be reentrant, * then the string is a pointer into a static string * in the C library and will change not only on * subsequent calls to this function, but also if * any calls are made to the C library password * file lookup functions. Thus to be safe, you should * make a copy of this string before calling any * other function that might do a password file * lookup. * * On error, NULL is returned and a description * of the error can be acquired by calling * _hd_last_home_dir_error(). */ const char *_hd_lookup_home_dir(HomeDir *home, const char *user) { const char *home_dir; /* A pointer to the home directory of the user */ /* * If no username has been specified, arrange to lookup the current * user. */ int login_user = !user || *user=='\0'; /* * Check the arguments. */ if(!home) { errno = EINVAL; return NULL; }; /* * Handle the ksh "~+". This expands to the absolute path of the * current working directory. */ if(!login_user && strcmp(user, "+") == 0) { home_dir = hd_getpwd(home); if(!home_dir) { _err_record_msg(home->err, "Can't determine current directory", END_ERR_MSG); return NULL; } return home_dir; }; /* * When looking up the home directory of the current user, see if the * HOME environment variable is set, and if so, return its value. */ if(login_user) { home_dir = getenv("HOME"); if(home_dir) return home_dir; }; /* * Look up the password entry of the user. * First the POSIX threads version - this is painful! */ #ifdef THREAD_COMPATIBLE { struct passwd *ret; /* The returned pointer to pwd */ int status; /* The return value of getpwnam_r() */ /* * Look up the password entry of the specified user. */ if(login_user) status = getpwuid_r(geteuid(), &home->pwd, home->buffer, home->buflen, &ret); else status = getpwnam_r(user, &home->pwd, home->buffer, home->buflen, &ret); if(status || !ret) { _err_record_msg(home->err, "User '", user, "' doesn't exist.", END_ERR_MSG); return NULL; }; /* * Get a pointer to the string that holds the home directory. */ home_dir = home->pwd.pw_dir; }; /* * Now the classic unix version. */ #else { struct passwd *pwd = login_user ? getpwuid(geteuid()) : getpwnam(user); if(!pwd) { _err_record_msg(home->err, "User '", user, "' doesn't exist.", END_ERR_MSG); return NULL; }; /* * Get a pointer to the home directory. */ home_dir = pwd->pw_dir; }; #endif return home_dir; } /*....................................................................... * Return a description of the last error that caused _hd_lookup_home_dir() * to return NULL. * * Input: * home HomeDir * The resources needed to record the home directory. * Output: * return char * The description of the last error. */ const char *_hd_last_home_dir_error(HomeDir *home) { return home ? _err_get_msg(home->err) : "NULL HomeDir argument"; } /*....................................................................... * The _hd_scan_user_home_dirs() function calls a user-provided function * for each username known by the system, passing the function both * the name and the home directory of the user. * * Input: * home HomeDir * The resource object for reading home * directories. * prefix const char * Only information for usernames that * start with this prefix will be * returned. Note that the empty & string "", matches all usernames. * data void * Anonymous data to be passed to the * callback function. * callback_fn HOME_DIR_FN(*) The function to call for each user. * Output: * return int 0 - Successful completion. * 1 - An error occurred. A description * of the error can be obtained by * calling _hd_last_home_dir_error(). */ int _hd_scan_user_home_dirs(HomeDir *home, const char *prefix, void *data, HOME_DIR_FN(*callback_fn)) { int waserr = 0; /* True after errors */ int prefix_len; /* The length of prefix[] */ /* * Check the arguments. */ if(!home || !prefix || !callback_fn) { if(home) { _err_record_msg(home->err, "_hd_scan_user_home_dirs: Missing callback function", END_ERR_MSG); }; return 1; }; /* * Get the length of the username prefix. */ prefix_len = strlen(prefix); /* * There are no reentrant versions of getpwent() etc for scanning * the password file, so disable username completion when the * library is compiled to be reentrant. */ #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L #if defined __sun && defined __SVR4 if(thr_main() >= 0) /* thread library is linked in */ #else if(1) #endif { struct passwd pwd_buffer; /* A returned password entry */ struct passwd *pwd; /* A pointer to pwd_buffer */ char buffer[512]; /* The buffer in which the string members of */ /* pwd_buffer are stored. */ /* * See if the prefix that is being completed is a complete username. */ if(!waserr && getpwnam_r(prefix, &pwd_buffer, buffer, sizeof(buffer), &pwd) == 0 && pwd != NULL) { waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir, _err_get_msg(home->err), ERR_MSG_LEN); }; /* * See if the username of the current user minimally matches the prefix. */ if(!waserr && getpwuid_r(getuid(), &pwd_buffer, buffer, sizeof(buffer), &pwd) == 0 && pwd != NULL && strncmp(prefix, pwd->pw_name, prefix_len)==0) { waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir, _err_get_msg(home->err), ERR_MSG_LEN); }; /* * Reentrancy not required? */ } else #endif { struct passwd *pwd; /* The pointer to the latest password entry */ /* * Open the password file. */ setpwent(); /* * Read the contents of the password file, looking for usernames * that start with the specified prefix, and adding them to the * list of matches. */ while((pwd = getpwent()) != NULL && !waserr) { if(strncmp(prefix, pwd->pw_name, prefix_len) == 0) { waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir, _err_get_msg(home->err), ERR_MSG_LEN); }; }; /* * Close the password file. */ endpwent(); }; /* * Under ksh ~+ stands for the absolute pathname of the current working * directory. */ if(!waserr && strncmp(prefix, "+", prefix_len) == 0) { const char *pwd = hd_getpwd(home); if(pwd) { waserr = callback_fn(data, "+", pwd, _err_get_msg(home->err),ERR_MSG_LEN); } else { waserr = 1; _err_record_msg(home->err, "Can't determine current directory.", END_ERR_MSG); }; }; return waserr; } /*....................................................................... * Return the value of getenv("PWD") if this points to the current * directory, or the return value of getcwd() otherwise. The reason for * prefering PWD over getcwd() is that the former preserves the history * of symbolic links that have been traversed to reach the current * directory. This function is designed to provide the equivalent * expansion of the ksh ~+ directive, which normally returns its value * of PWD. * * Input: * home HomeDir * The resource object for reading home directories. * Output: * return const char * A pointer to either home->buffer, where the * pathname is recorded, the string returned by * getenv("PWD"), or NULL on error. */ static const char *hd_getpwd(HomeDir *home) { /* * Get the absolute path of the current working directory. */ char *cwd = getcwd(home->buffer, home->buflen); /* * Some shells set PWD with the path of the current working directory. * This will differ from cwd in that it won't have had symbolic links * expanded. */ const char *pwd = getenv("PWD"); /* * If PWD was set, and it points to the same directory as cwd, return * its value. Note that it won't be the same if the current shell or * the current program has changed directories, after inheriting PWD * from a parent shell. */ struct stat cwdstat, pwdstat; if(pwd && cwd && stat(cwd, &cwdstat)==0 && stat(pwd, &pwdstat)==0 && cwdstat.st_dev == pwdstat.st_dev && cwdstat.st_ino == pwdstat.st_ino) return pwd; /* * Also return pwd if getcwd() failed, since it represents the best * information that we have access to. */ if(!cwd) return pwd; /* * In the absence of a valid PWD, return cwd. */ return cwd; } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/homedir.h0100644000076400007640000000676410027466660012755 0ustar mcsmcs#ifndef homedir_h #define homedir_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ typedef struct HomeDir HomeDir; /* * The following constructor and destructor functions create and * delete the resources needed to look up home directories. */ HomeDir *_new_HomeDir(void); HomeDir *_del_HomeDir(HomeDir *home); /* * Return the home directory of a specified user, or NULL if unknown. */ const char *_hd_lookup_home_dir(HomeDir *home, const char *user); /* * Get the description of the that occured when _hd_lookup_home_dir() was * last called. */ const char *_hd_last_home_dir_error(HomeDir *home); /* * The _hd_scan_user_home_dirs() function calls a user-provided function * for each username known by the system, passing the function both * the name and the home directory of the user. * * The following macro can be used to declare both pointers and * prototypes for the callback functions. The 'data' argument is * a copy of the 'data' argument passed to _hd_scan_user_home_dirs() * and is intended for the user of _hd_scan_user_home_dirs() to use * to pass anonymous context data to the callback function. * The username and home directories are passed to the callback function * in the *usrnam and *homedir arguments respectively. * To abort the scan, and have _hd_scan_user_home_dirs() return 1, the * callback function can return 1. A description of up to maxerr * characters before the terminating '\0', can be written to errmsg[]. * This can then be examined by calling _hd_last_home_dir_error(). * To indicate success and continue the scan, the callback function * should return 0. _hd_scan_user_home_dirs() returns 0 on successful * completion of the scan, or 1 if an error occurred or a call to the * callback function returned 1. */ #define HOME_DIR_FN(fn) int (fn)(void *data, const char *usrnam, const char *homedir, char *errmsg, int maxerr) int _hd_scan_user_home_dirs(HomeDir *home, const char *prefix, void *data, HOME_DIR_FN(*callback_fn)); #endif ./libtecla/keytab.c0100644000076400007640000007364110027466660012576 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include "keytab.h" #include "strngmem.h" #include "getline.h" #include "errmsg.h" #include "hash.h" /* * When allocating or reallocating the key-binding table, how * many entries should be added? */ #define KT_TABLE_INC 100 /* * Define the size of the hash table that is used to associate action * names with action functions. This should be a prime number. */ #define KT_HASH_SIZE 113 /* * Define a binary-symbol-table object. */ struct KeyTab { ErrMsg *err; /* Information about the last error */ int size; /* The allocated dimension of table[] */ int nkey; /* The current number of members in the table */ KeySym *table; /* The table of lexically sorted key sequences */ HashTable *actions; /* The hash table of actions */ StringMem *smem; /* Memory for allocating strings */ }; static int _kt_extend_table(KeyTab *kt); static int _kt_parse_keybinding_string(const char *keyseq, char *binary, int *nc); static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2); static void _kt_assign_action(KeySym *sym, KtBinder binder, KtKeyFn *keyfn, void *data); static char _kt_backslash_escape(const char *string, const char **endp); static int _kt_is_emacs_meta(const char *string); static int _kt_is_emacs_ctrl(const char *string); static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq, int nc, int *first, int *last); /*....................................................................... * Create a new key-binding symbol table. * * Output: * return KeyTab * The new object, or NULL on error. */ KeyTab *_new_KeyTab(void) { KeyTab *kt; /* The object to be returned */ /* * Allocate the container. */ kt = (KeyTab *) malloc(sizeof(KeyTab)); if(!kt) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_KeyTab(). */ kt->err = NULL; kt->size = KT_TABLE_INC; kt->nkey = 0; kt->table = NULL; kt->actions = NULL; kt->smem = NULL; /* * Allocate a place to record error messages. */ kt->err = _new_ErrMsg(); if(!kt->err) return _del_KeyTab(kt); /* * Allocate the table. */ kt->table = (KeySym *) malloc(sizeof(kt->table[0]) * kt->size); if(!kt->table) { errno = ENOMEM; return _del_KeyTab(kt); }; /* * Allocate a hash table of actions. */ kt->actions = _new_HashTable(NULL, KT_HASH_SIZE, IGNORE_CASE, NULL, 0); if(!kt->actions) return _del_KeyTab(kt); /* * Allocate a string allocation object. This allows allocation of * small strings without fragmenting the heap. */ kt->smem = _new_StringMem(KT_TABLE_INC); if(!kt->smem) return _del_KeyTab(kt); return kt; } /*....................................................................... * Delete a KeyTab object. * * Input: * kt KeyTab * The object to be deleted. * Output: * return KeyTab * The deleted object (always NULL). */ KeyTab *_del_KeyTab(KeyTab *kt) { if(kt) { if(kt->table) free(kt->table); kt->actions = _del_HashTable(kt->actions); kt->smem = _del_StringMem(kt->smem, 1); kt->err = _del_ErrMsg(kt->err); free(kt); }; return NULL; } /*....................................................................... * Increase the size of the table to accomodate more keys. * * Input: * kt KeyTab * The table to be extended. * Output: * return int 0 - OK. * 1 - Error. */ static int _kt_extend_table(KeyTab *kt) { /* * Attempt to increase the size of the table. */ KeySym *newtab = (KeySym *) realloc(kt->table, sizeof(kt->table[0]) * (kt->size + KT_TABLE_INC)); /* * Failed? */ if(!newtab) { _err_record_msg(kt->err, "Can't extend keybinding table", END_ERR_MSG); errno = ENOMEM; return 1; }; /* * Install the resized table. */ kt->table = newtab; kt->size += KT_TABLE_INC; return 0; } /*....................................................................... * Add, update or remove a keybinding to the table. * * Input: * kt KeyTab * The table to add the binding to. * binder KtBinder The source of the binding. * keyseq const char * The key-sequence to bind. * action char * The action to associate with the key sequence, or * NULL to remove the action associated with the * key sequence. * Output: * return int 0 - OK. * 1 - Error. */ int _kt_set_keybinding(KeyTab *kt, KtBinder binder, const char *keyseq, const char *action) { KtKeyFn *keyfn; /* The action function */ void *data; /* The callback data of the action function */ /* * Check arguments. */ if(kt==NULL || !keyseq) { errno = EINVAL; if(kt) _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * Lookup the function that implements the specified action. */ if(!action) { keyfn = 0; data = NULL; } else { Symbol *sym = _find_HashSymbol(kt->actions, action); if(!sym) { _err_record_msg(kt->err, "Unknown key-binding action: ", action, END_ERR_MSG); errno = EINVAL; return 1; }; keyfn = (KtKeyFn *) sym->fn; data = sym->data; }; /* * Record the action in the table. */ return _kt_set_keyfn(kt, binder, keyseq, keyfn, data); } /*....................................................................... * Add, update or remove a keybinding to the table, specifying an action * function directly. * * Input: * kt KeyTab * The table to add the binding to. * binder KtBinder The source of the binding. * keyseq char * The key-sequence to bind. * keyfn KtKeyFn * The action function, or NULL to remove any existing * action function. * data void * A pointer to anonymous data to be passed to keyfn * whenever it is called. * Output: * return int 0 - OK. * 1 - Error. */ int _kt_set_keyfn(KeyTab *kt, KtBinder binder, const char *keyseq, KtKeyFn *keyfn, void *data) { const char *kptr; /* A pointer into keyseq[] */ char *binary; /* The binary version of keyseq[] */ int nc; /* The number of characters in binary[] */ int first,last; /* The first and last entries in the table which */ /* minimally match. */ int size; /* The size to allocate for the binary string */ int i; /* * Check arguments. */ if(kt==NULL || !keyseq) { errno = EINVAL; if(kt) _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * Work out a pessimistic estimate of how much space will be needed * for the binary copy of the string, noting that binary meta characters * embedded in the input string get split into two characters. */ for(size=0,kptr = keyseq; *kptr; kptr++) size += IS_META_CHAR(*kptr) ? 2 : 1; /* * Allocate a string that has the length of keyseq[]. */ binary = _new_StringMemString(kt->smem, size + 1); if(!binary) { errno = ENOMEM; _err_record_msg(kt->err, "Insufficient memory to record key sequence", END_ERR_MSG); return 1; }; /* * Convert control and octal character specifications to binary characters. */ if(_kt_parse_keybinding_string(keyseq, binary, &nc)) { binary = _del_StringMemString(kt->smem, binary); return 1; }; /* * Lookup the position in the table at which to insert the binding. */ switch(_kt_locate_keybinding(kt, binary, nc, &first, &last)) { /* * If an exact match for the key-sequence is already in the table, * simply replace its binding function (or delete the entry if * the new binding is 0). */ case KT_EXACT_MATCH: if(keyfn) { _kt_assign_action(kt->table + first, binder, keyfn, data); } else { _del_StringMemString(kt->smem, kt->table[first].keyseq); memmove(kt->table + first, kt->table + first + 1, (kt->nkey - first - 1) * sizeof(kt->table[0])); kt->nkey--; }; binary = _del_StringMemString(kt->smem, binary); break; /* * If an ambiguous match has been found and we are installing a * callback, then our new key-sequence would hide all of the ambiguous * matches, so we shouldn't allow it. */ case KT_AMBIG_MATCH: if(keyfn) { _err_record_msg(kt->err, "Can't bind \"", keyseq, "\", because it is a prefix of another binding", END_ERR_MSG); binary = _del_StringMemString(kt->smem, binary); errno = EPERM; return 1; }; break; /* * If the entry doesn't exist, create it. */ case KT_NO_MATCH: /* * Add a new binding? */ if(keyfn) { KeySym *sym; /* * We will need a new entry, extend the table if needed. */ if(kt->nkey + 1 > kt->size) { if(_kt_extend_table(kt)) { binary = _del_StringMemString(kt->smem, binary); return 1; }; }; /* * Make space to insert the new key-sequence before 'last'. */ if(last < kt->nkey) { memmove(kt->table + last + 1, kt->table + last, (kt->nkey - last) * sizeof(kt->table[0])); }; /* * Insert the new binding in the vacated position. */ sym = kt->table + last; sym->keyseq = binary; sym->nc = nc; for(i=0; iactions + i; action->fn = 0; action->data = NULL; }; sym->binder = -1; _kt_assign_action(sym, binder, keyfn, data); kt->nkey++; }; break; case KT_BAD_MATCH: binary = _del_StringMemString(kt->smem, binary); return 1; break; }; return 0; } /*....................................................................... * Perform a min-match lookup of a key-binding. * * Input: * kt KeyTab * The keybinding table to lookup in. * binary_keyseq char * The binary key-sequence to lookup. * nc int the number of characters in keyseq[]. * Input/Output: * first,last int * If there is an ambiguous or exact match, the indexes * of the first and last symbols that minimally match * will be assigned to *first and *last respectively. * If there is no match, then first and last will * bracket the location where the symbol should be * inserted. * Output: * return KtKeyMatch One of the following enumerators: * KT_EXACT_MATCH - An exact match was found. * KT_AMBIG_MATCH - An ambiguous match was found. * KT_NO_MATCH - No match was found. * KT_BAD_MATCH - An error occurred while searching. */ static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq, int nc, int *first, int *last) { int mid; /* The index at which to bisect the table */ int bot; /* The lowest index of the table not searched yet */ int top; /* The highest index of the table not searched yet */ int test; /* The return value of strcmp() */ /* * Perform a binary search for the key-sequence. */ bot = 0; top = kt->nkey - 1; while(top >= bot) { mid = (top + bot)/2; test = _kt_compare_strings(kt->table[mid].keyseq, kt->table[mid].nc, binary_keyseq, nc); if(test > 0) top = mid - 1; else if(test < 0) bot = mid + 1; else { *first = *last = mid; return KT_EXACT_MATCH; }; }; /* * An exact match wasn't found, but top is the index just below the * index where a match would be found, and bot is the index just above * where the match ought to be found. */ *first = top; *last = bot; /* * See if any ambiguous matches exist, and if so make *first and *last * refer to the first and last matches. */ if(*last < kt->nkey && kt->table[*last].nc > nc && _kt_compare_strings(kt->table[*last].keyseq, nc, binary_keyseq, nc)==0) { *first = *last; while(*last+1 < kt->nkey && kt->table[*last+1].nc > nc && _kt_compare_strings(kt->table[*last+1].keyseq, nc, binary_keyseq, nc)==0) (*last)++; return KT_AMBIG_MATCH; }; /* * No match. */ return KT_NO_MATCH; } /*....................................................................... * Lookup the sub-array of key-bindings who's key-sequences minimally * match a given key-sequence. * * Input: * kt KeyTab * The keybinding table to lookup in. * binary_keyseq char * The binary key-sequence to lookup. * nc int the number of characters in keyseq[]. * Input/Output: * matches KeySym ** The array of minimally matching symbols * can be found in (*matches)[0..nmatch-1], unless * no match was found, in which case *matches will * be set to NULL. * nmatch int The number of ambiguously matching symbols. This * will be 0 if there is no match, 1 for an exact * match, and a number greater than 1 for an ambiguous * match. * Output: * return KtKeyMatch One of the following enumerators: * KT_EXACT_MATCH - An exact match was found. * KT_AMBIG_MATCH - An ambiguous match was found. * KT_NO_MATCH - No match was found. * KT_BAD_MATCH - An error occurred while searching. */ KtKeyMatch _kt_lookup_keybinding(KeyTab *kt, const char *binary_keyseq, int nc, KeySym **matches, int *nmatch) { KtKeyMatch status; /* The return status */ int first,last; /* The indexes of the first and last matching entry */ /* in the symbol table. */ /* * Check the arguments. */ if(!kt || !binary_keyseq || !matches || !nmatch || nc < 0) { errno = EINVAL; if(kt) _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG); return KT_BAD_MATCH; }; /* * Lookup the indexes of the binding-table entries that bracket the * target key-sequence. */ status = _kt_locate_keybinding(kt, binary_keyseq, nc, &first, &last); /* * Translate the indexes into the corresponding subarray of matching * table entries. */ switch(status) { case KT_EXACT_MATCH: case KT_AMBIG_MATCH: *matches = kt->table + first; *nmatch = last - first + 1; break; default: *matches = NULL; *nmatch = 0; break; }; return status; } /*....................................................................... * Convert a keybinding string into a uniq binary representation. * * Control characters can be given directly in their binary form, * expressed as either ^ or C-, followed by the character, expressed in * octal, like \129 or via C-style backslash escapes, with the addition * of '\E' to denote the escape key. Similarly, meta characters can be * given directly in binary or expressed as M- followed by the character. * Meta characters are recorded as two characters in the binary output * string, the first being the escape key, and the second being the key * that was modified by the meta key. This means that binding to * \EA or ^[A or M-A are all equivalent. * * Input: * keyseq char * The key sequence being added. * Input/Output: * binary char * The binary version of the key sequence will be * assigned to binary[], which must have at least * as many characters as keyseq[] plus the number * of embedded binary meta characters. * nc int * The number of characters assigned to binary[] * will be recorded in *nc. * Output: * return int 0 - OK. * 1 - Error. */ static int _kt_parse_keybinding_string(const char *keyseq, char *binary, int *nc) { const char *iptr = keyseq; /* Pointer into keyseq[] */ char *optr = binary; /* Pointer into binary[] */ char c; /* An intermediate character */ /* * Parse the input characters until they are exhausted or the * output string becomes full. */ while(*iptr) { /* * Check for special characters. */ switch(*iptr) { case '^': /* A control character specification */ /* * Convert the caret expression into the corresponding control * character unless no character follows the caret, in which case * record a literal caret. */ if(iptr[1]) { /* * Get the next, possibly escaped, character. */ if(iptr[1] == '\\') { c = _kt_backslash_escape(iptr+2, &iptr); } else { c = iptr[1]; iptr += 2; }; /* * Convert the character to a control character. */ *optr++ = MAKE_CTRL(c); } else { *optr++ = *iptr++; }; break; /* * A backslash-escaped character? */ case '\\': /* * Convert the escape sequence to a binary character. */ *optr++ = _kt_backslash_escape(iptr+1, &iptr); break; /* * Possibly an emacs-style meta character? */ case 'M': if(_kt_is_emacs_meta(iptr)) { *optr++ = GL_ESC_CHAR; iptr += 2; } else { *optr++ = *iptr++; }; break; /* * Possibly an emacs-style control character specification? */ case 'C': if(_kt_is_emacs_ctrl(iptr)) { *optr++ = MAKE_CTRL(iptr[2]); iptr += 3; } else { *optr++ = *iptr++; }; break; default: /* * Convert embedded meta characters into an escape character followed * by the meta-unmodified character. */ if(IS_META_CHAR(*iptr)) { *optr++ = GL_ESC_CHAR; *optr++ = META_TO_CHAR(*iptr); iptr++; /* * To allow keysequences that start with printable characters to * be distinguished from the cursor-key keywords, prepend a backslash * to the former. This same operation is performed in gl_interpret_char() * before looking up a keysequence that starts with a printable character. */ } else if(iptr==keyseq && !IS_CTRL_CHAR(*iptr) && strcmp(keyseq, "up") != 0 && strcmp(keyseq, "down") != 0 && strcmp(keyseq, "left") != 0 && strcmp(keyseq, "right") != 0) { *optr++ = '\\'; *optr++ = *iptr++; } else { *optr++ = *iptr++; }; }; }; /* * How many characters were placed in the output array? */ *nc = optr - binary; return 0; } /*....................................................................... * Add, remove or modify an action. * * Input: * kt KeyTab * The key-binding table. * action char * The name of the action. * fn KtKeyFn * The function that implements the action, or NULL * to remove an existing action. * data void * A pointer to arbitrary callback data to pass to the * action function whenever it is called. * Output: * return int 0 - OK. * 1 - Error. */ int _kt_set_action(KeyTab *kt, const char *action, KtKeyFn *fn, void *data) { Symbol *sym; /* The symbol table entry of the action */ /* * Check the arguments. */ if(!kt || !action) { errno = EINVAL; if(kt) _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * If no function was provided, delete an existing action. */ if(!fn) { sym = _del_HashSymbol(kt->actions, action); return 0; }; /* * If the action already exists, replace its action function. */ sym = _find_HashSymbol(kt->actions, action); if(sym) { sym->fn = (void (*)(void))fn; sym->data = data; return 0; }; /* * Add a new action. */ if(!_new_HashSymbol(kt->actions, action, 0, (void (*)(void))fn, data, 0)) { _err_record_msg(kt->err, "Insufficient memory to record key-binding action", END_ERR_MSG); return 1; }; return 0; } /*....................................................................... * Compare two strings of specified length which may contain embedded * ascii NUL's. * * Input: * s1 char * The first of the strings to be compared. * n1 int The length of the string in s1. * s2 char * The second of the strings to be compared. * n2 int The length of the string in s2. * Output: * return int < 0 if(s1 < s2) * 0 if(s1 == s2) * > 0 if(s1 > s2) */ static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2) { int i; /* * Find the first character where the two strings differ. */ for(i=0; i= KTB_NBIND) return; /* * Record the action according to its source. */ action = sym->actions + binder; action->fn = keyfn; action->data = data; /* * Find the highest priority binding source that has supplied an * action. Note that the actions[] array is ordered in order of * descreasing priority, so the first entry that contains a function * is the one to use. */ for(i=0; iactions[i].fn; i++) ; /* * Record the index of this action for use during lookups. */ sym->binder = i < KTB_NBIND ? i : -1; return; } /*....................................................................... * Remove all key bindings that came from a specified source. * * Input: * kt KeyTab * The table of key bindings. * binder KtBinder The source of the bindings to be cleared. */ void _kt_clear_bindings(KeyTab *kt, KtBinder binder) { int oldkey; /* The index of a key in the original binding table */ int newkey; /* The index of a key in the updated binding table */ /* * If there is no table, then no bindings exist to be deleted. */ if(!kt) return; /* * Clear bindings of the given source. */ for(oldkey=0; oldkeynkey; oldkey++) _kt_assign_action(kt->table + oldkey, binder, 0, NULL); /* * Delete entries that now don't have a binding from any source. */ newkey = 0; for(oldkey=0; oldkeynkey; oldkey++) { KeySym *sym = kt->table + oldkey; if(sym->binder < 0) { _del_StringMemString(kt->smem, sym->keyseq); } else { if(oldkey != newkey) kt->table[newkey] = *sym; newkey++; }; }; /* * Record the number of keys that were kept. */ kt->nkey = newkey; return; } /*....................................................................... * Translate a backslash escape sequence to a binary character. * * Input: * string const char * The characters that follow the backslash. * Input/Output: * endp const char ** If endp!=NULL, on return *endp will be made to * point to the character in string[] which follows * the escape sequence. * Output: * return char The binary character. */ static char _kt_backslash_escape(const char *string, const char **endp) { char c; /* The output character */ /* * Is the backslash followed by one or more octal digits? */ switch(*string) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = strtol(string, (char **)&string, 8); break; case 'a': c = '\a'; string++; break; case 'b': c = '\b'; string++; break; case 'e': case 'E': /* Escape */ c = GL_ESC_CHAR; string++; break; case 'f': c = '\f'; string++; break; case 'n': c = '\n'; string++; break; case 'r': c = '\r'; string++; break; case 't': c = '\t'; string++; break; case 'v': c = '\v'; string++; break; case '\0': c = '\\'; break; default: c = *string++; break; }; /* * Report the character which follows the escape sequence. */ if(endp) *endp = string; return c; } /*....................................................................... * Return non-zero if the next two characters are M- and a third character * follows. Otherwise return 0. * * Input: * string const char * The sub-string to scan. * Output: * return int 1 - The next two characters are M- and these * are followed by at least one character. * 0 - The next two characters aren't M- or no * character follows a M- pair. */ static int _kt_is_emacs_meta(const char *string) { return *string++ == 'M' && *string++ == '-' && *string; } /*....................................................................... * Return non-zero if the next two characters are C- and a third character * follows. Otherwise return 0. * * Input: * string const char * The sub-string to scan. * Output: * return int 1 - The next two characters are C- and these * are followed by at least one character. * 0 - The next two characters aren't C- or no * character follows a C- pair. */ static int _kt_is_emacs_ctrl(const char *string) { return *string++ == 'C' && *string++ == '-' && *string; } /*....................................................................... * Merge an array of bindings with existing bindings. * * Input: * kt KeyTab * The table of key bindings. * binder KtBinder The source of the bindings. * bindings const KtKeyBinding * The array of bindings. * n int The number of bindings in bindings[]. * Output: * return int 0 - OK. * 1 - Error. */ int _kt_add_bindings(KeyTab *kt, KtBinder binder, const KtKeyBinding *bindings, unsigned n) { int i; /* * Check the arguments. */ if(!kt || !bindings) { errno = EINVAL; if(kt) _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * Install the array of bindings. */ for(i=0; ierr, "NULL argument(s)", END_ERR_MSG); return 1; }; /* * Lookup the symbol table entry of the action. */ sym = _find_HashSymbol(kt->actions, action); if(!sym) return 1; /* * Return the function and ccallback data associated with the action. */ if(fn) *fn = (KtKeyFn *) sym->fn; if(data) *data = sym->data; return 0; } /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in any of the public functions of this * module. * * Input: * kt KeyTab * The table of key bindings. * Output: * return const char * A pointer to the internal buffer in which * the error message is temporarily stored. */ const char *_kt_last_error(KeyTab *kt) { return kt ? _err_get_msg(kt->err) : "NULL KeyTab argument"; } ./libtecla/keytab.h0100644000076400007640000001264510027466660012600 0ustar mcsmcs#ifndef keytab_h #define keytab_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include "libtecla.h" /*-----------------------------------------------------------------------* * This module defines a binary-search symbol table of key-bindings. * *-----------------------------------------------------------------------*/ /* * All key-binding functions are defined as follows. * * Input: * gl GetLine * The resource object of this library. * count int A positive repeat count specified by the user, * or 1. Action functions should ignore this if * repeating the action multiple times isn't * appropriate. * data void * A pointer to action-specific data, * cast to (void *). * Output: * return int 0 - OK. * 1 - Error. */ #define KT_KEY_FN(fn) int (fn)(GetLine *gl, int count, void *data) typedef KT_KEY_FN(KtKeyFn); /* * Allow the association of arbitrary callback data with each action * function. */ typedef struct { KtKeyFn *fn; /* The acion function */ void *data; /* A pointer to arbitrary data to be passed to */ /* fn() whenever it is called. */ } KtAction; /* * Enumerate the possible sources of key-bindings in order of decreasing * priority. */ typedef enum { KTB_USER, /* This is a binding being set by the user */ KTB_NORM, /* This is the default binding set by the library */ KTB_TERM, /* This is a binding taken from the terminal settings */ /* The following entry must always be last */ KTB_NBIND /* The number of binding sources listed above */ } KtBinder; /* * Define an entry of a key-binding binary symbol table. */ typedef struct { char *keyseq; /* The key sequence that triggers the macro */ int nc; /* The number of characters in keyseq[] */ KtAction actions[KTB_NBIND]; /* Bindings from different sources */ int binder; /* The index of the highest priority element */ /* of actions[] that has been assigned an */ /* action function, or -1 if none have. */ } KeySym; /* * Provide an opaque type alias to the symbol table container. */ typedef struct KeyTab KeyTab; /* * Create a new symbol table. */ KeyTab *_new_KeyTab(void); /* * Delete the symbol table. */ KeyTab *_del_KeyTab(KeyTab *kt); int _kt_set_keybinding(KeyTab *kt, KtBinder binder, const char *keyseq, const char *action); int _kt_set_keyfn(KeyTab *kt, KtBinder binder, const char *keyseq, KtKeyFn *fn, void *data); int _kt_set_action(KeyTab *kt, const char *action, KtKeyFn *fn, void *data); /* * Lookup the function that implements a given action. */ int _kt_lookup_action(KeyTab *kt, const char *action, KtKeyFn **fn, void **data); typedef enum { KT_EXACT_MATCH, /* An exact match was found */ KT_AMBIG_MATCH, /* An ambiguous match was found */ KT_NO_MATCH, /* No match was found */ KT_BAD_MATCH /* An error occurred while searching */ } KtKeyMatch; KtKeyMatch _kt_lookup_keybinding(KeyTab *kt, const char *binary_keyseq, int nc, KeySym **matches, int *nmatch); /* * Remove all key bindings that came from a specified source. */ void _kt_clear_bindings(KeyTab *kt, KtBinder binder); /* * When installing an array of keybings each binding is defined by * an element of the following type: */ typedef struct { const char *keyseq; /* The sequence of keys that trigger this binding */ const char *action; /* The name of the action function that is triggered */ } KtKeyBinding; /* * Merge an array of bindings with existing bindings. */ int _kt_add_bindings(KeyTab *kt, KtBinder binder, const KtKeyBinding *bindings, unsigned n); /* * Get information about the last error in this module. */ const char *_kt_last_error(KeyTab *kt); #endif ./libtecla/libtecla.h0100644000076400007640000024360410050336672013074 0ustar mcsmcs#ifndef libtecla_h #define libtecla_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #ifdef __cplusplus extern "C" { #endif #include /* FILE * */ #include /* size_t */ #include /* time_t */ #include /* struct sigaction */ /* * The following are the three components of the libtecla version number. * Note that it is better to use the libtecla_version() function than these * macros since the macros only tell you which version of the library your * code was compiled against, whereas the libtecla_version() function * tells you which version of the shared tecla library your program is * actually linked to. */ #define TECLA_MAJOR_VER 1 #define TECLA_MINOR_VER 6 #define TECLA_MICRO_VER 1 /*....................................................................... * Query the version number of the tecla library. * * Input: * major int * The major version number of the library * will be assigned to *major. This number is * only incremented when a change to the library is * made that breaks binary (shared library) and/or * compilation backwards compatibility. * minor int * The minor version number of the library * will be assigned to *minor. This number is * incremented whenever new functions are added to * the public API. * micro int * The micro version number of the library will be * assigned to *micro. This number is incremented * whenever internal changes are made that don't * change the public API, such as bug fixes and * performance enhancements. */ void libtecla_version(int *major, int *minor, int *micro); /*----------------------------------------------------------------------- * The getline module provides interactive command-line input, recall * and editing by users at terminals. See the gl_getline(3) man page for * more details. *-----------------------------------------------------------------------*/ /* * Provide an opaque handle for the resource object that is defined in * getline.h. */ typedef struct GetLine GetLine; /* * The following two functions are used to create and delete the * resource objects that are used by the gl_getline() function. */ GetLine *new_GetLine(size_t linelen, size_t histlen); GetLine *del_GetLine(GetLine *gl); /* * Read a line into an internal buffer of gl. */ char *gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); /*....................................................................... * Prompt the user for a single-character reply. * * Input: * gl GetLine * A resource object returned by new_GetLine(). * prompt char * The prompt to prefix the query with, or NULL * to reuse the previous prompt. * defchar char The character to substitute if the * user simply hits return, or '\n' if you don't * need to substitute anything. * Output: * return int The character that was read, or EOF if the read * had to be aborted (in which case you can call * gl_return_status() to find out why). */ int gl_query_char(GetLine *gl, const char *prompt, char defchar); /*....................................................................... * Read a single uninterpretted character from the user, without * displaying anything. * * Input: * gl GetLine * A resource object previously returned by * new_GetLine(). * Output: * return int The character that was read, or EOF if the read * had to be aborted (in which case you can call * gl_return_status() to find out why). */ int gl_read_char(GetLine *gl); /* * Configure the application specific and/or user-specific behavior of * gl_get_line(). */ int gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file); /* * The following enumerators specify the origin of a key binding, and * are listed in order of decreasing priority, such that user-specified * key-bindings take precedence over application default bindings. */ typedef enum { GL_USER_KEY, /* A key-binding specified by the user */ GL_APP_KEY /* A key-binding specified by the application */ } GlKeyOrigin; /* * Bind a key sequence to a given action. If action==NULL, unbind the * key-sequence. */ int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin, const char *keyseq, const char *action); /*----------------------------------------------------------------------- * The file-expansion module provides facilities for expanding ~user/ and * $envvar expressions, and for expanding glob-style wildcards. * See the ef_expand_file(3) man page for more details. *-----------------------------------------------------------------------*/ /* * ExpandFile objects contain the resources needed to expand pathnames. */ typedef struct ExpandFile ExpandFile; /* * The following functions are used to create and delete the resource * objects that are used by the ef_expand_file() function. */ ExpandFile *new_ExpandFile(void); ExpandFile *del_ExpandFile(ExpandFile *ef); /* * A container of the following type is returned by ef_expand_file(). */ typedef struct { int exists; /* True if the files in files[] currently exist. */ /* This only time that this may not be true is if */ /* the input filename didn't contain any wildcards */ /* and thus wasn't matched against existing files. */ /* In this case the single entry in 'nfile' may not */ /* refer to an existing file. */ int nfile; /* The number of files in files[] */ char **files; /* An array of 'nfile' filenames. */ } FileExpansion; /* * The ef_expand_file() function expands a specified pathname, converting * ~user/ and ~/ patterns at the start of the pathname to the * corresponding home directories, replacing $envvar with the value of * the corresponding environment variable, and then, if there are any * wildcards, matching these against existing filenames. * * If no errors occur, a container is returned containing the array of * files that resulted from the expansion. If there were no wildcards * in the input pathname, this will contain just the original pathname * after expansion of ~ and $ expressions. If there were any wildcards, * then the array will contain the files that matched them. Note that * if there were any wildcards but no existing files match them, this * is counted as an error and NULL is returned. * * The supported wildcards and their meanings are: * * - Match any sequence of zero or more characters. * ? - Match any single character. * [chars] - Match any single character that appears in 'chars'. * If 'chars' contains an expression of the form a-b, * then any character between a and b, including a and b, * matches. The '-' character looses its special meaning * as a range specifier when it appears at the start * of the sequence of characters. * [^chars] - The same as [chars] except that it matches any single * character that doesn't appear in 'chars'. * * Wildcard expressions are applied to individual filename components. * They don't match across directory separators. A '.' character at * the beginning of a filename component must also be matched * explicitly by a '.' character in the input pathname, since these * are UNIX's hidden files. * * Input: * fe ExpandFile * The pathname expansion resource object. * path const char * The path name to be expanded. * pathlen int The length of the suffix of path[] that * constitutes the filename to be expanded, * or -1 to specify that the whole of the * path string should be used. * Output: * return FileExpansion * A pointer to a results container within the * given ExpandFile object. This contains an * array of the pathnames that resulted from * expanding ~ and $ expressions and from * matching any wildcards, sorted into lexical * order. * * This container and its contents will be * recycled on subsequent calls, so if you need * to keep the results of two successive runs, * you will either have to allocate a private * copy of the array, or use two ExpandFile * objects. * * On error, NULL is returned. A description * of the error can be acquired by calling the * ef_last_error() function. */ FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen); /*....................................................................... * Print out an array of matching files. * * Input: * result FileExpansion * The container of the sorted array of * expansions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width); /* * The ef_last_error() function returns a description of the last error * that occurred in a call ef_expand_file(). Note that this message is * contained in an array which is allocated as part of *ef, and its * contents thus potentially change on every call to ef_expand_file(). */ const char *ef_last_error(ExpandFile *ef); /*----------------------------------------------------------------------- * The WordCompletion module is used for completing incomplete words, such * as filenames. Programs can use functions within this module to register * their own customized completion functions. *-----------------------------------------------------------------------*/ /* * Ambiguous completion matches are recorded in objects of the * following type. */ typedef struct WordCompletion WordCompletion; /* * Create a new completion object. */ WordCompletion *new_WordCompletion(void); /* * Delete a redundant completion object. */ WordCompletion *del_WordCompletion(WordCompletion *cpl); /*....................................................................... * Callback functions declared and prototyped using the following macro * are called upon to return an array of possible completion suffixes * for the token that precedes a specified location in the given * input line. It is up to this function to figure out where the token * starts, and to call cpl_add_completion() to register each possible * completion before returning. * * Input: * cpl WordCompletion * An opaque pointer to the object that will * contain the matches. This should be filled * via zero or more calls to cpl_add_completion(). * data void * The anonymous 'data' argument that was * passed to cpl_complete_word() or * gl_customize_completion()). * line const char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * Output * return int 0 - OK. * 1 - Error. */ #define CPL_MATCH_FN(fn) int (fn)(WordCompletion *cpl, void *data, \ const char *line, int word_end) typedef CPL_MATCH_FN(CplMatchFn); /*....................................................................... * Optional callback functions declared and prototyped using the * following macro are called upon to return non-zero if a given * file, specified by its pathname, is to be included in a list of * completions. * * Input: * data void * The application specified pointer which * was specified when this callback function * was registered. This can be used to have * anything you like passed to your callback. * pathname const char * The pathname of the file to be checked to * see if it should be included in the list * of completions. * Output * return int 0 - Ignore this file. * 1 - Do include this file in the list * of completions. */ #define CPL_CHECK_FN(fn) int (fn)(void *data, const char *pathname) typedef CPL_CHECK_FN(CplCheckFn); /* * You can use the following CplCheckFn callback function to only * have executables included in a list of completions. */ CPL_CHECK_FN(cpl_check_exe); /* * cpl_file_completions() is the builtin filename completion callback * function. This can also be called by your own custom CPL_MATCH_FN() * callback functions. To do this pass on all of the arguments of your * custom callback function to cpl_file_completions(), with the exception * of the (void *data) argument. The data argument should either be passed * NULL to request the default behaviour of the file-completion function, * or be passed a pointer to a CplFileConf structure (see below). In the * latter case the contents of the structure modify the behavior of the * file-completer. */ CPL_MATCH_FN(cpl_file_completions); /* * Objects of the following type can be used to change the default * behavior of the cpl_file_completions() callback function. */ typedef struct CplFileConf CplFileConf; /* * If you want to change the behavior of the cpl_file_completions() * callback function, call the following function to allocate a * configuration object, then call one or more of the subsequent * functions to change any of the default configuration parameters * that you don't want. This function returns NULL when there is * insufficient memory. */ CplFileConf *new_CplFileConf(void); /* * If backslashes in the prefix being passed to cpl_file_completions() * should be treated as literal characters, call the following function * with literal=1. Otherwise the default is to treat them as escape * characters which remove the special meanings of spaces etc.. */ void cfc_literal_escapes(CplFileConf *cfc, int literal); /* * Before calling cpl_file_completions(), call this function if you * know the index at which the filename prefix starts in the input line. * Otherwise by default, or if you specify start_index to be -1, the * filename is taken to start after the first unescaped space preceding * the cursor, or the start of the line, which ever comes first. */ void cfc_file_start(CplFileConf *cfc, int start_index); /* * If you only want certain types of files to be included in the * list of completions, use the following function to specify a * callback function which will be called to ask whether a given file * should be included. The chk_data argument is will be passed to the * callback function whenever it is called and can be anything you want. */ void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data); /* * The following function deletes a CplFileConf objects previously * returned by new_CplFileConf(). It always returns NULL. */ CplFileConf *del_CplFileConf(CplFileConf *cfc); /* * The following configuration structure is deprecated. Do not change * its contents, since this will break any programs that still use it, * and don't use it in new programs. Instead use opaque CplFileConf * objects as described above. cpl_file_completions() figures out * what type of structure you pass it, by virtue of a magic int code * placed at the start of CplFileConf object by new_CplFileConf(). */ typedef struct { int escaped; /* Opposite to the argument of cfc_literal_escapes() */ int file_start; /* Equivalent to the argument of cfc_file_start() */ } CplFileArgs; /* * This initializes the deprecated CplFileArgs structures. */ void cpl_init_FileArgs(CplFileArgs *cfa); /*....................................................................... * When an error occurs while performing a completion, custom completion * callback functions should register a terse description of the error * by calling cpl_record_error(). This message will then be returned on * the next call to cpl_last_error() and used by getline to display an * error message to the user. * * Input: * cpl WordCompletion * The string-completion resource object that was * originally passed to the callback. * errmsg const char * The description of the error. */ void cpl_record_error(WordCompletion *cpl, const char *errmsg); /*....................................................................... * This function can be used to replace the builtin filename-completion * function with one of the user's choice. The user's completion function * has the option of calling the builtin filename-completion function * if it believes that the token that it has been presented with is a * filename (see cpl_file_completions() above). * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * This is passed to match_fn() whenever it is * called. It could, for example, point to a * symbol table that match_fn() would look up * matches in. * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * report matching symbols. * Output: * return int 0 - OK. * 1 - Error. */ int gl_customize_completion(GetLine *gl, void *data, CplMatchFn *match_fn); /*....................................................................... * This function allows you to install alternate completion action * functions or completion listing functions, or to change the * completion function of an existing action of the same type. This * should preferably be called before the first call to gl_get_line() * so that the name of the action becomes defined before the user's * configuration file is read. * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * This is passed to match_fn() whenever it is * called. It could, for example, point to a * symbol table that match_fn() would look up * matches in. * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * report matching symbols. * list_only int If non-zero, install an action that only lists * possible completions, rather than attempting * to perform the completion. * name const char * The name with which users can refer to the * binding in tecla configuration files. * keyseq const char * The key sequence with which to invoke * the binding. This should be specified in the * same manner as key-sequences in tecla * configuration files (eg. "M-^I"). * Output: * return int 0 - OK. * 1 - Error. */ int gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq); /*....................................................................... * Change the terminal (or stream) that getline interacts with. * * Input: * gl GetLine * The resource object of the command-line input * module. * input_fp FILE * The stdio stream to read from. * output_fp FILE * The stdio stream to write to. * term const char * The terminal type. This can be NULL if * either or both of input_fp and output_fp don't * refer to a terminal. Otherwise it should refer * to an entry in the terminal information database. * Output: * return int 0 - OK. * 1 - Error. */ int gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term); /*....................................................................... * The following functions can be used to save and restore the contents * of the history buffer. * * Input: * gl GetLine * The resource object of the command-line input * module. * filename const char * The name of the new file to write to. * comment const char * Extra information such as timestamps will * be recorded on a line started with this * string, the idea being that the file can * double as a command file. Specify "" if * you don't care. Be sure to specify the * same string to both functions. * max_lines int The maximum number of lines to save, or -1 * to save all of the lines in the history * list. * Output: * return int 0 - OK. * 1 - Error. */ int gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines); int gl_load_history(GetLine *gl, const char *filename, const char *comment); /* * Enumerate file-descriptor events that can be waited for. */ typedef enum { GLFD_READ, /* Watch for data waiting to be read from a file descriptor */ GLFD_WRITE, /* Watch for ability to write to a file descriptor */ GLFD_URGENT /* Watch for urgent out-of-band data on the file descriptor */ } GlFdEvent; /* * The following enumeration is used for the return status of file * descriptor event callbacks. */ typedef enum { GLFD_ABORT, /* Cause gl_get_line() to abort with an error */ GLFD_REFRESH, /* Redraw the input line and continue waiting for input */ GLFD_CONTINUE /* Continue to wait for input, without redrawing the line */ } GlFdStatus; /*....................................................................... * On systems that have the select() system call, while gl_get_line() * is waiting for terminal input, it can also be asked to listen for * activity on arbitrary file descriptors. Callback functions of the * following type can be registered to be called when activity is * seen. If your callback needs to write to the terminal or use * signals, please see the gl_get_line(3) man page. * * Input: * gl GetLine * The gl_get_line() resource object. You can use * this safely to call gl_watch_fd() or * gl_inactivity_timeout(). The effect of calling other * functions that take a gl argument is undefined, * and must be avoided. * data void * A pointer to arbitrary callback data, as originally * registered with gl_watch_fd(). * fd int The file descriptor that has activity. * event GlFdEvent The activity seen on the file descriptor. The * inclusion of this argument allows the same * callback to be registered for multiple events. * Output: * return GlFdStatus GLFD_ABORT - Cause gl_get_line() to abort with * an error (set errno if you need it). * GLFD_REFRESH - Redraw the input line and continue * waiting for input. Use this if you * wrote something to the terminal. * GLFD_CONTINUE - Continue to wait for input, without * redrawing the line. */ #define GL_FD_EVENT_FN(fn) GlFdStatus (fn)(GetLine *gl, void *data, int fd, \ GlFdEvent event) typedef GL_FD_EVENT_FN(GlFdEventFn); /*....................................................................... * Where possible, register a function and associated data to be called * whenever a specified event is seen on a file descriptor. * * Input: * gl GetLine * The resource object of the command-line input * module. * fd int The file descriptor to watch. * event GlFdEvent The type of activity to watch for. * callback GlFdEventFn * The function to call when the specified * event occurs. Setting this to 0 removes * any existing callback. * data void * A pointer to arbitrary data to pass to the * callback function. * Output: * return int 0 - OK. * 1 - Either gl==NULL, or this facility isn't * available on the the host system * (ie. select() isn't available). No * error message is generated in the latter * case. */ int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data); /* * Enumerators from the following list are returned by activity * timeout callbacks registered by gl_inactivity_timeout(). They tell * gl_get_line() whether and how to procede. */ typedef enum { GLTO_ABORT, /* Cause gl_get_line() to abort with an error */ GLTO_REFRESH, /* Redraw the input line and continue waiting for input */ GLTO_CONTINUE /* Continue to wait for input, without redrawing the line */ } GlAfterTimeout; /*....................................................................... * On systems that have the select() system call, the application has * the option of providing a callback function of the following type, * which is called whenever no terminal input or other I/O activity is * seen for the timeout duration specified in the last call to * gl_inactivity_timeout(). * * Input: * gl GetLine * The gl_get_line() resource object. You can use * this safely to call gl_watch_fd() or * gl_inactivity_timeout(). The effect of calling other * functions that take a gl argument is undefined, * and must be avoided. * data void * A pointer to arbitrary callback data, as * originally registered with gl_inactivity_timeout(). * Output: * return GlAfterTimeout GLTO_ABORT - Cause gl_get_line() to * abort with an error (set * errno if you need it). * GLTO_REFRESH - Redraw the input line and * continue waiting for * input. Use this if you * wrote something to the * terminal. * GLTO_CONTINUE - Continue to wait for * input, without redrawing * the line. */ #define GL_TIMEOUT_FN(fn) GlAfterTimeout (fn)(GetLine *gl, void *data) typedef GL_TIMEOUT_FN(GlTimeoutFn); /*....................................................................... * On systems with the select() system call, the gl_inactivity_timeout() * function provides the option of setting (or cancelling) an * inactivity timeout. Inactivity, in this case, refers both to * terminal input received from the user, and to I/O on any file * descriptors registered by calls to gl_watch_fd(). If at any time, * no activity is seen for the requested time period, the specified * timeout callback function is called. On returning, this callback * returns a code which tells gl_get_line() what to do next. Note that * each call to gl_inactivity_timeout() replaces any previously installed * timeout callback, and that specifying a callback of 0, turns off * inactivity timing. * * Beware that although the timeout argument includes a nano-second * component, few computer clocks presently have resolutions finer * than a few milliseconds, so asking for less than a few milliseconds * is equivalent to zero on a lot of systems. * * Input: * gl GetLine * The resource object of the command-line input * module. * callback GlTimeoutFn * The function to call when the inactivity * timeout is exceeded. To turn off * inactivity timeouts altogether, send 0. * data void * A pointer to arbitrary data to pass to the * callback function. * sec unsigned long The number of whole seconds in the timeout. * nsec unsigned long The fractional number of seconds in the * timeout, expressed in nano-seconds (see * the caveat above). * Output: * return int 0 - OK. * 1 - Either gl==NULL, or this facility isn't * available on the the host system * (ie. select() isn't available). No * error message is generated in the latter * case. */ int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *timeout_fn, void *data, unsigned long sec, unsigned long nsec); /*....................................................................... * Switch history streams. History streams represent separate history * lists recorded within a single history buffer. Different streams * are distinguished by integer identifiers chosen by the calling * appplicaton. Initially new_GetLine() sets the stream identifier to * 0. Whenever a new line is appended to the history list, the current * stream identifier is recorded with it, and history lookups only * consider lines marked with the current stream identifier. * * Input: * gl GetLine * The resource object of gl_get_line(). * id unsigned The new history stream identifier. * Output: * return int 0 - OK. * 1 - Error. */ int gl_group_history(GetLine *gl, unsigned id); /*....................................................................... * Display the contents of the history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * fp FILE * The stdio output stream to write to. * fmt const char * A format string. This containing characters to be * written verbatim, plus any of the following * format directives: * %D - The date, formatted like 2001-11-20 * %T - The time of day, formatted like 23:59:59 * %N - The sequential entry number of the * line in the history buffer. * %G - The number of the history group that * the line belongs to. * %% - A literal % character. * %H - The history line itself. * Note that a '\n' newline character is not * appended by default. * all_groups int If true, display history lines from all * history groups. Otherwise only display * those of the current history group. * max_lines int If max_lines is < 0, all available lines * are displayed. Otherwise only the most * recent max_lines lines will be displayed. * Output: * return int 0 - OK. * 1 - Error. */ int gl_show_history(GetLine *gl, FILE *fp, const char *fmt, int all_groups, int max_lines); /*....................................................................... * Resize or delete the history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * bufsize size_t The number of bytes in the history buffer, or 0 * to delete the buffer completely. * Output: * return int 0 - OK. * 1 - Insufficient memory (the previous buffer * will have been retained). No error message * will be displayed. */ int gl_resize_history(GetLine *gl, size_t bufsize); /*....................................................................... * Set an upper limit to the number of lines that can be recorded in the * history list, or remove a previously specified limit. * * Input: * gl GetLine * The resource object of gl_get_line(). * max_lines int The maximum number of lines to allow, or -1 to * cancel a previous limit and allow as many lines * as will fit in the current history buffer size. */ void gl_limit_history(GetLine *gl, int max_lines); /*....................................................................... * Discard either all historical lines, or just those associated with the * current history group. * * Input: * gl GetLine * The resource object of gl_get_line(). * all_groups int If true, clear all of the history. If false, * clear only the stored lines associated with the * currently selected history group. */ void gl_clear_history(GetLine *gl, int all_groups); /*....................................................................... * Temporarily enable or disable the gl_get_line() history mechanism. * * Input: * gl GetLine * The resource object of gl_get_line(). * enable int If true, turn on the history mechanism. If * false, disable it. */ void gl_toggle_history(GetLine *gl, int enable); /* * Objects of the following type are returned by gl_terminal_size(). */ typedef struct { int nline; /* The terminal has nline lines */ int ncolumn; /* The terminal has ncolumn columns */ } GlTerminalSize; /*....................................................................... * Update if necessary, and return the current size of the terminal. * * Input: * gl GetLine * The resource object of gl_get_line(). * def_ncolumn int If the number of columns in the terminal * can't be determined, substitute this number. * def_nline int If the number of lines in the terminal can't * be determined, substitute this number. * Output: * return GlTerminalSize The current terminal size. */ GlTerminalSize gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline); /*....................................................................... * Tell gl_get_line() the current terminal size. Note that this is only * necessary on systems where changes in terminal size aren't reported * via SIGWINCH. * * Input: * gl GetLine * The resource object of gl_get_line(). * ncolumn int The number of columns in the terminal. * nline int The number of rows in the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int gl_set_term_size(GetLine *gl, int ncolumn, int nline); /* * The gl_lookup_history() function returns information in an * argument of the following type. */ typedef struct { const char *line; /* The requested history line */ unsigned group; /* The history group to which the */ /* line belongs. */ time_t timestamp; /* The date and time at which the */ /* line was originally entered. */ } GlHistoryLine; /*....................................................................... * Lookup a history line by its sequential number of entry in the * history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * id unsigned long The identification number of the line to * be returned, where 0 denotes the first line * that was entered in the history list, and * each subsequently added line has a number * one greater than the previous one. For * the range of lines currently in the list, * see the gl_range_of_history() function. * Input/Output: * line GlHistoryLine * A pointer to the variable in which to * return the details of the line. * Output: * return int 0 - The line is no longer in the history * list, and *line has not been changed. * 1 - The requested line can be found in * *line. Note that the string in * line->line is part of the history * buffer and will change, so a private * copy should be made if you wish to * use it after subsequent calls to any * functions that take gl as an argument. */ int gl_lookup_history(GetLine *gl, unsigned long id, GlHistoryLine *line); /* * The gl_state_of_history() function returns information in an argument * of the following type. */ typedef struct { int enabled; /* True if history is enabled */ unsigned group; /* The current history group */ int max_lines; /* The current upper limit on the number of lines */ /* in the history list, or -1 if unlimited. */ } GlHistoryState; /*....................................................................... * Query the state of the history list. Note that any of the input/output * pointers can be specified as NULL. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * state GlHistoryState * A pointer to the variable in which to record * the return values. */ void gl_state_of_history(GetLine *gl, GlHistoryState *state); /* * The gl_range_of_history() function returns information in an argument * of the following type. */ typedef struct { unsigned long oldest; /* The sequential entry number of the oldest */ /* line in the history list. */ unsigned long newest; /* The sequential entry number of the newest */ /* line in the history list. */ int nlines; /* The number of lines in the history list */ } GlHistoryRange; /*....................................................................... * Query the number and range of lines in the history buffer. * * Input: * gl GetLine * The resource object of gl_get_line(). * range GlHistoryRange * A pointer to the variable in which to record * the return values. If range->nline=0, the * range of lines will be given as 0-0. */ void gl_range_of_history(GetLine *gl, GlHistoryRange *range); /* * The gl_size_of_history() function returns information in an argument * of the following type. */ typedef struct { size_t size; /* The size of the history buffer (bytes) */ size_t used; /* The number of bytes of the history buffer */ /* that are currently occupied. */ } GlHistorySize; /*....................................................................... * Return the size of the history buffer and the amount of the * buffer that is currently in use. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * GlHistorySize size * A pointer to the variable in which to return * the results. */ void gl_size_of_history(GetLine *gl, GlHistorySize *size); /*....................................................................... * Enable or disable the automatic addition of newly entered lines to the * history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * enable int If true, subsequently entered lines will * automatically be added to the history list * before they are returned to the caller of * gl_get_line(). If 0, the choice of how and * when to archive lines in the history list, * is left up to the calling application, which * can do so via calls to gl_append_history(). * Output: * return int 0 - OK. * 1 - Error. */ int gl_automatic_history(GetLine *gl, int enable); /*....................................................................... * Append a specified line to the history list. * * Input: * gl GetLine * The resource object of gl_get_line(). * line const char * The line to be added. * Output: * return int 0 - OK. * 1 - Error. */ int gl_append_history(GetLine *gl, const char *line); /*....................................................................... * Specify whether text that users type should be displayed or hidden. * In the latter case, only the prompt is displayed, and the final * input line is not archived in the history list. * * Input: * gl GetLine * The input-line history maintenance object. * enable int 0 - Disable echoing. * 1 - Enable echoing. * -1 - Just query the mode without changing it. * Output: * return int The echoing disposition that was in effect * before this function was called: * 0 - Echoing was disabled. * 1 - Echoing was enabled. */ int gl_echo_mode(GetLine *gl, int enable); /*....................................................................... * This function can be called from gl_get_line() callbacks to have * the prompt changed when they return. It has no effect if gl_get_line() * is not currently being invoked. * * Input: * gl GetLine * The resource object of gl_get_line(). * prompt const char * The new prompt. */ void gl_replace_prompt(GetLine *gl, const char *prompt); /* * Enumerate the available prompt formatting styles. */ typedef enum { GL_LITERAL_PROMPT, /* Display the prompt string literally */ GL_FORMAT_PROMPT /* The prompt string can contain any of the */ /* following formatting directives: */ /* %B - Display subsequent characters */ /* with a bold font. */ /* %b - Stop displaying characters */ /* with the bold font. */ /* %U - Underline subsequent characters. */ /* %u - Stop underlining characters. */ /* %S - Highlight subsequent characters */ /* (also known as standout mode). */ /* %s - Stop highlighting characters */ /* %% - Display a single % character. */ } GlPromptStyle; /*....................................................................... * Specify whether to heed text attribute directives within prompt * strings. * * Input: * gl GetLine * The resource object of gl_get_line(). * style GlPromptStyle The style of prompt (see the definition of * GlPromptStyle in libtecla.h for details). */ void gl_prompt_style(GetLine *gl, GlPromptStyle style); /*....................................................................... * Remove a signal from the list of signals that gl_get_line() traps. * * Input: * gl GetLine * The resource object of gl_get_line(). * signo int The number of the signal to be ignored. * Output: * return int 0 - OK. * 1 - Error. */ int gl_ignore_signal(GetLine *gl, int signo); /* * A bitwise union of the following enumerators is passed to * gl_trap_signal() to specify the environment in which the * application's signal handler is to be called. */ typedef enum { GLS_RESTORE_SIG=1, /* Restore the caller's signal environment */ /* while handling the signal. */ GLS_RESTORE_TTY=2, /* Restore the caller's terminal settings */ /* while handling the signal. */ GLS_RESTORE_LINE=4, /* Move the cursor to the start of the next line */ GLS_REDRAW_LINE=8, /* Redraw the input line when the signal handler */ /* returns. */ GLS_UNBLOCK_SIG=16, /* Normally a signal who's delivery is found to */ /* be blocked by the calling application is not */ /* trapped by gl_get_line(). Including this flag */ /* causes it to be temporarily unblocked and */ /* trapped while gl_get_line() is executing. */ GLS_DONT_FORWARD=32,/* Don't forward the signal to the signal handler */ /* of the calling program. */ GLS_RESTORE_ENV = GLS_RESTORE_SIG | GLS_RESTORE_TTY | GLS_REDRAW_LINE, GLS_SUSPEND_INPUT = GLS_RESTORE_ENV | GLS_RESTORE_LINE } GlSignalFlags; /* * The following enumerators are passed to gl_trap_signal() to tell * it what to do after the application's signal handler has been called. */ typedef enum { GLS_RETURN, /* Return the line as though the user had pressed the */ /* return key. */ GLS_ABORT, /* Cause gl_get_line() to return NULL */ GLS_CONTINUE /* After handling the signal, resume command line editing */ } GlAfterSignal; /*....................................................................... * Tell gl_get_line() how to respond to a given signal. This can be used * both to override the default responses to signals that gl_get_line() * normally catches and to add new signals to the list that are to be * caught. * * Input: * gl GetLine * The resource object of gl_get_line(). * signo int The number of the signal to be caught. * flags unsigned A bitwise union of GlSignalFlags enumerators. * after GlAfterSignal What to do after the application's signal * handler has been called. * errno_value int The value to set errno to. * Output: * return int 0 - OK. * 1 - Insufficient memory to record the * new signal disposition. */ int gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value); /*....................................................................... * By default, gl_get_line() doesn't trap signals that are blocked * when it is called. This default can be changed either on a * per-signal basis by calling gl_trap_signal(), or on a global basis * by calling this function. What this function does is add the * GLS_UNBLOCK_SIG flag to all signals that are currently configured * to be trapped by gl_get_line(), such that when subsequent calls to * gl_get_line() wait for I/O, these signals are temporarily * unblocked. This behavior is useful in non-blocking server-I/O mode, * where it is used to avoid race conditions related to handling these * signals externally to gl_get_line(). See the demonstration code in * demo3.c, or the gl_handle_signal() man page for further * information. * * Input: * gl GetLine * The resource object of gl_get_line(). */ void gl_catch_blocked(GetLine *gl); /*....................................................................... * In server-I/O mode the terminal is left in raw mode between calls * to gl_get_line(), so it is necessary for the application to install * terminal restoring signal handlers for signals that could terminate * or suspend the process, plus a terminal reconfiguration handler to * be called when a process resumption signal is received, and finally * a handler to be called when a terminal-resize signal is received. * * Since there are many signals that by default terminate or suspend * processes, and different systems support different sub-sets of * these signals, this function provides a convenient wrapper around * sigaction() for assigning the specified handlers to all appropriate * signals. It also arranges that when any one of these signals is * being handled, all other catchable signals are blocked. This is * necessary so that the specified signal handlers can safely call * gl_raw_io(), gl_normal_io() and gl_update_size() without reentrancy * issues. * * Input: * term_handler void (*)(int) The signal handler to invoke when * a process terminating signal is * received. * susp_handler void (*)(int) The signal handler to invoke when * a process suspending signal is * received. * cont_handler void (*)(int) The signal handler to invoke when * a process resumption signal is * received (ie. SIGCONT). * size_handler void (*)(int) The signal handler to invoke when * a terminal-resize signal (ie. SIGWINCH) * is received. * Output: * return int 0 - OK. * 1 - Error. */ int gl_tty_signals(void (*term_handler)(int), void (*susp_handler)(int), void (*cont_handler)(int), void (*size_handler)(int)); /*....................................................................... * Return the last signal that was caught by the most recent call to * gl_get_line(), or -1 if no signals were caught. This is useful if * gl_get_line() returns errno=EINTR and you need to find out what signal * caused it to abort. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int The last signal caught by the most recent * call to gl_get_line(), or -1 if no signals * were caught. */ int gl_last_signal(GetLine *gl); /*....................................................................... * Return the signal mask used by gl_get_line(). This is the set of * signals that gl_get_line() is currently configured to trap. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * set sigset_t * The set of signals will be returned in *set, * in the form of a signal process mask, as * used by sigaction(), sigprocmask(), * sigpending(), sigsuspend(), sigsetjmp() and * other standard POSIX signal-aware * functions. * Output: * return int 0 - OK. * 1 - Error (examine errno for reason). */ int gl_list_signals(GetLine *gl, sigset_t *set); /*....................................................................... * Respond to signals who's default effects have important * consequences to gl_get_line(). This is intended for use in * non-blocking server mode, where the external event loop is * responsible for catching signals. Signals that are handled include * those that by default terminate or suspend the process, and the * signal that indicates that the terminal size has changed. Note that * this function is not signal safe and should thus not be called from * a signal handler itself. See the gl_io_mode() man page for how it * should be used. * * In the case of signals that by default terminate or suspend * processes, command-line editing will be suspended, the terminal * returned to a usable state, then the default disposition of the * signal restored and the signal resent, in order to suspend or * terminate the process. If the process subsequently resumes, * command-line editing is resumed. * * In the case of signals that indicate that the terminal has been * resized, the new size will be queried, and any input line that is * being edited will be redrawn to fit the new dimensions of the * terminal. * * Input: * signo int The number of the signal to respond to. * gl GetLine * The first element of an array of 'ngl' GetLine * objects. * ngl int The number of elements in the gl[] array. Normally * this will be one. */ void gl_handle_signal(int signo, GetLine *gl, int ngl); /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in either gl_get_line() or its * associated public functions. * * Input: * gl GetLine * The resource object of gl_get_line(). * Input/Output: * buff char * An optional output buffer. Note that if the * calling application calls any gl_*() * functions from signal handlers, it should * provide a buffer here, so that a copy of * the latest error message can safely be made * while signals are blocked. * n size_t The allocated size of buff[]. * Output: * return const char * A pointer to the error message. This will * be the buff argument, unless buff==NULL, in * which case it will be a pointer to an * internal error buffer. In the latter case, * note that the contents of the returned buffer * will change on subsequent calls to any gl_*() * functions. */ const char *gl_error_message(GetLine *gl, char *buff, size_t n); /*....................................................................... * Clear the terminal and leave the cursor at the home position. In * server I/O mode, arrange for the input line to be redrawn from scratch * when gl_get_line() is next called. * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return int 0 - OK. * 1 - Error. */ int gl_erase_terminal(GetLine *gl); /*....................................................................... * Display a left-justified string over multiple terminal lines, * taking account of the current width of the terminal. Optional * indentation and an optional prefix string can be specified to be * displayed at the start of each new terminal line used. Similarly, * an optional suffix can be specified to be displayed at the end of * each terminal line. If needed, a single paragraph can be broken * across multiple calls. Note that literal newlines in the input * string can be used to force a newline at any point and that you * should use this feature to explicitly end all paragraphs, including * at the end of the last string that you write. Note that when a new * line is started between two words that are separated by spaces, * those spaces are not output, whereas when a new line is started * because a newline character was found in the string, only the * spaces before the newline character are discarded. * * Input: * gl GetLine * The resource object of gl_get_line(). * indentation int The number of spaces of indentation to write * at the beginning of each new terminal line. * prefix const char * An optional prefix string to write after the * indentation margin at the start of each new * terminal line. You can specify NULL if no * prefix is required. * suffix const char * An optional suffix string to draw at the end * of the terminal line. Spaces will be added * where necessary to ensure that the suffix ends * in the last column of the terminal line. If * no suffix is desired, specify NULL. * fill_char int The padding character to use when indenting * the line or padding up to the suffix. * def_width int If the terminal width isn't known, such as when * writing to a pipe or redirecting to a file, * this number specifies what width to assume. * start int The number of characters already written to * the start of the current terminal line. This * is primarily used to allow individual * paragraphs to be written over multiple calls * to this function, but can also be used to * allow you to start the first line of a * paragraph with a different prefix or * indentation than those specified above. * string const char * The string to be written. * Output: * return int On error -1 is returned. Otherwise the * return value is the terminal column index at * which the cursor was left after writing the * final word in the string. Successful return * values can thus be passed verbatim to the * 'start' arguments of subsequent calls to * gl_display_text() to allow the printing of a * paragraph to be broken across multiple calls * to gl_display_text(). */ int gl_display_text(GetLine *gl, int indentation, const char *prefix, const char *suffix, int fill_char, int def_width, int start, const char *string); /* * Enumerate the I/O modes supported by gl_get_line(). */ typedef enum { GL_NORMAL_MODE, /* Normal line-at-a-time mode using gl_get_line()'s */ /* internal event loop. */ GL_SERVER_MODE /* Non-blocking server mode, driven by an external */ /* event loop. */ } GlIOMode; /*....................................................................... * Select the I/O mode to be used by gl_get_line(). * * Input: * gl GetLine * The resource object of gl_get_line(). * mode GlIOMode The I/O mode to establish. Note that * when server mode, the terminal is placed * in raw mode, as though gl_raw_io() had * been called. * Output: * return int 0 - OK. * 1 - Error. */ int gl_io_mode(GetLine *gl, GlIOMode mode); /*....................................................................... * In server mode, this function configures the terminal for non-blocking * raw terminal I/O. In normal I/O mode it does nothing. * * Callers of this function must be careful to trap all signals that * terminate or suspend the program, and call gl_normal_io() * from the corresponding signal handlers in order to restore the * terminal to its original settings before the program is terminated * or suspended. They should also trap the SIGCONT signal to detect * when the program resumes, and ensure that its signal handler * call gl_raw_io() to redisplay the line and resume editing. * * Input: * gl GetLine * The line editor resource object. * Output: * return int 0 - OK. * 1 - Error. */ int gl_raw_io(GetLine *gl); /*....................................................................... * Restore the terminal to the state that it had when gl_raw_io() was * last called. After calling gl_raw_io(), this function must be called * before terminating or suspending the program, and before attempting * other uses of the terminal from within the program. See gl_raw_io() * for more details. * * Input: * gl GetLine * The line editor resource object. * Output: * return int 0 - OK. * 1 - Error. */ int gl_normal_io(GetLine *gl); /*....................................................................... * When in non-blocking server mode, this function can be used to abandon * the current incompletely entered input line, and prepare to start * editing a new line on the next call to gl_get_line(). * * Input: * gl GetLine * The line editor resource object. * Output: * return int 0 - OK. * 1 - Error. */ void gl_abandon_line(GetLine *gl); /* * Enumerators of the following type are used to report why * gl_get_line() returned. This is most useful in non-blocking * server mode, since in that mode a NULL return value can mean * either that an error occurred, or that I/O blocked. */ typedef enum { GLR_NEWLINE, /* A new input line was returned */ GLR_BLOCKED, /* The terminal was in non-blocking mode, and input */ /* or output would have blocked. */ GLR_SIGNAL, /* A signal caused gl_get_line() to return. */ GLR_TIMEOUT, /* An application timeout callback returned GLTO_ABORT */ GLR_FDABORT, /* An application I/O callack returned GLFD_ABORT */ GLR_EOF, /* End of file reached */ GLR_ERROR /* An unexpected error caused gl_get_line() to abort */ } GlReturnStatus; /*....................................................................... * Ask gl_get_line() what caused it to return. * * Input: * gl GetLine * The line editor resource object. * Output: * return GlReturnStatus The return status of the last call to * gl_get_line(). */ GlReturnStatus gl_return_status(GetLine *gl); /* * Enumerate the types of I/O that gl_get_line() can be waiting for * in non-blocking sedrver I/O mode. */ typedef enum { GLP_READ, /* gl_get_line() is waiting to write to the terminal */ GLP_WRITE /* gl_get_line() is waiting to read from the terminal */ } GlPendingIO; /*....................................................................... * In non-blocking server-I/O mode, this function should be called * from the application's external event loop to see what type of * terminal I/O is being waited for by gl_get_line(), and thus what * direction of I/O to wait for with select() or poll(). * * Input: * gl GetLine * The resource object of gl_get_line(). * Output: * return GlPendingIO The type of pending I/O being waited for. */ GlPendingIO gl_pending_io(GetLine *gl); /* * The following enumerators are returned by externally defined action * functions to tell gl_get_line() how to procede after the action * function returns. */ typedef enum { GLA_ABORT, /* Cause gl_get_line() to return NULL */ GLA_RETURN, /* Return the line as though the user had pressed the */ /* return key. */ GLA_CONTINUE /* Resume command-line editing */ } GlAfterAction; /*....................................................................... * Functions of the following form implement external * application-specific action functions, which can then be bound to * sequences of terminal keys. * * Input: * gl GetLine * The line editor resource object. * data void * The anonymous 'data' argument that was * passed to gl_external_action() when the * callback function was registered. * count int A positive repeat count specified by the user, * or 1 if not specified. Action functions should * ignore this if repeating the action multiple * times isn't appropriate. Alternatively they * can interpret it as a general numeric * argument. * curpos size_t The position of the cursor within the input * line, expressed as the index of the * corresponding character within the line[] * array. * line const char * A read-only copy of the current input line. * Output * return GlAfterAction What should gl_get_line() do when the action * function returns? * GLA_ABORT - Cause gl_get_line() to * abort with an error (set * errno if you need it). * GLA_RETURN - Return the input line as * though the user had typed * the return key. * GLA_CONTINUE - Resume waiting for keyboard * input. */ #define GL_ACTION_FN(fn) GlAfterAction (fn)(GetLine *gl, void *data, \ int count, size_t curpos, const char *line) typedef GL_ACTION_FN(GlActionFn); /*....................................................................... * Register an application-provided function as an action function. * This should preferably be called before the first call to gl_get_line() * so that the name of the action becomes defined before the user's * configuration file is read. * * Input: * gl GetLine * The resource object of the command-line input * module. * data void * Arbitrary application-specific callback * data to be passed to the callback * function, fn(). * fn GlActionFn * The application-specific function that * implements the action. This will be invoked * whenever the user presses any * key-sequence which is bound to this action. * name const char * The name with which users can refer to the * binding in tecla configuration files. * keyseq const char * The key sequence with which to invoke * the binding. This should be specified in the * same manner as key-sequences in tecla * configuration files (eg. "M-^I"). * Output: * return int 0 - OK. * 1 - Error. */ int gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq); /*....................................................................... * This function is designed to be called by CPL_MATCH_FN() callback * functions. It adds one possible completion of the token that is being * completed to an array of completions. If the completion needs any * special quoting to be valid when displayed in the input line, this * quoting must be included in the string. * * Input: * cpl WordCompletion * The argument of the same name that was passed * to the calling CPL_MATCH_FN() callback function. * line const char * The input line, as received by the callback * function. * word_start int The index within line[] of the start of the * word that is being completed. If an empty * string is being completed, set this to be * the same as word_end. * word_end int The index within line[] of the character which * follows the incomplete word, as received by the * callback function. * suffix const char * The appropriately quoted string that could * be appended to the incomplete token to complete * it. A copy of this string will be allocated * internally. * type_suffix const char * When listing multiple completions, gl_get_line() * appends this string to the completion to indicate * its type to the user. If not pertinent pass "". * Otherwise pass a literal or static string. * cont_suffix const char * If this turns out to be the only completion, * gl_get_line() will append this string as * a continuation. For example, the builtin * file-completion callback registers a directory * separator here for directory matches, and a * space otherwise. If the match were a function * name you might want to append an open * parenthesis, etc.. If not relevant pass "". * Otherwise pass a literal or static string. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_add_completion(WordCompletion *cpl, const char *line, int word_start, int word_end, const char *suffix, const char *type_suffix, const char *cont_suffix); /* * Each possible completion string is recorded in an array element of * the following type. */ typedef struct { char *completion; /* The matching completion string */ char *suffix; /* The pointer into completion[] at which the */ /* string was extended. */ const char *type_suffix; /* A suffix to be added when listing completions */ /* to indicate the type of the completion. */ } CplMatch; /* * Completions are returned in a container of the following form. */ typedef struct { char *suffix; /* The common initial part of all of the */ /* completion suffixes. */ const char *cont_suffix; /* Optional continuation string to be appended to */ /* the sole completion when nmatch==1. */ CplMatch *matches; /* The array of possible completion strings, */ /* sorted into lexical order. */ int nmatch; /* The number of elements in matches[] */ } CplMatches; /*....................................................................... * Given an input line and the point at which completion is to be * attempted, return an array of possible completions. * * Input: * cpl WordCompletion * The word-completion resource object. * line const char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * data void * Anonymous 'data' to be passed to match_fn(). * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * record completion suffixes. * Output: * return CplMatches * The container of the array of possible * completions. The returned pointer refers * to a container owned by the parent Completion * object, and its contents thus potentially * change on every call to cpl_complete_word(). */ CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line, int word_end, void *data, CplMatchFn *match_fn); /*....................................................................... * Recall the return value of the last call to cpl_complete_word(). * * Input: * cpl WordCompletion * The completion resource object. * Output: * return CplMatches * The container of the array of possible * completions, as returned by the last call to * cpl_complete_word(). The returned pointer refers * to a container owned by the parent WordCompletion * object, and its contents thus potentially * change on every call to cpl_complete_word(). * On error, either in the execution of this * function, or in the last call to * cpl_complete_word(), NULL is returned, and a * description of the error can be acquired by * calling cpl_last_error(cpl). */ CplMatches *cpl_recall_matches(WordCompletion *cpl); /*....................................................................... * Print out an array of matching completions. * * Input: * result CplMatches * The container of the sorted array of * completions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_list_completions(CplMatches *result, FILE *fp, int term_width); /*....................................................................... * Return a description of the error that occurred on the last call to * cpl_complete_word() or cpl_add_completion(). * * Input: * cpl WordCompletion * The string-completion resource object. * Output: * return const char * The description of the last error. */ const char *cpl_last_error(WordCompletion *cpl); /* * PathCache objects encapsulate the resources needed to record * files of interest from comma-separated lists of directories. */ typedef struct PathCache PathCache; /*....................................................................... * Create an object who's function is to maintain a cache of filenames * found within a list of directories, and provide quick lookup and * completion of selected files in this cache. * * Output: * return PathCache * The new, initially empty cache, or NULL * on error. */ PathCache *new_PathCache(void); /*....................................................................... * Delete a given cache of files, returning the resources that it * was using to the system. * * Input: * pc PathCache * The cache to be deleted (can be NULL). * Output: * return PathCache * The deleted object (ie. allways NULL). */ PathCache *del_PathCache(PathCache *pc); /*....................................................................... * Return a description of the last path-caching error that occurred. * * Input: * pc PathCache * The filename cache that suffered the error. * Output: * return char * The description of the last error. */ const char *pca_last_error(PathCache *pc); /*....................................................................... * Build the list of files of interest contained in a given * colon-separated list of directories. * * Input: * pc PathCache * The cache in which to store the names of * the files that are found in the list of * directories. * path const char * A colon-separated list of directory * paths. Under UNIX, when searching for * executables, this should be the return * value of getenv("PATH"). * Output: * return int 0 - OK. * 1 - An error occurred. */ int pca_scan_path(PathCache *pc, const char *path); /*....................................................................... * If you want subsequent calls to pca_lookup_file() and * pca_path_completions() to only return the filenames of certain * types of files, for example executables, or filenames ending in * ".ps", call this function to register a file-selection callback * function. This callback function takes the full pathname of a file, * plus application-specific data, and returns 1 if the file is of * interest, and zero otherwise. * * Input: * pc PathCache * The filename cache. * check_fn CplCheckFn * The function to call to see if the name of * a given file should be included in the * cache. This determines what type of files * will reside in the cache. To revert to * selecting all files, regardless of type, * pass 0 here. * data void * You can pass a pointer to anything you * like here, including NULL. It will be * passed to your check_fn() callback * function, for its private use. */ void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn, void *data); /*....................................................................... * Given the simple name of a file, search the cached list of files * in the order in which they where found in the list of directories * previously presented to pca_scan_path(), and return the pathname * of the first file which has this name. * * Input: * pc PathCache * The cached list of files. * name const char * The name of the file to lookup. * name_len int The length of the filename substring at the * beginning of name[], or -1 to assume that the * filename occupies the whole of the string. * literal int If this argument is zero, lone backslashes * in name[] are ignored during comparison * with filenames in the cache, under the * assumption that they were in the input line * soley to escape the special significance of * characters like spaces. To have them treated * as normal characters, give this argument a * non-zero value, such as 1. * Output: * return char * The pathname of the first matching file, * or NULL if not found. Note that the returned * pointer points to memory owned by *pc, and * will become invalid on the next call. */ char *pca_lookup_file(PathCache *pc, const char *name, int name_len, int literal); /* * Objects of the following type can be used to change the default * behavior of the pca_path_completions() callback function. */ typedef struct PcaPathConf PcaPathConf; /* * pca_path_completions() is a completion callback function for use directly * with cpl_complete_word() or gl_customize_completions(), or indirectly * from your own completion callback function. It requires that a PcaPathConf * object be passed via its 'void *data' argument (see below). */ CPL_MATCH_FN(pca_path_completions); /*....................................................................... * Allocate and initialize a pca_path_completions() configuration object. * * Input: * pc PathCache * The filename cache in which to look for * file name completions. * Output: * return PcaPathConf * The new configuration structure, or NULL * on error. */ PcaPathConf *new_PcaPathConf(PathCache *pc); /*....................................................................... * Deallocate memory, previously allocated by new_PcaPathConf(). * * Input: * ppc PcaPathConf * Any pointer previously returned by * new_PcaPathConf() [NULL is allowed]. * Output: * return PcaPathConf * The deleted structure (always NULL). */ PcaPathConf *del_PcaPathConf(PcaPathConf *ppc); /* * If backslashes in the prefix being passed to pca_path_completions() * should be treated as literal characters, call the following function * with literal=1. Otherwise the default is to treat them as escape * characters which remove the special meanings of spaces etc.. */ void ppc_literal_escapes(PcaPathConf *ppc, int literal); /* * Before calling pca_path_completions, call this function if you know * the index at which the filename prefix starts in the input line. * Otherwise by default, or if you specify start_index to be -1, the * filename is taken to start after the first unescaped space preceding * the cursor, or the start of the line, whichever comes first. */ void ppc_file_start(PcaPathConf *ppc, int start_index); #ifdef __cplusplus } #endif #endif ./libtecla/pathutil.c0100644000076400007640000003633010027466660013143 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM #include #include #include #include #include #include #include #include #include #include "pathutil.h" /*....................................................................... * Create a new PathName object. * * Output: * return PathName * The new object, or NULL on error. */ PathName *_new_PathName(void) { PathName *path; /* The object to be returned */ /* * Allocate the container. */ path = (PathName *) malloc(sizeof(PathName)); if(!path) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_PathName(). */ path->name = NULL; path->dim = 0; /* * Figure out the maximum length of an expanded pathname. */ path->dim = _pu_pathname_dim(); if(path->dim == 0) return _del_PathName(path); /* * Allocate the pathname buffer. */ path->name = (char *)malloc(path->dim * sizeof(char)); if(!path->name) { errno = ENOMEM; return _del_PathName(path); }; return path; } /*....................................................................... * Delete a PathName object. * * Input: * path PathName * The object to be deleted. * Output: * return PathName * The deleted object (always NULL). */ PathName *_del_PathName(PathName *path) { if(path) { if(path->name) free(path->name); free(path); }; return NULL; } /*....................................................................... * Return the pathname to a zero-length string. * * Input: * path PathName * The pathname container. * Output: * return char * The cleared pathname buffer, or NULL on error. */ char *_pn_clear_path(PathName *path) { /* * Check the arguments. */ if(!path) { errno = EINVAL; return NULL; }; path->name[0] = '\0'; return path->name; } /*....................................................................... * Append a string to a pathname, increasing the size of the pathname * buffer if needed. * * Input: * path PathName * The pathname container. * string const char * The string to be appended to the pathname. * Note that regardless of the slen argument, * this should be a '\0' terminated string. * slen int The maximum number of characters to append * from string[], or -1 to append the whole * string. * remove_escapes int If true, remove the backslashes that escape * spaces, tabs, backslashes etc.. * Output: * return char * The pathname string path->name[], which may * have been reallocated, or NULL if there was * insufficient memory to extend the pathname. */ char *_pn_append_to_path(PathName *path, const char *string, int slen, int remove_escapes) { int pathlen; /* The length of the pathname */ int i; /* * Check the arguments. */ if(!path || !string) { errno = EINVAL; return NULL; }; /* * Get the current length of the pathname. */ pathlen = strlen(path->name); /* * How many characters should be appended? */ if(slen < 0 || slen > strlen(string)) slen = strlen(string); /* * Resize the pathname if needed. */ if(!_pn_resize_path(path, pathlen + slen)) return NULL; /* * Append the string to the output pathname, removing any escape * characters found therein. */ if(remove_escapes) { int is_escape = 0; for(i=0; iname[pathlen++] = string[i]; }; /* * Terminate the string. */ path->name[pathlen] = '\0'; } else { /* * Append the string directly to the pathname. */ memcpy(path->name + pathlen, string, slen); path->name[pathlen + slen] = '\0'; }; return path->name; } /*....................................................................... * Prepend a string to a pathname, increasing the size of the pathname * buffer if needed. * * Input: * path PathName * The pathname container. * string const char * The string to be prepended to the pathname. * Note that regardless of the slen argument, * this should be a '\0' terminated string. * slen int The maximum number of characters to prepend * from string[], or -1 to append the whole * string. * remove_escapes int If true, remove the backslashes that escape * spaces, tabs, backslashes etc.. * Output: * return char * The pathname string path->name[], which may * have been reallocated, or NULL if there was * insufficient memory to extend the pathname. */ char *_pn_prepend_to_path(PathName *path, const char *string, int slen, int remove_escapes) { int pathlen; /* The length of the pathname */ int shift; /* The number of characters to shift the suffix by */ int i,j; /* * Check the arguments. */ if(!path || !string) { errno = EINVAL; return NULL; }; /* * Get the current length of the pathname. */ pathlen = strlen(path->name); /* * How many characters should be appended? */ if(slen < 0 || slen > strlen(string)) slen = strlen(string); /* * Work out how far we need to shift the original path string to make * way for the new prefix. When removing escape characters, we need * final length of the new prefix, after unescaped backslashes have * been removed. */ if(remove_escapes) { int is_escape = 0; for(shift=0,i=0; iname + shift, path->name, pathlen+1); /* * Copy the new prefix into the vacated space at the beginning of the * output pathname, removing any escape characters if needed. */ if(remove_escapes) { int is_escape = 0; for(i=j=0; iname[j++] = string[i]; }; } else { memcpy(path->name, string, slen); }; return path->name; } /*....................................................................... * If needed reallocate a given pathname buffer to allow a string of * a given length to be stored in it. * * Input: * path PathName * The pathname container object. * length size_t The required length of the pathname buffer, * not including the terminating '\0'. * Output: * return char * The pathname buffer, or NULL if there was * insufficient memory. */ char *_pn_resize_path(PathName *path, size_t length) { /* * Check the arguments. */ if(!path) { errno = EINVAL; return NULL; }; /* * If the pathname buffer isn't large enough to accomodate a string * of the specified length, attempt to reallocate it with the new * size, plus space for a terminating '\0'. Also add a bit of * head room to prevent too many reallocations if the initial length * turned out to be very optimistic. */ if(length + 1 > path->dim) { size_t dim = length + 1 + PN_PATHNAME_INC; char *name = (char *) realloc(path->name, dim); if(!name) return NULL; path->name = name; path->dim = dim; }; return path->name; } /*....................................................................... * Estimate the largest amount of space needed to store a pathname. * * Output: * return size_t The number of bytes needed, including space for the * terminating '\0'. */ size_t _pu_pathname_dim(void) { int maxlen; /* The return value excluding space for the '\0' */ /* * If the POSIX PATH_MAX macro is defined in limits.h, use it. */ #ifdef PATH_MAX maxlen = PATH_MAX; /* * If we have pathconf, use it. */ #elif defined(_PC_PATH_MAX) errno = 0; maxlen = pathconf(FS_ROOT_DIR, _PC_PATH_MAX); if(maxlen <= 0 || errno) maxlen = MAX_PATHLEN_FALLBACK; /* * None of the above approaches worked, so substitute our fallback * guess. */ #else maxlen = MAX_PATHLEN_FALLBACK; #endif /* * Return the amount of space needed to accomodate a pathname plus * a terminating '\0'. */ return maxlen + 1; } /*....................................................................... * Return non-zero if the specified path name refers to a directory. * * Input: * pathname const char * The path to test. * Output: * return int 0 - Not a directory. * 1 - pathname[] refers to a directory. */ int _pu_path_is_dir(const char *pathname) { struct stat statbuf; /* The file-statistics return buffer */ /* * Look up the file attributes. */ if(stat(pathname, &statbuf) < 0) return 0; /* * Is the file a directory? */ return S_ISDIR(statbuf.st_mode) != 0; } /*....................................................................... * Return non-zero if the specified path name refers to a regular file. * * Input: * pathname const char * The path to test. * Output: * return int 0 - Not a regular file. * 1 - pathname[] refers to a regular file. */ int _pu_path_is_file(const char *pathname) { struct stat statbuf; /* The file-statistics return buffer */ /* * Look up the file attributes. */ if(stat(pathname, &statbuf) < 0) return 0; /* * Is the file a regular file? */ return S_ISREG(statbuf.st_mode) != 0; } /*....................................................................... * Return non-zero if the specified path name refers to an executable. * * Input: * pathname const char * The path to test. * Output: * return int 0 - Not an executable file. * 1 - pathname[] refers to an executable file. */ int _pu_path_is_exe(const char *pathname) { struct stat statbuf; /* The file-statistics return buffer */ /* * Look up the file attributes. */ if(stat(pathname, &statbuf) < 0) return 0; /* * Is the file a regular file which is executable by the current user. */ return S_ISREG(statbuf.st_mode) != 0 && (statbuf.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) && access(pathname, X_OK) == 0; } /*....................................................................... * Search backwards for the potential start of a filename. This * looks backwards from the specified index in a given string, * stopping at the first unescaped space or the start of the line. * * Input: * string const char * The string to search backwards in. * back_from int The index of the first character in string[] * that follows the pathname. * Output: * return char * The pointer to the first character of * the potential pathname, or NULL on error. */ char *_pu_start_of_path(const char *string, int back_from) { int i, j; /* * Check the arguments. */ if(!string || back_from < 0) { errno = EINVAL; return NULL; }; /* * Search backwards from the specified index. */ for(i=back_from-1; i>=0; i--) { int c = string[i]; /* * Stop on unescaped spaces. */ if(isspace((int)(unsigned char)c)) { /* * The space can't be escaped if we are at the start of the line. */ if(i==0) break; /* * Find the extent of the escape characters which precedes the space. */ for(j=i-1; j>=0 && string[j]=='\\'; j--) ; /* * If there isn't an odd number of escape characters before the space, * then the space isn't escaped. */ if((i - 1 - j) % 2 == 0) break; }; }; return (char *)string + i + 1; } /*....................................................................... * Find the length of a potential filename starting from a given * point. This looks forwards from the specified index in a given string, * stopping at the first unescaped space or the end of the line. * * Input: * string const char * The string to search backwards in. * start_from int The index of the first character of the pathname * in string[]. * Output: * return char * The pointer to the character that follows * the potential pathname, or NULL on error. */ char *_pu_end_of_path(const char *string, int start_from) { int c; /* The character being examined */ int escaped = 0; /* True when the next character is escaped */ int i; /* * Check the arguments. */ if(!string || start_from < 0) { errno = EINVAL; return NULL; }; /* * Search forwards from the specified index. */ for(i=start_from; (c=string[i]) != '\0'; i++) { if(escaped) { escaped = 0; } else if(isspace(c)) { break; } else if(c == '\\') { escaped = 1; }; }; return (char *)string + i; } /*....................................................................... * Return non-zero if the specified path name refers to an existing file. * * Input: * pathname const char * The path to test. * Output: * return int 0 - The file doesn't exist. * 1 - The file does exist. */ int _pu_file_exists(const char *pathname) { struct stat statbuf; return stat(pathname, &statbuf) == 0; } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/pathutil.h0100644000076400007640000001056310027466660013150 0ustar mcsmcs#ifndef pathutil_h #define pathutil_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * The following object encapsulates a buffer designed to be used to * store pathnames. The pathname member of the object is initially * allocated with the size that _pu_pathname_dim() returns, and then * if this turns out to be pessimistic, the pathname can be reallocated * via calls to pb_append_to_path() and/or pb_resize_path(). */ typedef struct { char *name; /* The path buffer */ size_t dim; /* The current allocated size of buffer[] */ } PathName; PathName *_new_PathName(void); PathName *_del_PathName(PathName *path); char *_pn_clear_path(PathName *path); char *_pn_append_to_path(PathName *path, const char *string, int slen, int remove_escapes); char *_pn_prepend_to_path(PathName *path, const char *string, int slen, int remove_escapes); char *_pn_resize_path(PathName *path, size_t length); /* * Search backwards for the potential start of a filename. This * looks backwards from the specified index in a given string, * stopping at the first unescaped space or the start of the line. */ char *_pu_start_of_path(const char *string, int back_from); /* * Find the end of a potential filename, starting from a given index * in the string. This looks forwards from the specified index in a * given string, stopping at the first unescaped space or the end * of the line. */ char *_pu_end_of_path(const char *string, int start_from); /* * Return an estimate of the the length of the longest pathname * on the local system. */ size_t _pu_pathname_dim(void); /* * Return non-zero if the specified path name refers to a directory. */ int _pu_path_is_dir(const char *pathname); /* * Return non-zero if the specified path name refers to a regular file. */ int _pu_path_is_file(const char *pathname); /* * Return non-zero if the specified path name refers to an executable. */ int _pu_path_is_exe(const char *pathname); /* * Return non-zero if a file exists with the specified pathname. */ int _pu_file_exists(const char *pathname); /* * If neither the POSIX PATH_MAX macro nor the pathconf() function * can be used to find out the maximum pathlength on the target * system, the following fallback maximum length is used. */ #define MAX_PATHLEN_FALLBACK 1024 /* * If the pathname buffer turns out to be too small, it will be extended * in chunks of the following amount (plus whatever is needed at the time). */ #define PN_PATHNAME_INC 100 /* * Define the special character-sequences of the filesystem. */ #define FS_ROOT_DIR "/" /* The root directory */ #define FS_ROOT_DIR_LEN (sizeof(FS_ROOT_DIR) - 1) #define FS_PWD "." /* The current working directory */ #define FS_PWD_LEN (sizeof(FS_PWD_LEN) - 1) #define FS_DIR_SEP "/" /* The directory separator string */ #define FS_DIR_SEP_LEN (sizeof(FS_DIR_SEP) - 1) #endif ./libtecla/pcache.c0100644000076400007640000015217710027466660012544 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * If file-system access is to be excluded, this module has no function, * so all of its code should be excluded. */ #ifndef WITHOUT_FILE_SYSTEM #include #include #include #include #include "libtecla.h" #include "pathutil.h" #include "homedir.h" #include "freelist.h" #include "direader.h" #include "stringrp.h" #include "errmsg.h" /* * The new_PcaPathConf() constructor sets the integer first member of * the returned object to the following magic number. This is then * checked for by pca_path_completions() as a sanity check. */ #define PPC_ID_CODE 4567 /* * A pointer to a structure of the following type can be passed to * the builtin path-completion callback function to modify its behavior. */ struct PcaPathConf { int id; /* This is set to PPC_ID_CODE by new_PcaPathConf() */ PathCache *pc; /* The path-list cache in which to look up the executables */ int escaped; /* If non-zero, backslashes in the input line are */ /* interpreted as escaping special characters and */ /* spaces, and any special characters and spaces in */ /* the listed completions will also be escaped with */ /* added backslashes. This is the default behaviour. */ /* If zero, backslashes are interpreted as being */ /* literal parts of the file name, and none are added */ /* to the completion suffixes. */ int file_start; /* The index in the input line of the first character */ /* of the file name. If you specify -1 here, */ /* pca_path_completions() identifies the */ /* the start of the file by looking backwards for */ /* an unescaped space, or the beginning of the line. */ }; /* * Prepended to each chached filename is a character which contains * one of the following status codes. When a given filename (minus * this byte) is passed to the application's check_fn(), the result * is recorded in this byte, such that the next time it is looked * up, we don't have to call check_fn() again. These codes are cleared * whenever the path is scanned and whenever the check_fn() callback * is changed. */ typedef enum { PCA_F_ENIGMA='?', /* The file remains to be checked */ PCA_F_WANTED='+', /* The file has been selected by the caller's callback */ PCA_F_IGNORE='-' /* The file has been rejected by the caller's callback */ } PcaFileStatus; /* * Encapsulate the memory management objects which supply memoy for * the arrays of filenames. */ typedef struct { StringGroup *sg; /* The memory used to record the names of files */ size_t files_dim; /* The allocated size of files[] */ char **files; /* Memory for 'files_dim' pointers to files */ size_t nfiles; /* The number of filenames currently in files[] */ } CacheMem; static CacheMem *new_CacheMem(void); static CacheMem *del_CacheMem(CacheMem *cm); static void rst_CacheMem(CacheMem *cm); /* * Lists of nodes of the following type are used to record the * names and contents of individual directories. */ typedef struct PathNode PathNode; struct PathNode { PathNode *next; /* The next directory in the path */ int relative; /* True if the directory is a relative pathname */ CacheMem *mem; /* The memory used to store dir[] and files[] */ char *dir; /* The directory pathname (stored in pc->sg) */ int nfile; /* The number of filenames stored in 'files' */ char **files; /* Files of interest in the current directory, */ /* or NULL if dir[] is a relative pathname */ /* who's contents can't be cached. This array */ /* and its contents are taken from pc->abs_mem */ /* or pc->rel_mem */ }; /* * Append a new node to the list of directories in the path. */ static int add_PathNode(PathCache *pc, const char *dirname); /* * Set the maximum length allowed for usernames. * names. */ #define USR_LEN 100 /* * PathCache objects encapsulate the resources needed to record * files of interest from comma-separated lists of directories. */ struct PathCache { ErrMsg *err; /* The error reporting buffer */ FreeList *node_mem; /* A free-list of PathNode objects */ CacheMem *abs_mem; /* Memory for the filenames of absolute paths */ CacheMem *rel_mem; /* Memory for the filenames of relative paths */ PathNode *head; /* The head of the list of directories in the */ /* path, or NULL if no path has been scanned yet. */ PathNode *tail; /* The tail of the list of directories in the */ /* path, or NULL if no path has been scanned yet. */ PathName *path; /* The fully qualified name of a file */ HomeDir *home; /* Home-directory lookup object */ DirReader *dr; /* A portable directory reader */ CplFileConf *cfc; /* Configuration parameters to pass to */ /* cpl_file_completions() */ CplCheckFn *check_fn; /* The callback used to determine if a given */ /* filename should be recorded in the cache. */ void *data; /* Annonymous data to be passed to pc->check_fn() */ char usrnam[USR_LEN+1];/* The buffer used when reading the names of */ /* users. */ }; /* * Empty the cache. */ static void pca_clear_cache(PathCache *pc); /* * Read a username from string[] and record it in pc->usrnam[]. */ static int pca_read_username(PathCache *pc, const char *string, int slen, int literal, const char **nextp); /* * Extract the next component of a colon separated list of directory * paths. */ static int pca_extract_dir(PathCache *pc, const char *path, const char **nextp); /* * Scan absolute directories for files of interest, recording their names * in mem->sg and recording pointers to these names in mem->files[]. */ static int pca_scan_dir(PathCache *pc, const char *dirname, CacheMem *mem); /* * A qsort() comparison function for comparing the cached filename * strings pointed to by two (char **) array elements. Note that * this ignores the initial cache-status byte of each filename. */ static int pca_cmp_matches(const void *v1, const void *v2); /* * A qsort() comparison function for comparing a filename * against an element of an array of pointers to filename cache * entries. */ static int pca_cmp_file(const void *v1, const void *v2); /* * Initialize a PcaPathConf configuration objects with the default * options. */ static int pca_init_PcaPathConf(PcaPathConf *ppc, PathCache *pc); /* * Make a copy of a completion suffix, suitable for passing to * cpl_add_completion(). */ static int pca_prepare_suffix(PathCache *pc, const char *suffix, int add_escapes); /* * Return non-zero if the specified string appears to start with a pathname. */ static int cpa_cmd_contains_path(const char *prefix, int prefix_len); /* * Return a given prefix with escapes optionally removed. */ static const char *pca_prepare_prefix(PathCache *pc, const char *prefix, size_t prefix_len, int escaped); /* * If there is a tilde expression at the beginning of the specified path, * place the corresponding home directory into pc->path. Otherwise * just clear pc->path. */ static int pca_expand_tilde(PathCache *pc, const char *path, int pathlen, int literal, const char **endp); /* * Clear the filename status codes that are recorded before each filename * in the cache. */ static void pca_remove_marks(PathCache *pc); /* * Specify how many PathNode's to allocate at a time. */ #define PATH_NODE_BLK 30 /* * Specify the amount by which the files[] arrays are to be extended * whenever they are found to be too small. */ #define FILES_BLK_FACT 256 /*....................................................................... * Create a new object who's function is to maintain a cache of * filenames found within a list of directories, and provide quick * lookup and completion of selected files in this cache. * * Output: * return PathCache * The new, initially empty cache, or NULL * on error. */ PathCache *new_PathCache(void) { PathCache *pc; /* The object to be returned */ /* * Allocate the container. */ pc = (PathCache *)malloc(sizeof(PathCache)); if(!pc) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_PathCache(). */ pc->err = NULL; pc->node_mem = NULL; pc->abs_mem = NULL; pc->rel_mem = NULL; pc->head = NULL; pc->tail = NULL; pc->path = NULL; pc->home = NULL; pc->dr = NULL; pc->cfc = NULL; pc->check_fn = 0; pc->data = NULL; pc->usrnam[0] = '\0'; /* * Allocate a place to record error messages. */ pc->err = _new_ErrMsg(); if(!pc->err) return del_PathCache(pc); /* * Allocate the freelist of directory list nodes. */ pc->node_mem = _new_FreeList(sizeof(PathNode), PATH_NODE_BLK); if(!pc->node_mem) return del_PathCache(pc); /* * Allocate memory for recording names of files in absolute paths. */ pc->abs_mem = new_CacheMem(); if(!pc->abs_mem) return del_PathCache(pc); /* * Allocate memory for recording names of files in relative paths. */ pc->rel_mem = new_CacheMem(); if(!pc->rel_mem) return del_PathCache(pc); /* * Allocate a pathname buffer. */ pc->path = _new_PathName(); if(!pc->path) return del_PathCache(pc); /* * Allocate an object for looking up home-directories. */ pc->home = _new_HomeDir(); if(!pc->home) return del_PathCache(pc); /* * Allocate an object for reading directories. */ pc->dr = _new_DirReader(); if(!pc->dr) return del_PathCache(pc); /* * Allocate a cpl_file_completions() configuration object. */ pc->cfc = new_CplFileConf(); if(!pc->cfc) return del_PathCache(pc); /* * Configure cpl_file_completions() to use check_fn() to select * files of interest. */ cfc_set_check_fn(pc->cfc, pc->check_fn, pc->data); /* * Return the cache, ready for use. */ return pc; } /*....................................................................... * Delete a given cache of files, returning the resources that it * was using to the system. * * Input: * pc PathCache * The cache to be deleted (can be NULL). * Output: * return PathCache * The deleted object (ie. allways NULL). */ PathCache *del_PathCache(PathCache *pc) { if(pc) { /* * Delete the error message buffer. */ pc->err = _del_ErrMsg(pc->err); /* * Delete the memory of the list of path nodes. */ pc->node_mem = _del_FreeList(pc->node_mem, 1); /* * Delete the memory used to record filenames. */ pc->abs_mem = del_CacheMem(pc->abs_mem); pc->rel_mem = del_CacheMem(pc->rel_mem); /* * The list of PathNode's was already deleted when node_mem was * deleted. */ pc->head = NULL; pc->tail = NULL; /* * Delete the pathname buffer. */ pc->path = _del_PathName(pc->path); /* * Delete the home-directory lookup object. */ pc->home = _del_HomeDir(pc->home); /* * Delete the directory reader. */ pc->dr = _del_DirReader(pc->dr); /* * Delete the cpl_file_completions() config object. */ pc->cfc = del_CplFileConf(pc->cfc); /* * Delete the container. */ free(pc); }; return NULL; } /*....................................................................... * If you want subsequent calls to pca_lookup_file() and * pca_path_completions() to only return the filenames of certain * types of files, for example executables, or filenames ending in * ".ps", call this function to register a file-selection callback * function. This callback function takes the full pathname of a file, * plus application-specific data, and returns 1 if the file is of * interest, and zero otherwise. * * Input: * pc PathCache * The filename cache. * check_fn CplCheckFn * The function to call to see if the name of * a given file should be included in the * cache. This determines what type of files * will reside in the cache. To revert to * selecting all files, regardless of type, * pass 0 here. * data void * You can pass a pointer to anything you * like here, including NULL. It will be * passed to your check_fn() callback * function, for its private use. */ void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn, void *data) { if(pc) { /* * If the callback or its data pointer have changed, clear the cached * statuses of files that were accepted or rejected by the previous * calback. */ if(check_fn != pc->check_fn || data != pc->data) pca_remove_marks(pc); /* * Record the new callback locally. */ pc->check_fn = check_fn; pc->data = data; /* * Configure cpl_file_completions() to use the same callback to * select files of interest. */ cfc_set_check_fn(pc->cfc, check_fn, data); }; return; } /*....................................................................... * Return a description of the last path-caching error that occurred. * * Input: * pc PathCache * The filename cache that suffered the error. * Output: * return char * The description of the last error. */ const char *pca_last_error(PathCache *pc) { return pc ? _err_get_msg(pc->err) : "NULL PathCache argument"; } /*....................................................................... * Discard all cached filenames. * * Input: * pc PathCache * The cache to be cleared. */ static void pca_clear_cache(PathCache *pc) { if(pc) { /* * Return all path-nodes to the freelist. */ _rst_FreeList(pc->node_mem); pc->head = pc->tail = NULL; /* * Delete all filename strings. */ rst_CacheMem(pc->abs_mem); rst_CacheMem(pc->rel_mem); }; return; } /*....................................................................... * Build the list of files of interest contained in a given * colon-separated list of directories. * * Input: * pc PathCache * The cache in which to store the names of * the files that are found in the list of * directories. * path const char * A colon-separated list of directory * paths. Under UNIX, when searching for * executables, this should be the return * value of getenv("PATH"). * Output: * return int 0 - OK. * 1 - An error occurred. A description of * the error can be acquired by calling * pca_last_error(pc). */ int pca_scan_path(PathCache *pc, const char *path) { const char *pptr; /* A pointer to the next unprocessed character in path[] */ PathNode *node; /* A node in the list of directory paths */ char **fptr; /* A pointer into pc->abs_mem->files[] */ /* * Check the arguments. */ if(!pc) return 1; /* * Clear the outdated contents of the cache. */ pca_clear_cache(pc); /* * If no path list was provided, there is nothing to be added to the * cache. */ if(!path) return 0; /* * Extract directories from the path list, expanding tilde expressions * on the fly into pc->pathname, then add them to the list of path * nodes, along with a sorted list of the filenames of interest that * the directories hold. */ pptr = path; while(*pptr) { /* * Extract the next pathname component into pc->path->name. */ if(pca_extract_dir(pc, pptr, &pptr)) return 1; /* * Add a new node to the list of paths, containing both the * directory name and, if not a relative pathname, the list of * files of interest in the directory. */ if(add_PathNode(pc, pc->path->name)) return 1; }; /* * The file arrays in each absolute directory node are sections of * pc->abs_mem->files[]. Record pointers to the starts of each * of these sections in each directory node. Note that this couldn't * be done in add_PathNode(), because pc->abs_mem->files[] may * get reallocated in subsequent calls to add_PathNode(), thus * invalidating any pointers to it. */ fptr = pc->abs_mem->files; for(node=pc->head; node; node=node->next) { node->files = fptr; fptr += node->nfile; }; return 0; } /*....................................................................... * Extract the next directory path from a colon-separated list of * directories, expanding tilde home-directory expressions where needed. * * Input: * pc PathCache * The cache of filenames. * path const char * A pointer to the start of the next component * in the path list. * Input/Output: * nextp const char ** A pointer to the next unprocessed character * in path[] will be assigned to *nextp. * Output: * return int 0 - OK. The extracted path is in pc->path->name. * 1 - Error. A description of the error will * have been left in pc->err. */ static int pca_extract_dir(PathCache *pc, const char *path, const char **nextp) { const char *pptr; /* A pointer into path[] */ const char *sptr; /* The path following tilde expansion */ int escaped = 0; /* True if the last character was a backslash */ /* * If there is a tilde expression at the beginning of the specified path, * place the corresponding home directory into pc->path. Otherwise * just clear pc->path. */ if(pca_expand_tilde(pc, path, strlen(path), 0, &pptr)) return 1; /* * Keep a record of the current location in the path. */ sptr = pptr; /* * Locate the end of the directory name in the pathname string, stopping * when either the end of the string is reached, or an un-escaped colon * separator is seen. */ while(*pptr && (escaped || *pptr != ':')) escaped = !escaped && *pptr++ == '\\'; /* * Append the rest of the directory path to the pathname buffer. */ if(_pn_append_to_path(pc->path, sptr, pptr - sptr, 1) == NULL) { _err_record_msg(pc->err, "Insufficient memory to record directory name", END_ERR_MSG); return 1; }; /* * To facilitate subsequently appending filenames to the directory * path name, make sure that the recorded directory name ends in a * directory separator. */ { int dirlen = strlen(pc->path->name); if(dirlen < FS_DIR_SEP_LEN || strncmp(pc->path->name + dirlen - FS_DIR_SEP_LEN, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0) { if(_pn_append_to_path(pc->path, FS_DIR_SEP, FS_DIR_SEP_LEN, 0) == NULL) { _err_record_msg(pc->err, "Insufficient memory to record directory name", END_ERR_MSG); return 1; }; }; }; /* * Skip the separator unless we have reached the end of the path. */ if(*pptr==':') pptr++; /* * Return the unprocessed tail of the path-list string. */ *nextp = pptr; return 0; } /*....................................................................... * Read a username, stopping when a directory separator is seen, a colon * separator is seen, the end of the string is reached, or the username * buffer overflows. * * Input: * pc PathCache * The cache of filenames. * string char * The string who's prefix contains the name. * slen int The max number of characters to read from string[]. * literal int If true, treat backslashes as literal characters * instead of escapes. * Input/Output: * nextp char ** A pointer to the next unprocessed character * in string[] will be assigned to *nextp. * Output: * return int 0 - OK. The username can be found in pc->usrnam. * 1 - Error. A description of the error message * can be found in pc->err. */ static int pca_read_username(PathCache *pc, const char *string, int slen, int literal, const char **nextp) { int usrlen; /* The number of characters in pc->usrnam[] */ const char *sptr; /* A pointer into string[] */ int escaped = 0; /* True if the last character was a backslash */ /* * Extract the username. */ for(sptr=string,usrlen=0; usrlen < USR_LEN && (sptr-string) < slen; sptr++) { /* * Stop if the end of the string is reached, or a directory separator * or un-escaped colon separator is seen. */ if(!*sptr || strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN)==0 || (!escaped && *sptr == ':')) break; /* * Escape the next character? */ if(!literal && !escaped && *sptr == '\\') { escaped = 1; } else { escaped = 0; pc->usrnam[usrlen++] = *sptr; }; }; /* * Did the username overflow the buffer? */ if(usrlen >= USR_LEN) { _err_record_msg(pc->err, "Username too long", END_ERR_MSG); return 1; }; /* * Terminate the string. */ pc->usrnam[usrlen] = '\0'; /* * Indicate where processing of the input string should continue. */ *nextp = sptr; return 0; } /*....................................................................... * Create a new CacheMem object. * * Output: * return CacheMem * The new object, or NULL on error. */ static CacheMem *new_CacheMem(void) { CacheMem *cm; /* The object to be returned */ /* * Allocate the container. */ cm = (CacheMem *)malloc(sizeof(CacheMem)); if(!cm) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_CacheMem(). */ cm->sg = NULL; cm->files_dim = 0; cm->files = NULL; cm->nfiles = 0; /* * Allocate a list of string segments for storing filenames. */ cm->sg = _new_StringGroup(_pu_pathname_dim()); if(!cm->sg) return del_CacheMem(cm); /* * Allocate an array of pointers to filenames. * This will be extended later if needed. */ cm->files_dim = FILES_BLK_FACT; cm->files = (char **) malloc(sizeof(*cm->files) * cm->files_dim); if(!cm->files) { errno = ENOMEM; return del_CacheMem(cm); }; return cm; } /*....................................................................... * Delete a CacheMem object. * * Input: * cm CacheMem * The object to be deleted. * Output: * return CacheMem * The deleted object (always NULL). */ static CacheMem *del_CacheMem(CacheMem *cm) { if(cm) { /* * Delete the memory that was used to record filename strings. */ cm->sg = _del_StringGroup(cm->sg); /* * Delete the array of pointers to filenames. */ cm->files_dim = 0; if(cm->files) { free(cm->files); cm->files = NULL; }; /* * Delete the container. */ free(cm); }; return NULL; } /*....................................................................... * Re-initialize the memory used to allocate filename strings. * * Input: * cm CacheMem * The memory cache to be cleared. */ static void rst_CacheMem(CacheMem *cm) { _clr_StringGroup(cm->sg); cm->nfiles = 0; return; } /*....................................................................... * Append a new directory node to the list of directories read from the * path. * * Input: * pc PathCache * The filename cache. * dirname const char * The name of the new directory. * Output: * return int 0 - OK. * 1 - Error. */ static int add_PathNode(PathCache *pc, const char *dirname) { PathNode *node; /* The new directory list node */ int relative; /* True if dirname[] is a relative pathname */ /* * Have we been passed a relative pathname or an absolute pathname? */ relative = strncmp(dirname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) != 0; /* * If it's an absolute pathname, ignore it if the corresponding * directory doesn't exist. */ if(!relative && !_pu_path_is_dir(dirname)) return 0; /* * Allocate a new list node to record the specifics of the new directory. */ node = (PathNode *) _new_FreeListNode(pc->node_mem); if(!node) { _err_record_msg(pc->err, "Insufficient memory to cache new directory.", END_ERR_MSG); return 1; }; /* * Initialize the node. */ node->next = NULL; node->relative = relative; node->mem = relative ? pc->rel_mem : pc->abs_mem; node->dir = NULL; node->nfile = 0; node->files = NULL; /* * Make a copy of the directory pathname. */ node->dir = _sg_store_string(pc->abs_mem->sg, dirname, 0); if(!node->dir) { _err_record_msg(pc->err, "Insufficient memory to store directory name.", END_ERR_MSG); return 1; }; /* * Scan absolute directories for files of interest, recording their names * in node->mem->sg and appending pointers to these names to the * node->mem->files[] array. */ if(!node->relative) { int nfile = node->nfile = pca_scan_dir(pc, node->dir, node->mem); if(nfile < 1) { /* No files matched or an error occurred */ node = (PathNode *) _del_FreeListNode(pc->node_mem, node); return nfile < 0; }; }; /* * Append the new node to the list. */ if(pc->head) { pc->tail->next = node; pc->tail = node; } else { pc->head = pc->tail = node; }; return 0; } /*....................................................................... * Scan a given directory for files of interest, record their names * in mem->sg and append pointers to them to the mem->files[] array. * * Input: * pc PathCache * The filename cache. * dirname const char * The pathname of the directory to be scanned. * mem CacheMem * The memory in which to store filenames of * interest. * Output: * return int The number of files recorded, or -1 if a * memory error occurs. Note that the * inability to read the contents of the * directory is not counted as an error. */ static int pca_scan_dir(PathCache *pc, const char *dirname, CacheMem *mem) { int nfile = 0; /* The number of filenames recorded */ const char *filename; /* The name of the file being looked at */ /* * Attempt to open the directory. If the directory can't be read then * there are no accessible files of interest in the directory. */ if(_dr_open_dir(pc->dr, dirname, NULL)) return 0; /* * Record the names of all files in the directory in the cache. */ while((filename = _dr_next_file(pc->dr))) { char *copy; /* A copy of the filename */ /* * Make a temporary copy of the filename with an extra byte prepended. */ _pn_clear_path(pc->path); if(_pn_append_to_path(pc->path, " ", 1, 0) == NULL || _pn_append_to_path(pc->path, filename, -1, 1) == NULL) { _err_record_msg(pc->err, "Insufficient memory to record filename", END_ERR_MSG); return -1; }; /* * Store the filename. */ copy = _sg_store_string(mem->sg, pc->path->name, 0); if(!copy) { _err_record_msg(pc->err, "Insufficient memory to cache file name.", END_ERR_MSG); return -1; }; /* * Mark the filename as unchecked. */ copy[0] = PCA_F_ENIGMA; /* * Make room to store a pointer to the copy in mem->files[]. */ if(mem->nfiles + 1 > mem->files_dim) { int needed = mem->files_dim + FILES_BLK_FACT; char **files = (char **) realloc(mem->files, sizeof(*mem->files)*needed); if(!files) { _err_record_msg(pc->err, "Insufficient memory to extend filename cache.", END_ERR_MSG); return 1; }; mem->files = files; mem->files_dim = needed; }; /* * Record a pointer to the copy of the filename at the end of the files[] * array. */ mem->files[mem->nfiles++] = copy; /* * Keep a record of the number of files matched so far. */ nfile++; }; /* * Sort the list of files into lexical order. */ qsort(mem->files + mem->nfiles - nfile, nfile, sizeof(*mem->files), pca_cmp_matches); /* * Return the number of files recorded in mem->files[]. */ return nfile; } /*....................................................................... * A qsort() comparison function for comparing the cached filename * strings pointed to by two (char **) array elements. Note that * this ignores the initial cache-status byte of each filename. * * Input: * v1, v2 void * Pointers to the pointers of two strings to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int pca_cmp_matches(const void *v1, const void *v2) { const char **s1 = (const char **) v1; const char **s2 = (const char **) v2; return strcmp(*s1+1, *s2+1); } /*....................................................................... * Given the simple name of a file, search the cached list of files * in the order in which they where found in the list of directories * previously presented to pca_scan_path(), and return the pathname * of the first file which has this name. If a pathname to a file is * given instead of a simple filename, this is returned without being * looked up in the cache, but with any initial ~username expression * expanded, and optionally, unescaped backslashes removed. * * Input: * pc PathCache * The cached list of files. * name const char * The name of the file to lookup. * name_len int The length of the filename string at the * beginning of name[], or -1 to indicate that * the filename occupies the whole of the * string. * literal int If this argument is zero, lone backslashes * in name[] are ignored during comparison * with filenames in the cache, under the * assumption that they were in the input line * soley to escape the special significance of * characters like spaces. To have them treated * as normal characters, give this argument a * non-zero value, such as 1. * Output: * return char * The pathname of the first matching file, * or NULL if not found. Note that the returned * pointer points to memory owned by *pc, and * will become invalid on the next call to any * function in the PathCache module. */ char *pca_lookup_file(PathCache *pc, const char *name, int name_len, int literal) { PathNode *node; /* A node in the list of directories in the path */ char **match; /* A pointer to a matching filename string in the cache */ /* * Check the arguments. */ if(!pc || !name || name_len==0) return NULL; /* * If no length was specified, determine the length of the string to * be looked up. */ if(name_len < 0) name_len = strlen(name); /* * If the word starts with a ~username expression, the root directory, * of it contains any directory separators, then treat it isn't a simple * filename that can be looked up in the cache, but rather appears to * be the pathname of a file. If so, return a copy of this pathname with * escapes removed, if requested, and any initial ~username expression * expanded. */ if(cpa_cmd_contains_path(name, name_len)) { const char *nptr; if(pca_expand_tilde(pc, name, name_len, literal, &nptr) || _pn_append_to_path(pc->path, nptr, name_len - (nptr-name), !literal) == NULL) return NULL; return pc->path->name; }; /* * Look up the specified filename in each of the directories of the path, * in the same order that they were listed in the path, and stop as soon * as an instance of the file is found. */ for(node=pc->head; node; node=node->next) { /* * If the directory of the latest node is a relative pathname, * scan it for files of interest. */ if(node->relative) { rst_CacheMem(node->mem); if(pca_scan_dir(pc, node->dir, node->mem) < 1) continue; node->files = node->mem->files; node->nfile = node->mem->nfiles; }; /* * Copy the filename into a temporary buffer, while interpretting * escape characters if needed. */ _pn_clear_path(pc->path); if(_pn_append_to_path(pc->path, name, name_len, !literal) == NULL) return NULL; /* * Perform a binary search for the requested filename. */ match = (char **)bsearch(pc->path->name, node->files, node->nfile, sizeof(*node->files), pca_cmp_file); if(match) { /* * Prepend the pathname in which the directory was found, which we have * guaranteed to end in a directory separator, to the located filename. */ if(_pn_prepend_to_path(pc->path, node->dir, -1, 0) == NULL) return NULL; /* * Return the matching pathname unless it is rejected by the application. */ if(!pc->check_fn || (*match)[0] == PCA_F_WANTED || ((*match)[0]==PCA_F_ENIGMA && pc->check_fn(pc->data, pc->path->name))){ (*match)[0] = PCA_F_WANTED; return pc->path->name; } else { *(match)[0] = PCA_F_IGNORE; }; }; }; /* * File not found. */ return NULL; } /*....................................................................... * A qsort() comparison function for comparing a filename string to * a cached filename string pointed to by a (char **) array element. * This ignores the initial code byte at the start of the cached filename * string. * * Input: * v1, v2 void * Pointers to the pointers of two strings to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int pca_cmp_file(const void *v1, const void *v2) { const char *file_name = (const char *) v1; const char **cache_name = (const char **) v2; return strcmp(file_name, *cache_name + 1); } /*....................................................................... * The PcaPathConf structure may have options added to it in the future. * To allow your application to be linked against a shared version of the * tecla library, without these additions causing your application to * crash, you should use new_PcaPathConf() to allocate such structures. * This will set all of the configuration options to their default values, * which you can then change before passing the structure to * pca_path_completions(). * * Input: * pc PathCache * The filename cache in which to look for * file name completions. * Output: * return PcaPathConf * The new configuration structure, or NULL * on error. A descripition of the error * can be found by calling pca_last_error(pc). */ PcaPathConf *new_PcaPathConf(PathCache *pc) { PcaPathConf *ppc; /* The object to be returned */ /* * Check the arguments. */ if(!pc) return NULL; /* * Allocate the container. */ ppc = (PcaPathConf *)malloc(sizeof(PcaPathConf)); if(!ppc) { _err_record_msg(pc->err, "Insufficient memory.", END_ERR_MSG); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_PcaPathConf(). */ if(pca_init_PcaPathConf(ppc, pc)) return del_PcaPathConf(ppc); return ppc; } /*....................................................................... * Initialize a PcaPathConf configuration structure with defaults. * * Input: * ppc PcaPathConf * The structre to be initialized. * pc PathCache * The cache in which completions will be looked up. * Output: * return int 0 - OK. * 1 - Error. A description of the error can be * obtained by calling pca_last_error(pc). */ static int pca_init_PcaPathConf(PcaPathConf *ppc, PathCache *pc) { /* * Check the arguments. */ if(!pc) return 1; /* * Set the default options. */ ppc->id = PPC_ID_CODE; ppc->pc = pc; ppc->escaped = 1; ppc->file_start = -1; return 0; } /*....................................................................... * Delete a PcaPathConf object. * * Input: * ppc PcaPathConf * The object to be deleted. * Output: * return PcaPathConf * The deleted object (always NULL). */ PcaPathConf *del_PcaPathConf(PcaPathConf *ppc) { if(ppc) { ppc->pc = NULL; /* It is up to the caller to delete the cache */ /* * Delete the container. */ free(ppc); }; return NULL; } /*....................................................................... * pca_path_completions() is a completion callback function for use * directly with cpl_complete_word() or gl_customize_completions(), or * indirectly from your own completion callback function. It requires * that a CpaPathArgs object be passed via its 'void *data' argument. */ CPL_MATCH_FN(pca_path_completions) { PcaPathConf *ppc; /* The configuration arguments */ PathCache *pc; /* The cache in which to look for completions */ PathNode *node; /* A node in the list of directories in the path */ const char *filename; /* The name of the file being looked at */ const char *start_path; /* The pointer to the start of the pathname */ /* in line[]. */ int word_start; /* The index in line[] corresponding to start_path */ const char *prefix; /* The file-name prefix being searched for */ size_t prefix_len; /* The length of the prefix being completed */ int bot; /* The lowest index of the array not searched yet */ int top; /* The highest index of the array not searched yet */ /* * Check the arguments. */ if(!cpl) return 1; if(!line || word_end < 0 || !data) { cpl_record_error(cpl, "pca_path_completions: Invalid arguments."); return 1; }; /* * Get the configuration arguments. */ ppc = (PcaPathConf *) data; /* * Check that the callback data is a PcaPathConf structure returned * by new_PcaPathConf(). */ if(ppc->id != PPC_ID_CODE) { cpl_record_error(cpl, "Invalid callback data passed to pca_path_completions()"); return 1; }; /* * Get the filename cache. */ pc = ppc->pc; /* * Get the start of the file name. If not specified by the caller, * identify it by searching backwards in the input line for an * unescaped space or the start of the line. */ if(ppc->file_start < 0) { start_path = _pu_start_of_path(line, word_end); if(!start_path) { cpl_record_error(cpl, "Unable to find the start of the file name."); return 1; }; } else { start_path = line + ppc->file_start; }; /* * Get the index of the start of the word being completed. */ word_start = start_path - line; /* * Work out the length of the prefix that is bein completed. */ prefix_len = word_end - word_start; /* * If the word starts with a ~username expression or the root directory, * of it contains any directory separators, then completion must be * delegated to cpl_file_completions(). */ if(cpa_cmd_contains_path(start_path, prefix_len)) { cfc_file_start(pc->cfc, word_start); return cpl_file_completions(cpl, pc->cfc, line, word_end); }; /* * Look up the specified file name in each of the directories of the path, * in the same order that they were listed in the path, and stop as soon * as an instance of the file is found. */ for(node=pc->head; node; node=node->next) { /* * If the directory of the latest node is a relative pathname, * scan it for files of interest. */ if(node->relative) { rst_CacheMem(node->mem); if(pca_scan_dir(pc, node->dir, node->mem) < 1) continue; node->files = node->mem->files; node->nfile = node->mem->nfiles; }; /* * If needed, make a copy of the file-name being matched, with * escapes removed. Note that we need to do this anew every loop * iteration, because the above call to pca_scan_dir() uses * pc->path. */ prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped); if(!prefix) return 1; /* * The directory entries are sorted, so we can perform a binary * search for an instance of the prefix being searched for. */ bot = 0; top = node->nfile - 1; while(top >= bot) { int mid = (top + bot)/2; int test = strncmp(node->files[mid]+1, prefix, prefix_len); if(test > 0) top = mid - 1; else if(test < 0) bot = mid + 1; else { top = bot = mid; break; }; }; /* * If we found a match, look to see if any of its neigbors also match. */ if(top == bot) { while(--bot >= 0 && strncmp(node->files[bot]+1, prefix, prefix_len) == 0) ; while(++top < node->nfile && strncmp(node->files[top]+1, prefix, prefix_len) == 0) ; /* * We will have gone one too far in each direction. */ bot++; top--; /* * Add the completions to the list after checking them against the * callers requirements. */ for( ; bot<=top; bot++) { char *match = node->files[bot]; /* * Form the full pathname of the file. */ _pn_clear_path(pc->path); if(_pn_append_to_path(pc->path, node->dir, -1, 0) == NULL || _pn_append_to_path(pc->path, match+1, -1, 0) == NULL) { _err_record_msg(pc->err, "Insufficient memory to complete file name", END_ERR_MSG); return 1; }; /* * Should the file be included in the list of completions? */ if(!pc->check_fn || match[0] == PCA_F_WANTED || (match[0]==PCA_F_ENIGMA && pc->check_fn(pc->data, pc->path->name))) { match[0] = PCA_F_WANTED; /* * Copy the completion suffix into the work pathname pc->path->name, * adding backslash escapes if needed. */ if(pca_prepare_suffix(pc, match + 1 + prefix_len, ppc->escaped)) return 1; /* * Record the completion. */ if(cpl_add_completion(cpl, line, word_start, word_end, pc->path->name, "", " ")) return 1; /* * The file was rejected by the application. */ } else { match[0] = PCA_F_IGNORE; }; }; }; }; /* * We now need to search for subdirectories of the current directory which * have matching prefixes. First, if needed, make a copy of the word being * matched, with escapes removed. */ prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped); if(!prefix) return 1; /* * Now open the current directory. */ if(_dr_open_dir(pc->dr, FS_PWD, NULL)) return 0; /* * Scan the current directory for sub-directories whos names start with * the prefix that we are completing. */ while((filename = _dr_next_file(pc->dr))) { /* * Does the latest filename match the prefix, and is it a directory? */ if(strncmp(filename, prefix, prefix_len) == 0 && _pu_path_is_dir(filename)){ /* * Record the completion. */ if(pca_prepare_suffix(pc, filename + prefix_len, ppc->escaped) || cpl_add_completion(cpl, line, word_start, word_end, pc->path->name, FS_DIR_SEP, FS_DIR_SEP)) return 1; /* * The prefix in pc->path->name will have been overwritten by * pca_prepare_suffix(). Restore it here. */ prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped); if(!prefix) return 1; }; }; _dr_close_dir(pc->dr); return 0; } /*....................................................................... * Using the work buffer pc->path, make a suitably escaped copy of a * given completion suffix, ready to be passed to cpl_add_completion(). * * Input: * pc PathCache * The filename cache resource object. * suffix char * The suffix to be copied. * add_escapes int If true, escape special characters. * Output: * return int 0 - OK. * 1 - Error. */ static int pca_prepare_suffix(PathCache *pc, const char *suffix, int add_escapes) { const char *sptr; /* A pointer into suffix[] */ int nbsl; /* The number of backslashes to add to the suffix */ int i; /* * How long is the suffix? */ int suffix_len = strlen(suffix); /* * Clear the work buffer. */ _pn_clear_path(pc->path); /* * Count the number of backslashes that will have to be added to * escape spaces, tabs, backslashes and wildcard characters. */ nbsl = 0; if(add_escapes) { for(sptr = suffix; *sptr; sptr++) { switch(*sptr) { case ' ': case '\t': case '\\': case '*': case '?': case '[': nbsl++; break; }; }; }; /* * Arrange for the output path buffer to have sufficient room for the * both the suffix and any backslashes that have to be inserted. */ if(_pn_resize_path(pc->path, suffix_len + nbsl) == NULL) { _err_record_msg(pc->err, "Insufficient memory to complete file name", END_ERR_MSG); return 1; }; /* * If the suffix doesn't need any escapes, copy it directly into the * work buffer. */ if(nbsl==0) { strcpy(pc->path->name, suffix); } else { /* * Make a copy with special characters escaped? */ if(nbsl > 0) { const char *src = suffix; char *dst = pc->path->name; for(i=0; i= FS_ROOT_DIR_LEN && strncmp(prefix, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) return 1; /* * Search the prefix for directory separators, returning as soon as * any are found, since their presence indicates that the filename * starts with a pathname specification (valid or otherwise). */ for(i=0; i= FS_DIR_SEP_LEN && strncmp(prefix + i, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) return 1; }; /* * The file name doesn't appear to start with a pathname specification. */ return 0; } /*....................................................................... * If needed make a new copy of the prefix being matched, in pc->path->name, * but with escapes removed. If no escapes are to be removed, simply return * the original prefix string. * * Input: * pc PathCache * The cache being searched. * prefix const char * The prefix to be processed. * prefix_len size_t The length of the prefix. * escaped int If true, return a copy with escapes removed. * Output: * return const char * The prepared prefix, or NULL on error, in * which case an error message will have been * left in pc->err. */ static const char *pca_prepare_prefix(PathCache *pc, const char *prefix, size_t prefix_len, int escaped) { /* * Make a copy with escapes removed? */ if(escaped) { _pn_clear_path(pc->path); if(_pn_append_to_path(pc->path, prefix, prefix_len, 1) == NULL) { _err_record_msg(pc->err, "Insufficient memory to complete filename", END_ERR_MSG); return NULL; }; return pc->path->name; }; return prefix; } /*....................................................................... * If backslashes in the filename should be treated as literal * characters, call the following function with literal=1. Otherwise * the default is to treat them as escape characters, used for escaping * spaces etc.. * * Input: * ppc PcaPathConf * The pca_path_completions() configuration object * to be configured. * literal int Pass non-zero here to enable literal interpretation * of backslashes. Pass 0 to turn off literal * interpretation. */ void ppc_literal_escapes(PcaPathConf *ppc, int literal) { if(ppc) ppc->escaped = !literal; } /*....................................................................... * Call this function if you know where the index at which the * filename prefix starts in the input line. Otherwise by default, * or if you specify start_index to be -1, the filename is taken * to start after the first unescaped space preceding the cursor, * or the start of the line, which ever comes first. * * Input: * ppc PcaPathConf * The pca_path_completions() configuration object * to be configured. * start_index int The index of the start of the filename in * the input line, or -1 to select the default. */ void ppc_file_start(PcaPathConf *ppc, int start_index) { if(ppc) ppc->file_start = start_index; } /*....................................................................... * Expand any ~user expression found at the start of a path, leaving * either an empty string in pc->path if there is no ~user expression, * or the corresponding home directory. * * Input: * pc PathCache * The filename cache. * path const char * The path to expand. * pathlen int The max number of characters to look at in path[]. * literal int If true, treat backslashes as literal characters * instead of escapes. * Input/Output: * endp const char * A pointer to the next unprocessed character in * path[] will be assigned to *endp. * Output: * return int 0 - OK * 1 - Error (a description will have been placed * in pc->err). */ static int pca_expand_tilde(PathCache *pc, const char *path, int pathlen, int literal, const char **endp) { const char *pptr = path; /* A pointer into path[] */ const char *homedir=NULL; /* A home directory */ /* * Clear the pathname buffer. */ _pn_clear_path(pc->path); /* * If the first character is a tilde, then perform home-directory * interpolation. */ if(*pptr == '~') { /* * Skip the tilde character and attempt to read the username that follows * it, into pc->usrnam[]. */ if(pca_read_username(pc, ++pptr, pathlen-1, literal, &pptr)) return 1; /* * Attempt to lookup the home directory of the user. */ homedir = _hd_lookup_home_dir(pc->home, pc->usrnam); if(!homedir) { _err_record_msg(pc->err, _hd_last_home_dir_error(pc->home), END_ERR_MSG); return 1; }; /* * Append the home directory to the pathname string. */ if(_pn_append_to_path(pc->path, homedir, -1, 0) == NULL) { _err_record_msg(pc->err, "Insufficient memory for home directory expansion", END_ERR_MSG); return 1; }; }; /* * ~user and ~ are usually followed by a directory separator to * separate them from the file contained in the home directory. * If the home directory is the root directory, then we don't want * to follow the home directory by a directory separator, so we should * skip over it so that it doesn't get copied into the output pathname */ if(homedir && strcmp(homedir, FS_ROOT_DIR) == 0 && (pptr-path) + FS_DIR_SEP_LEN < pathlen && strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { pptr += FS_DIR_SEP_LEN; }; /* * Return a pointer to the next unprocessed character. */ *endp = pptr; return 0; } /*....................................................................... * Clear the filename status codes that are recorded before each filename * in the cache. * * Input: * pc PathCache * The filename cache. */ static void pca_remove_marks(PathCache *pc) { PathNode *node; /* A node in the list of directories in the path */ int i; /* * Traverse the absolute directories of the path, clearing the * filename status marks that precede each filename. */ for(node=pc->head; node; node=node->next) { if(!node->relative) { for(i=0; infile; i++) *node->files[i] = PCA_F_ENIGMA; }; }; return; } #endif /* ifndef WITHOUT_FILE_SYSTEM */ ./libtecla/stringrp.c0100644000076400007640000002113010042253672013143 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include "freelist.h" #include "stringrp.h" /* * StringSegment objects store lots of small strings in larger * character arrays. Since the total length of all of the strings can't * be known in advance, an extensible list of large character arrays, * called string-segments are used. */ typedef struct StringSegment StringSegment; struct StringSegment { StringSegment *next; /* A pointer to the next segment in the list */ char *block; /* An array of characters to be shared between strings */ int unused; /* The amount of unused space at the end of block[] */ }; /* * StringGroup is typedef'd in stringrp.h. */ struct StringGroup { FreeList *node_mem; /* The StringSegment free-list */ int block_size; /* The dimension of each character array block */ StringSegment *head; /* The list of character arrays */ }; /* * Specify how many StringSegment's to allocate at a time. */ #define STR_SEG_BLK 20 /*....................................................................... * Create a new StringGroup object. * * Input: * segment_size int The length of each of the large character * arrays in which multiple strings will be * stored. This sets the length of longest * string that can be stored, and for efficiency * should be at least 10 times as large as * the average string that will be stored. * Output: * return StringGroup * The new object, or NULL on error. */ StringGroup *_new_StringGroup(int segment_size) { StringGroup *sg; /* The object to be returned */ /* * Check the arguments. */ if(segment_size < 1) { errno = EINVAL; return NULL; }; /* * Allocate the container. */ sg = (StringGroup *) malloc(sizeof(StringGroup)); if(!sg) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_StringGroup(). */ sg->node_mem = NULL; sg->head = NULL; sg->block_size = segment_size; /* * Allocate the free list that is used to allocate list nodes. */ sg->node_mem = _new_FreeList(sizeof(StringSegment), STR_SEG_BLK); if(!sg->node_mem) return _del_StringGroup(sg); return sg; } /*....................................................................... * Delete a StringGroup object. * * Input: * sg StringGroup * The object to be deleted. * Output: * return StringGroup * The deleted object (always NULL). */ StringGroup *_del_StringGroup(StringGroup *sg) { if(sg) { StringSegment *node; /* * Delete the character arrays. */ for(node=sg->head; node; node=node->next) { if(node->block) free(node->block); node->block = NULL; }; /* * Delete the list nodes that contained the string segments. */ sg->node_mem = _del_FreeList(sg->node_mem, 1); sg->head = NULL; /* Already deleted by deleting sg->node_mem */ /* * Delete the container. */ free(sg); }; return NULL; } /*....................................................................... * Make a copy of a string in the specified string group, and return * a pointer to the copy. * * Input: * sg StringGroup * The group to store the string in. * string const char * The string to be recorded. * remove_escapes int If true, omit backslashes which escape * other characters when making the copy. * Output: * return char * The pointer to the copy of the string, * or NULL if there was insufficient memory. */ char *_sg_store_string(StringGroup *sg, const char *string, int remove_escapes) { char *copy; /* The recorded copy of string[] */ /* * Check the arguments. */ if(!sg || !string) return NULL; /* * Get memory for the string. */ copy = _sg_alloc_string(sg, strlen(string)); if(copy) { /* * If needed, remove backslash escapes while copying the input string * into the cache string. */ if(remove_escapes) { int escaped = 0; /* True if the next character should be */ /* escaped. */ const char *src = string; /* A pointer into the input string */ char *dst = copy; /* A pointer into the cached copy of the */ /* string. */ while(*src) { if(!escaped && *src == '\\') { escaped = 1; src++; } else { escaped = 0; *dst++ = *src++; }; }; *dst = '\0'; /* * If escapes have already been removed, copy the input string directly * into the cache. */ } else { strcpy(copy, string); }; }; /* * Return a pointer to the copy of the string (or NULL if the allocation * failed). */ return copy; } /*....................................................................... * Allocate memory for a string of a given length. * * Input: * sg StringGroup * The group to store the string in. * length int The required length of the string. * Output: * return char * The pointer to the copy of the string, * or NULL if there was insufficient memory. */ char *_sg_alloc_string(StringGroup *sg, int length) { StringSegment *node; /* A node of the list of string segments */ char *copy; /* The allocated string */ /* * If the string is longer than block_size, then we can't record it. */ if(length > sg->block_size || length < 0) return NULL; /* * See if there is room to record the string in one of the existing * string segments. Do this by advancing the node pointer until we find * a node with length+1 bytes unused, or we get to the end of the list. */ for(node=sg->head; node && node->unused <= length; node=node->next) ; /* * If there wasn't room, allocate a new string segment. */ if(!node) { node = (StringSegment *) _new_FreeListNode(sg->node_mem); if(!node) return NULL; /* * Initialize the segment. */ node->next = NULL; node->block = NULL; node->unused = sg->block_size; /* * Attempt to allocate the string segment character array. */ node->block = (char *) malloc(sg->block_size); if(!node->block) return NULL; /* * Prepend the node to the list. */ node->next = sg->head; sg->head = node; }; /* * Get memory for the string. */ copy = node->block + sg->block_size - node->unused; node->unused -= length + 1; /* * Return a pointer to the string memory. */ return copy; } /*....................................................................... * Delete all of the strings that are currently stored by a specified * StringGroup object. * * Input: * sg StringGroup * The group of strings to clear. */ void _clr_StringGroup(StringGroup *sg) { StringSegment *node; /* A node in the list of string segments */ /* * Mark all of the string segments as unoccupied. */ for(node=sg->head; node; node=node->next) node->unused = sg->block_size; return; } ./libtecla/stringrp.h0100644000076400007640000000673610027466660013175 0ustar mcsmcs#ifndef stringrp_h #define stringrp_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * StringGroup objects provide memory for modules that need to * allocate lots of small strings without needing to free any of them * individually, but rather is happy to free them all at the same * time. Taking advantage of these properties, StringGroup objects * avoid the heap fragmentation that tends to occur when lots of small * strings are allocated directly from the heap and later free'd. They * do this by allocating a list of large character arrays in each of * which multiple strings are stored. Thus instead of allocating lots * of small strings, a few large character arrays are allocated. When * the strings are free'd on mass, this list of character arrays is * maintained, ready for subsequent use in recording another set of * strings. */ typedef struct StringGroup StringGroup; /* * The following constructor allocates a string-allocation object. * The segment_size argument specifies how long each string segment * array should be. This should be at least 10 times the length of * the average string to be recorded in the string group, and * sets the length of the longest string that can be stored. */ StringGroup *_new_StringGroup(int segment_size); /* * Delete all of the strings that are currently stored by a specified * StringGroup object. */ void _clr_StringGroup(StringGroup *sg); /* * Make a copy of the specified string, returning a pointer to * the copy, or NULL if there was insufficient memory. If the * remove_escapes argument is non-zero, backslashes that escape * other characters will be removed. */ char *_sg_store_string(StringGroup *sg, const char *string, int remove_escapes); /* * Allocate memory for a string of a given length. */ char *_sg_alloc_string(StringGroup *sg, int length); /* * Delete a StringGroup object (and all of the strings that it * contains). */ StringGroup *_del_StringGroup(StringGroup *sg); #endif ./libtecla/strngmem.c0100644000076400007640000001525210027466660013145 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include "strngmem.h" #include "freelist.h" struct StringMem { unsigned long nmalloc; /* The number of strings allocated with malloc */ FreeList *fl; /* The free-list */ }; /*....................................................................... * Create a string free-list container and the first block of its free-list. * * Input: * blocking_factor int The blocking_factor argument specifies how * many strings of length SM_STRLEN * bytes (see stringmem.h) are allocated in each * free-list block. * For example if blocking_factor=64 and * SM_STRLEN=16, then each new * free-list block will take 1K of memory. * Output: * return StringMem * The new free-list container, or NULL on * error. */ StringMem *_new_StringMem(unsigned blocking_factor) { StringMem *sm; /* The container to be returned. */ /* * Check arguments. */ if(blocking_factor < 1) { errno = EINVAL; return NULL; }; /* * Allocate the container. */ sm = (StringMem *) malloc(sizeof(StringMem)); if(!sm) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize * the container at least up to the point at which it can safely * be passed to _del_StringMem(). */ sm->nmalloc = 0; sm->fl = NULL; /* * Allocate the free-list. */ sm->fl = _new_FreeList(SM_STRLEN, blocking_factor); if(!sm->fl) return _del_StringMem(sm, 1); /* * Return the free-list container. */ return sm; } /*....................................................................... * Delete a string free-list. * * Input: * sm StringMem * The string free-list to be deleted, or NULL. * force int If force==0 then _del_StringMem() will complain * and refuse to delete the free-list if any * of nodes have not been returned to the free-list. * If force!=0 then _del_StringMem() will not check * whether any nodes are still in use and will * always delete the list. * Output: * return StringMem * Always NULL (even if the list couldn't be * deleted). */ StringMem *_del_StringMem(StringMem *sm, int force) { if(sm) { /* * Check whether any strings have not been returned to the free-list. */ if(!force && (sm->nmalloc > 0 || _busy_FreeListNodes(sm->fl) > 0)) { errno = EBUSY; return NULL; }; /* * Delete the free-list. */ sm->fl = _del_FreeList(sm->fl, force); /* * Delete the container. */ free(sm); }; return NULL; } /*....................................................................... * Allocate an array of 'length' chars. * * Input: * sm StringMem * The string free-list to allocate from. * length size_t The length of the new string (including '\0'). * Output: * return char * The new string or NULL on error. */ char *_new_StringMemString(StringMem *sm, size_t length) { char *string; /* The string to be returned */ int was_malloc; /* True if malloc was used to allocate the string */ /* * Check arguments. */ if(!sm) return NULL; if(length < 1) length = 1; /* * Allocate the new node from the free list if possible. */ if(length < SM_STRLEN) { string = (char *)_new_FreeListNode(sm->fl); if(!string) return NULL; was_malloc = 0; } else { string = (char *) malloc(length+1); /* Leave room for the flag byte */ if(!string) return NULL; /* * Count malloc allocations. */ was_malloc = 1; sm->nmalloc++; }; /* * Use the first byte of the string to record whether the string was * allocated with malloc or from the free-list. Then return the rest * of the string for use by the user. */ string[0] = (char) was_malloc; return string + 1; } /*....................................................................... * Free a string that was previously returned by _new_StringMemString(). * * Input: * sm StringMem * The free-list from which the string was originally * allocated. * s char * The string to be returned to the free-list, or NULL. * Output: * return char * Always NULL. */ char *_del_StringMemString(StringMem *sm, char *s) { int was_malloc; /* True if the string originally came from malloc() */ /* * Is there anything to be deleted? */ if(s && sm) { /* * Retrieve the true string pointer. This is one less than the one * returned by _new_StringMemString() because the first byte of the * allocated memory is reserved by _new_StringMemString as a flag byte * to say whether the memory was allocated from the free-list or directly * from malloc(). */ s--; /* * Get the origination flag. */ was_malloc = s[0]; if(was_malloc) { free(s); s = NULL; sm->nmalloc--; } else { s = (char *) _del_FreeListNode(sm->fl, s); }; }; return NULL; } ./libtecla/strngmem.h0100644000076400007640000000626710027466660013160 0ustar mcsmcs#ifndef stringmem_h #define stringmem_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ typedef struct StringMem StringMem; /* * Applications that dynamically allocate lots of small strings * run the risk of significantly fragmenting the heap. This module * aims to reduce this risk by allocating large arrays of small fixed * length strings, arranging them as a free-list and allowing * callers to allocate from the list. Strings that are too long * to be allocated from the free-list are allocated from the heap. * Since typical implementations of malloc() eat up a minimum of * 16 bytes per call to malloc() [because of alignment and space * management constraints] it makes sense to set the free-list * string size to 16 bytes. Note that unlike malloc() which typically * keeps 8 bytes per allocation for its own use, our allocator will * return all but one of the 16 bytes for use. One hidden byte of overhead * is reserved for flagging whether the string was allocated directly * from malloc or from the free-list. */ /* * Set the length of each free-list string. The longest string that * will be returned without calling malloc() will be one less than * this number. */ #define SM_STRLEN 16 /* * Create a string free-list container and the first block of its free-list. */ StringMem *_new_StringMem(unsigned blocking_factor); /* * Delete a string free-list. */ StringMem *_del_StringMem(StringMem *sm, int force); /* * Allocate an array of 'length' chars. */ char *_new_StringMemString(StringMem *sm, size_t size); /* * Free a string that was previously returned by _new_StringMemString(). */ char *_del_StringMemString(StringMem *sm, char *s); #endif ./libtecla/update_html0100744000076400007640000000217207604750056013376 0ustar mcsmcs#!/bin/sh # Convert man pages to html files. for dir in man/prog man/libr man/func man/misc man/file; do for template in $dir/*.in;do page=`basename "$template" .in` if [ `wc -l < $template` -gt 1 ]; then html="html/$page.html" man2html $template > $html for ref in libtecla cpl_complete_word ef_expand_file gl_get_line pca_lookup_file enhance gl_io_mode tecla; do link="$ref.html" ed -s $html << EOF %s|$ref[(][^)][^) ]*[)]|$ref|g w q EOF done fi done done # Convert the change log into a web page. cd html echo 'The tecla library change log' > changes.html echo '
' >> changes.html
sed 's/&/&/g; s//\>/g' ../CHANGES >> changes.html
echo '
' >> changes.html # Do the same to the release-notes file. cd ../html echo 'The tecla library release notes' > release.html echo '
' >> release.html
sed 's/&/&/g; s/> release.html
echo '
' >> release.html ./libtecla/Makefile.in0100644000076400007640000002116610141245601013176 0ustar mcsmcs#----------------------------------------------------------------------- # This is the template that the libtecla configure script uses to create # the libtecla Makefile. It does this by replacing all instances of # @name@ with the value of the correspondingly named configuration # variable. You should find another file in the same directory as this # one, called "configure.in". The latter file contains extensive comments # explaining how this all works. #----------------------------------------------------------------------- # Where is the source code? srcdir = @srcdir@ # Where do you want to install the library, its header file, and the man pages? prefix=@prefix@ exec_prefix=@exec_prefix@ LIBDIR=@libdir@ INCDIR=@includedir@ MANDIR=@mandir@ BINDIR=@bindir@ # Which C compiler do you want to use? CC = @CC@ # If 'make' doesn't define the MAKE variable, define it here. @SET_MAKE@ # To use RANLIB set the RANLIB variable to ranlib. Otherwise set it to # :, which is the bourne shell do-nothing command. RANLIB = @RANLIB@ # Optional flags to pass to the linker. LDFLAGS = @LDFLAGS@ # Optional C pre-processor flags. CPPFLAGS = @CPPFLAGS@ # The following optional defines change the characteristics of the library. # # USE_TERMINFO # Use the terminfo terminal information database when looking up # terminal characteristics. Most modern UNIX and UNIX-like operating # systems support terminfo, so this define should normally be included. # If in doubt leave it in, and see if the library compiles. # USE_TERMCAP # If you don't have terminfo but do have the termcap database, replace # the -DUSE_TERMINFO with -DUSE_TERMCAP. If there is a termcap.h in # /usr/include/, also add -DHAVE_TERMCAP_H. # # If neither USE_TERMINFO nor USE_TERMCAP are included, ANSI VT100 control # sequences will be used to control all terminal types. # # For Solaris and Linux, use: # # DEFINES = -DUSE_TERMINFO # DEFINES = @DEFS@ # # The following defines are used in addition to the above when compiling # the reentrant version of the library. Note that the definition of # _POSIX_C_SOURCE to request reentrant functions, has the unfortunate # side-effect on some systems of stopping the TIOCGWINSZ ioctl macro from # getting defined. This in turn stops the library from being # able to respond to terminal size changes. Under Solaris this can be # remedied by adding -D__EXTENSIONS__. On linux this isn't necessary. # If you don't get this right, the library will still work, but # it will get confused if the terminal size gets changed and you try to # edit a line that exceeds the terminal width. # # Thus on Solaris you should use: # # DEFINES_R = -D_POSIX_C_SOURCE=199506L -D__EXTENSIONS__ # # and on linux you should use: # # DEFINES_R = -D_POSIX_C_SOURCE=199506L # DEFINES_R = @DEFS_R@ # # The compiler optimization flags. I like to keep this separate so # that I can set it to -g from the 'make' command line without having # to edit this file when debugging the library. If you aren't working # on modifying the library, leave this set to -O. # OPT = -O # # These are paranoid gcc warning flags to use when compiling new code. # Simply invoke make with WARNING_FLAGS='$(PEDANTIC_FLAGS)'. # PEDANTIC_FLAGS=-Wall -Wshadow -Wpointer-arith -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls # # Specify any extra compiler warning options that you want to use. # Leave this blank unless you are porting the library to a new system, # or modifying the library. # WARNING_FLAGS= # # If you want to compile the demo program, specify any system # libraries that are needed for the terminal I/O functions. # # If you are using terminfo, you will probably only need -lcurses. # For termcap you may need -ltermcap or -ltermlib. # # For Solaris, use: # # LIBS = -lcurses # # For linux, use: # # LIBS = -lncurses # LIBS = @LIBS@ # # List the default target libraries. This should be one or # both of the words "normal" and "reentrant". # TARGETS = @TARGETS@ # # List which types of the above libraries are required. # This should be one or both of the words "static" and "shared". # TARGET_LIBS = @TARGET_LIBS@ # # If you want the demo programs to be built, the following variable # should be assigned the single word: demos. If it isn't assigned # anything, the demo programs won't be built. # DEMOS = demos # # List the programs that are to be made by default. # PROGRAMS = enhance # # List programs for which reentrant versions are to be built by default. # PROGRAMS_R = #----------------------------------------------------------------------- # You shouldn't need to change anything below this line. #----------------------------------------------------------------------- CFLAGS = $(OPT) $(WARNING_FLAGS) $(DEFINES) @CFLAGS@ @SHARED_CFLAGS@ default: $(TARGETS) normal: @$(MAKE) -f $(srcdir)/Makefile.rules TARGETS="$(TARGET_LIBS)" SUFFIX="" CFLAGS="$(CFLAGS)" CC="$(CC)" OBJDIR=normal_obj LINK_SHARED='@LINK_SHARED@' SHARED_EXT='@SHARED_EXT@' SHARED_ALT='@SHARED_ALT@' LIBS='$(LIBS)' srcdir='$(srcdir)' LIBDIR='$(LIBDIR)' LN_S='@LN_S@' DEMOS="$(DEMOS)" PROGRAMS='$(PROGRAMS)' RANLIB='$(RANLIB)' LDFLAGS=$(LDFLAGS) CPPFLAGS=$(CPPFLAGS) reentrant: @$(MAKE) -f $(srcdir)/Makefile.rules TARGETS="$(TARGET_LIBS)" SUFFIX="_r" CFLAGS="$(CFLAGS) $(DEFINES_R)" CC="$(CC)" OBJDIR=reentrant_obj LINK_SHARED='@LINK_SHARED@' SHARED_EXT='@SHARED_EXT@' SHARED_ALT='@SHARED_ALT@' LIBS='$(LIBS)' srcdir='$(srcdir)' LIBDIR='$(LIBDIR)' LN_S='@LN_S@' DEMOS="$(DEMOS)" PROGRAMS='$(PROGRAMS_R)' RANLIB='$(RANLIB)' LDFLAGS=$(LDFLAGS) CPPFLAGS=$(CPPFLAGS) demos: normal demos_r: reentrant clean: rm -rf *.o normal_obj reentrant_obj libtecla*.a demo demo[0-9] demo_r demo[0-9]_r enhance *~ man/*~ man/*/*~ html/*~ compile_reentrant compile_normal `/bin/ls -1 man/*/*.in | sed 's/\.in$$//'` @endings="@SHARED_EXT@ @SHARED_ALT@" ; \ for alt in $$endings ; do \ lib="libtecla*$$alt" ; \ rm -f $$lib; echo rm -f $$lib ; \ done distclean: clean rm -rf config.cache config.status config.log Makefile libtecla.map.opt \ autom*.cache cp $(srcdir)/Makefile.stub Makefile install_lib: $(TARGETS) $(LIBDIR) @for lib in libtecla.a libtecla_r.a ; do \ if [ -f $$lib ] ; then \ cp $$lib $(LIBDIR)/ ; chmod ugo+r $(LIBDIR)/$$lib; \ echo "cp $$lib $(LIBDIR)/ ; chmod ugo+r $(LIBDIR)/$$lib"; \ fi ; \ done @for lib in libtecla libtecla_r ; do \ src="$$lib@SHARED_EXT@"; \ if [ -f $$src ] ; then \ dst="$(LIBDIR)/$$src"; \ cp -f $$src $$dst; chmod a=rX $$dst; \ echo "cp -f $$src $$dst ; chmod a=rX $$dst"; \ endings="@SHARED_ALT@" ; \ for alt in $$endings ; do \ lnk="$$lib$$alt"; \ (cd $(LIBDIR); rm -f $$lnk; @LN_S@ $$src $$lnk); \ echo "(cd $(LIBDIR); rm -f $$lnk; @LN_S@ $$src $$lnk)"; \ done ; \ fi ; \ done install_inc: $(INCDIR) @if [ -f $(srcdir)/libtecla.h ]; then \ cp $(srcdir)/libtecla.h $(INCDIR)/ ; chmod ugo+r $(INCDIR)/libtecla.h; \ echo "cp $(srcdir)/libtecla.h $(INCDIR)/ ; chmod ugo+r $(INCDIR)/libtecla.h"; \ fi install_man: $(MANDIR) libr_man func_man prog_man misc_man file_man libr_man: if test "@MAKE_MAN_PAGES@"_ = "yes"_; then \ for template in man/libr/*.in; do \ page=`basename "$$template" .in`; \ $(srcdir)/install-sh -c -m 644 man/libr/$$page ${MANDIR}/@LIBR_MANDIR@/$$page.@LIBR_MANEXT@; \ done ; \ fi func_man: if test "@MAKE_MAN_PAGES@"_ = "yes"_; then \ for template in man/func/*.in; do \ page=`basename "$$template" .in`; \ $(srcdir)/install-sh -c -m 644 man/func/$$page ${MANDIR}/@FUNC_MANDIR@/$$page.@FUNC_MANEXT@; \ done ; \ fi prog_man: if test "@MAKE_MAN_PAGES@"_ = "yes"_; then \ for template in man/prog/*.in; do \ page=`basename "$$template" .in`; \ $(srcdir)/install-sh -c -m 644 man/prog/$$page ${MANDIR}/@PROG_MANDIR@/$$page.@PROG_MANEXT@; \ done ; \ fi misc_man: if test "@MAKE_MAN_PAGES@"_ = "yes"_; then \ for template in man/misc/*.in; do \ page=`basename "$$template" .in`; \ $(srcdir)/install-sh -c -m 644 man/misc/$$page ${MANDIR}/@MISC_MANDIR@/$$page.@MISC_MANEXT@; \ done ; \ fi file_man: if test "@MAKE_MAN_PAGES@"_ = "yes"_; then \ for template in man/file/*.in; do \ page=`basename "$$template" .in`; \ $(srcdir)/install-sh -c -m 644 man/file/$$page ${MANDIR}/@FILE_MANDIR@/$$page.@FILE_MANEXT@; \ done ; \ fi install_bin: $(BINDIR) $(PROGRAMS) $(PROGRAMS_R) progs="$(PROGRAMS) $(PROGRAMS_R)"; \ for prog in $$progs; do \ $(srcdir)/install-sh -c -m 755 -s $$prog $(BINDIR)/; \ done install: install_lib install_inc install_man install_bin # Make any missing installation directories. $(MANDIR) $(LIBDIR) $(INCDIR) $(BINDIR): $(srcdir)/install-sh -d $@ chmod ugo+rx $@ ./libtecla/PORTING0100644000076400007640000000315407255055554012215 0ustar mcsmcsThe Tecla library was written with portability in mind, so no modifications to the source code should be needed on UNIX or LINUX platforms. The default compilation and linking arrangements should also work unchanged on these systems, but if no specific configuration has been provided for your system, shared libraries won't be compiled. Configuring these requires modifications to be made to the file: configure.in This file is heavily commented (comments start with the word dnl) and is relatively simple, so the instructions and suggestions that you find in this file should be sufficient to help you figure out how to add a configuration for your system. This file is an input file to the GNU autoconf program, which uses it as a template for generating the distributed configure script. If autoconf is installed on your system, creating a new configure script is a simple matter of typing. autoconf To avoid confusion with the leftovers of the previous configuration, you should then do the following: rm -f config.cache ./configure make clean ./configure make The first ./configure creates a new makefile for your system, allowing you to type 'make clean' to discard any files that were compiled with the previous configuration. Since 'make clean' also deletes the new makefile, a second invokation of the configure script is then performed to re-create the makefile. The final make then creates the tecla library from scratch. Once you have confirmed that the new configuration works, please send the modified "configure.in" template to mcs@astro.caltech.edu, so that your changes can be included in subsequent releases. ./libtecla/config.guess0100644000076400007640000012214110013516065013444 0ustar mcsmcs#! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003 Free Software Foundation, Inc. timestamp='2003-10-03' # This file 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 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit 0 ;; amiga:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; arc:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; hp300:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mac68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; macppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvmeppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; pmax:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sgi:OpenBSD:*:*) echo mipseb-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sun3:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; wgrisc:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:OpenBSD:*:*) echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} exit 0 ;; alpha:OSF1:*:*) if test $UNAME_RELEASE = "V4.0"; then UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` fi # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit 0 ;; Alpha*:OpenVMS:*:*) echo alpha-hp-vms exit 0 ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit 0 ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit 0 ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit 0;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit 0 ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit 0 ;; *:OS/390:*:*) echo i370-ibm-openedition exit 0 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit 0;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit 0;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit 0 ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit 0 ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit 0 ;; DRS?6000:UNIX_SV:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7 && exit 0 ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit 0 ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit 0 ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit 0 ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit 0 ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit 0 ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit 0 ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit 0 ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit 0 ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit 0 ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit 0 ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit 0 ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit 0 ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c \ && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ && exit 0 echo mips-mips-riscos${UNAME_RELEASE} exit 0 ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit 0 ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit 0 ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit 0 ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit 0 ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit 0 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit 0 ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit 0 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit 0 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit 0 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit 0 ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit 0 ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit 0 ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo rs6000-ibm-aix3.2.5 elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit 0 ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:*:*) echo rs6000-ibm-aix exit 0 ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit 0 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit 0 ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit 0 ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit 0 ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit 0 ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit 0 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then # avoid double evaluation of $set_cc_for_build test -n "$CC_FOR_BUILD" || eval $set_cc_for_build if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit 0 ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit 0 ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo unknown-hitachi-hiuxwe2 exit 0 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit 0 ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit 0 ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit 0 ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit 0 ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit 0 ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit 0 ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit 0 ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit 0 ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit 0 ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit 0 ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit 0 ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; *:UNICOS/mp:*:*) echo nv1-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit 0 ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:FreeBSD:*:*|*:GNU/FreeBSD:*:*) # Determine whether the default compiler uses glibc. eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #if __GLIBC__ >= 2 LIBC=gnu #else LIBC= #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` # GNU/FreeBSD systems have a "k" prefix to indicate we are using # FreeBSD's kernel, but not the complete OS. case ${LIBC} in gnu) kernel_only='k' ;; esac echo ${UNAME_MACHINE}-unknown-${kernel_only}freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} exit 0 ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit 0 ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit 0 ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit 0 ;; x86:Interix*:[34]*) echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' exit 0 ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit 0 ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit 0 ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit 0 ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit 0 ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; *:GNU:*:*) echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit 0 ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit 0 ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit 0 ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit 0 ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit 0 ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit 0 ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit 0 ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit 0 ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit 0 ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit 0 ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit 0 ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit 0 ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit 0 ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #ifdef __INTEL_COMPILER LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit 0 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit 0 ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit 0 ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit 0 ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit 0 ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit 0 ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit 0 ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit 0 ;; i*86:*:5:[78]*) case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit 0 ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit 0 ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit 0 ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit 0 ;; paragon:*:*:*) echo i860-intel-osf1 exit 0 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit 0 ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit 0 ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit 0 ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit 0 ;; M68*:*:R3V[567]*:*) test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4.3${OS_REL} && exit 0 /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4 && exit 0 ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit 0 ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit 0 ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit 0 ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit 0 ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit 0 ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit 0 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit 0 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit 0 ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit 0 ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit 0 ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit 0 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit 0 ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit 0 ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit 0 ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit 0 ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit 0 ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit 0 ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit 0 ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Darwin:*:*) case `uname -p` in *86) UNAME_PROCESSOR=i686 ;; powerpc) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit 0 ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit 0 ;; *:QNX:*:4*) echo i386-pc-qnx exit 0 ;; NSR-[DGKLNPTVWY]:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit 0 ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit 0 ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit 0 ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit 0 ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit 0 ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit 0 ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit 0 ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit 0 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit 0 ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit 0 ;; *:ITS:*:*) echo pdp10-unknown-its exit 0 ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit 0 ;; esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit 0 ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; c34*) echo c34-convex-bsd exit 0 ;; c38*) echo c38-convex-bsd exit 0 ;; c4*) echo c4-convex-bsd exit 0 ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: ./libtecla/config.sub0100644000076400007640000007340610013516062013115 0ustar mcsmcs#! /bin/sh # Configuration validation subroutine script. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003 Free Software Foundation, Inc. timestamp='2003-08-18' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software # can handle that machine. It does not imply ALL GNU software can. # # This file 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 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS $0 [OPTION] ALIAS Canonicalize a configuration name. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" exit 1 ;; *local*) # First pass through any local machine types. echo $1 exit 0;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-dietlibc | kfreebsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] then os=`echo $1 | sed 's/.*-/-/'` else os=; fi ;; esac ### Let's recognize common machines as not being operating systems so ### that things like config.sub decstation-3100 work. We also ### recognize some manufacturers as not being operating systems, so we ### can provide default operating systems below. case $os in -sun*os*) # Prevent following clause from handling this invalid input. ;; -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ -apple | -axis) os= basic_machine=$1 ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; -scout) ;; -wrs) os=-vxworks basic_machine=$1 ;; -chorusos*) os=-chorusos basic_machine=$1 ;; -chorusrdb) os=-chorusrdb basic_machine=$1 ;; -hiux*) os=-hiuxwe2 ;; -sco5) os=-sco3.2v5 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco4) os=-sco3.2v4 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2v[4-9]*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco*) os=-sco3.2v2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -udk*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -isc) os=-isc2.2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -clix*) basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` ;; -windowsnt*) os=`echo $os | sed -e 's/windowsnt/winnt/'` ;; -psos*) os=-psos ;; -mint | -mint[0-9]*) basic_machine=m68k-atari os=-mint ;; esac # Decode aliases for certain CPU-COMPANY combinations. case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ | c4x | clipper \ | d10v | d30v | dlx | dsp16xx \ | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ | m32r | m68000 | m68k | m88k | mcore \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64vr | mips64vrel \ | mips64orion | mips64orionel \ | mips64vr4100 | mips64vr4100el \ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | msp430 \ | ns16k | ns32k \ | openrisc | or32 \ | pdp10 | pdp11 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ | pyramid \ | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv9 | sparcv9b \ | strongarm \ | tahoe | thumb | tic4x | tic80 | tron \ | v850 | v850e \ | we32k \ | x86 | xscale | xstormy16 | xtensa \ | z8k) basic_machine=$basic_machine-unknown ;; m6811 | m68hc11 | m6812 | m68hc12) # Motorola 68HC11/12. basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) basic_machine=$basic_machine-pc ;; # Object if more than one company name word. *-*-*) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* \ | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ | clipper-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | elxsi-* \ | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ | m32r-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ | m88110-* | m88k-* | mcore-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64vr-* | mips64vrel-* \ | mips64orion-* | mips64orionel-* \ | mips64vr4100-* | mips64vr4100el-* \ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ | mipstx39-* | mipstx39el-* \ | msp430-* \ | none-* | np1-* | nv1-* | ns16k-* | ns32k-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ | pyramid-* \ | romp-* | rs6000-* \ | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \ | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ | tahoe-* | thumb-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ | tron-* \ | v850-* | v850e-* | vax-* \ | we32k-* \ | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \ | xtensa-* \ | ymp-* \ | z8k-*) ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 386bsd) basic_machine=i386-unknown os=-bsd ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; a29khif) basic_machine=a29k-amd os=-udi ;; adobe68k) basic_machine=m68010-adobe os=-scout ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; am29k) basic_machine=a29k-none os=-bsd ;; amd64) basic_machine=x86_64-pc ;; amdahl) basic_machine=580-amdahl os=-sysv ;; amiga | amiga-*) basic_machine=m68k-unknown ;; amigaos | amigados) basic_machine=m68k-unknown os=-amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=-sysv4 ;; apollo68) basic_machine=m68k-apollo os=-sysv ;; apollo68bsd) basic_machine=m68k-apollo os=-bsd ;; aux) basic_machine=m68k-apple os=-aux ;; balance) basic_machine=ns32k-sequent os=-dynix ;; c90) basic_machine=c90-cray os=-unicos ;; convex-c1) basic_machine=c1-convex os=-bsd ;; convex-c2) basic_machine=c2-convex os=-bsd ;; convex-c32) basic_machine=c32-convex os=-bsd ;; convex-c34) basic_machine=c34-convex os=-bsd ;; convex-c38) basic_machine=c38-convex os=-bsd ;; cray | j90) basic_machine=j90-cray os=-unicos ;; crds | unos) basic_machine=m68k-crds ;; cris | cris-* | etrax*) basic_machine=cris-axis ;; da30 | da30-*) basic_machine=m68k-da30 ;; decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) basic_machine=mips-dec ;; decsystem10* | dec10*) basic_machine=pdp10-dec os=-tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec os=-tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; delta88) basic_machine=m88k-motorola os=-sysv3 ;; dpx20 | dpx20-*) basic_machine=rs6000-bull os=-bosx ;; dpx2* | dpx2*-bull) basic_machine=m68k-bull os=-sysv3 ;; ebmon29k) basic_machine=a29k-amd os=-ebmon ;; elxsi) basic_machine=elxsi-elxsi os=-bsd ;; encore | umax | mmax) basic_machine=ns32k-encore ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=-ose ;; fx2800) basic_machine=i860-alliant ;; genix) basic_machine=ns32k-ns ;; gmicro) basic_machine=tron-gmicro os=-sysv ;; go32) basic_machine=i386-pc os=-go32 ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; h8300hms) basic_machine=h8300-hitachi os=-hms ;; h8300xray) basic_machine=h8300-hitachi os=-xray ;; h8500hms) basic_machine=h8500-hitachi os=-hms ;; harris) basic_machine=m88k-harris os=-sysv3 ;; hp300-*) basic_machine=m68k-hp ;; hp300bsd) basic_machine=m68k-hp os=-bsd ;; hp300hpux) basic_machine=m68k-hp os=-hpux ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) basic_machine=m68000-hp ;; hp9k3[2-9][0-9]) basic_machine=m68k-hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) basic_machine=hppa1.1-hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) basic_machine=hppa1.1-hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; hppa-next) os=-nextstep3 ;; hppaosf) basic_machine=hppa1.1-hp os=-osf ;; hppro) basic_machine=hppa1.1-hp os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; # I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv4 ;; i*86v) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv ;; i*86sol2) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-solaris2 ;; i386mach) basic_machine=i386-mach os=-mach ;; i386-vsta | vsta) basic_machine=i386-unknown os=-vsta ;; iris | iris4d) basic_machine=mips-sgi case $os in -irix*) ;; *) os=-irix4 ;; esac ;; isi68 | isi) basic_machine=m68k-isi os=-sysv ;; m88k-omron*) basic_machine=m88k-omron ;; magnum | m3230) basic_machine=mips-mips os=-sysv ;; merlin) basic_machine=ns32k-utek os=-sysv ;; mingw32) basic_machine=i386-pc os=-mingw32 ;; miniframe) basic_machine=m68000-convergent ;; *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari os=-mint ;; mips3*-*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` ;; mips3*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown ;; mmix*) basic_machine=mmix-knuth os=-mmixware ;; monitor) basic_machine=m68k-rom68k os=-coff ;; morphos) basic_machine=powerpc-unknown os=-morphos ;; msdos) basic_machine=i386-pc os=-msdos ;; mvs) basic_machine=i370-ibm os=-mvs ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) basic_machine=i386-unknown os=-netbsd ;; netwinder) basic_machine=armv4l-rebel os=-linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=-newsos ;; news1000) basic_machine=m68030-sony os=-newsos ;; news-3600 | risc-news) basic_machine=mips-sony os=-newsos ;; necv70) basic_machine=v70-nec os=-sysv ;; next | m*-next ) basic_machine=m68k-next case $os in -nextstep* ) ;; -ns2*) os=-nextstep2 ;; *) os=-nextstep3 ;; esac ;; nh3000) basic_machine=m68k-harris os=-cxux ;; nh[45]000) basic_machine=m88k-harris os=-cxux ;; nindy960) basic_machine=i960-intel os=-nindy ;; mon960) basic_machine=i960-intel os=-mon960 ;; nonstopux) basic_machine=mips-compaq os=-nonstopux ;; np1) basic_machine=np1-gould ;; nv1) basic_machine=nv1-cray os=-unicosmp ;; nsr-tandem) basic_machine=nsr-tandem ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki os=-proelf ;; or32 | or32-*) basic_machine=or32-unknown os=-coff ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=-ose ;; os68k) basic_machine=m68k-none os=-os68k ;; pa-hitachi) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; paragon) basic_machine=i860-intel os=-osf ;; pbd) basic_machine=sparc-tti ;; pbb) basic_machine=m68k-tti ;; pc532 | pc532-*) basic_machine=ns32k-pc532 ;; pentium | p5 | k5 | k6 | nexgen | viac3) basic_machine=i586-pc ;; pentiumpro | p6 | 6x86 | athlon | athlon_*) basic_machine=i686-pc ;; pentiumii | pentium2 | pentiumiii | pentium3) basic_machine=i686-pc ;; pentium4) basic_machine=i786-pc ;; pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumpro-* | p6-* | 6x86-* | athlon-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentium4-*) basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; ppc) basic_machine=powerpc-unknown ;; ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle | ppc-le | powerpc-little) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64) basic_machine=powerpc64-unknown ;; ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64le | powerpc64little | ppc64-le | powerpc64-little) basic_machine=powerpc64le-unknown ;; ppc64le-* | powerpc64little-*) basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ps2) basic_machine=i386-ibm ;; pw32) basic_machine=i586-unknown os=-pw32 ;; rom68k) basic_machine=m68k-rom68k os=-coff ;; rm[46]00) basic_machine=mips-siemens ;; rtpc | rtpc-*) basic_machine=romp-ibm ;; s390 | s390-*) basic_machine=s390-ibm ;; s390x | s390x-*) basic_machine=s390x-ibm ;; sa29200) basic_machine=a29k-amd os=-udi ;; sb1) basic_machine=mipsisa64sb1-unknown ;; sb1el) basic_machine=mipsisa64sb1el-unknown ;; sei) basic_machine=mips-sei os=-seiux ;; sequent) basic_machine=i386-sequent ;; sh) basic_machine=sh-hitachi os=-hms ;; sh64) basic_machine=sh64-unknown ;; sparclite-wrs | simso-wrs) basic_machine=sparclite-wrs os=-vxworks ;; sps7) basic_machine=m68k-bull os=-sysv2 ;; spur) basic_machine=spur-unknown ;; st2000) basic_machine=m68k-tandem ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun os=-sunos3 ;; sun2os4) basic_machine=m68000-sun os=-sunos4 ;; sun3os3) basic_machine=m68k-sun os=-sunos3 ;; sun3os4) basic_machine=m68k-sun os=-sunos4 ;; sun4os3) basic_machine=sparc-sun os=-sunos3 ;; sun4os4) basic_machine=sparc-sun os=-sunos4 ;; sun4sol2) basic_machine=sparc-sun os=-solaris2 ;; sun3 | sun3-*) basic_machine=m68k-sun ;; sun4) basic_machine=sparc-sun ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; sv1) basic_machine=sv1-cray os=-unicos ;; symmetry) basic_machine=i386-sequent os=-dynix ;; t3e) basic_machine=alphaev5-cray os=-unicos ;; t90) basic_machine=t90-cray os=-unicos ;; tic54x | c54x*) basic_machine=tic54x-unknown os=-coff ;; tic55x | c55x*) basic_machine=tic55x-unknown os=-coff ;; tic6x | c6x*) basic_machine=tic6x-unknown os=-coff ;; tx39) basic_machine=mipstx39-unknown ;; tx39el) basic_machine=mipstx39el-unknown ;; toad1) basic_machine=pdp10-xkl os=-tops20 ;; tower | tower-32) basic_machine=m68k-ncr ;; udi29k) basic_machine=a29k-amd os=-udi ;; ultra3) basic_machine=a29k-nyu os=-sym1 ;; v810 | necv810) basic_machine=v810-nec os=-none ;; vaxv) basic_machine=vax-dec os=-sysv ;; vms) basic_machine=vax-dec os=-vms ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; vxworks960) basic_machine=i960-wrs os=-vxworks ;; vxworks68) basic_machine=m68k-wrs os=-vxworks ;; vxworks29k) basic_machine=a29k-wrs os=-vxworks ;; w65*) basic_machine=w65-wdc os=-none ;; w89k-*) basic_machine=hppa1.1-winbond os=-proelf ;; xps | xps100) basic_machine=xps100-honeywell ;; ymp) basic_machine=ymp-cray os=-unicos ;; z8k-*-coff) basic_machine=z8k-unknown os=-sim ;; none) basic_machine=none-none os=-none ;; # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) basic_machine=hppa1.1-winbond ;; op50n) basic_machine=hppa1.1-oki ;; op60c) basic_machine=hppa1.1-oki ;; romp) basic_machine=romp-ibm ;; rs6000) basic_machine=rs6000-ibm ;; vax) basic_machine=vax-dec ;; pdp10) # there are many clones, so DEC is not a safe bet basic_machine=pdp10-unknown ;; pdp11) basic_machine=pdp11-dec ;; we32k) basic_machine=we32k-att ;; sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele) basic_machine=sh-unknown ;; sh64) basic_machine=sh64-unknown ;; sparc | sparcv9 | sparcv9b) basic_machine=sparc-sun ;; cydra) basic_machine=cydra-cydrome ;; orion) basic_machine=orion-highlevel ;; orion105) basic_machine=clipper-highlevel ;; mac | mpw | mac-mpw) basic_machine=m68k-apple ;; pmac | pmac-mpw) basic_machine=powerpc-apple ;; *-unknown) # Make sure to match an already-canonicalized machine name. ;; *) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; esac # Here we canonicalize certain aliases for manufacturers. case $basic_machine in *-digital*) basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` ;; *-commodore*) basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in # First match some system type aliases # that might get confused with valid system types. # -solaris* is a basic system type, with this one exception. -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) os=-solaris2 ;; -svr4*) os=-sysv4 ;; -unixware*) os=-sysv4.2uw ;; -gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # First accept the basic system types. # The portable systems comes first. # Each alternative MUST END IN A *, to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -386bsd* | -netbsd* | -openbsd* | -kfreebsd* | -freebsd* | -riscix* \ | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* \ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) ;; *) os=-nto$os ;; esac ;; -nto-qnx*) ;; -nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) ;; -mac*) os=`echo $os | sed -e 's|mac|macos|'` ;; -linux-dietlibc) os=-linux-dietlibc ;; -linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; -sunos5*) os=`echo $os | sed -e 's|sunos5|solaris2|'` ;; -sunos6*) os=`echo $os | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; -wince*) os=-wince ;; -osfrose*) os=-osfrose ;; -osf*) os=-osf ;; -utek*) os=-bsd ;; -dynix*) os=-bsd ;; -acis*) os=-aos ;; -atheos*) os=-atheos ;; -386bsd) os=-bsd ;; -ctix* | -uts*) os=-sysv ;; -nova*) os=-rtmk-nova ;; -ns2 ) os=-nextstep2 ;; -nsk*) os=-nsk ;; # Preserve the version number of sinix5. -sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; -triton*) os=-sysv3 ;; -oss*) os=-sysv3 ;; -svr4) os=-sysv4 ;; -svr3) os=-sysv3 ;; -sysvr4) os=-sysv4 ;; # This must come after -sysvr4. -sysv*) ;; -ose*) os=-ose ;; -es1800*) os=-ose ;; -xenix) os=-xenix ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) os=-mint ;; -aros*) os=-aros ;; -kaos*) os=-kaos ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in *-acorn) os=-riscix1.2 ;; arm*-rebel) os=-linux ;; arm*-semi) os=-aout ;; c4x-* | tic4x-*) os=-coff ;; # This must come before the *-dec entry. pdp10-*) os=-tops20 ;; pdp11-*) os=-none ;; *-dec | vax-*) os=-ultrix4.2 ;; m68*-apollo) os=-domain ;; i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 # This also exists in the configure program, but was not the # default. # os=-sunos4 ;; m68*-cisco) os=-aout ;; mips*-cisco) os=-elf ;; mips*-*) os=-elf ;; or32-*) os=-coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=-sysv3 ;; sparc-* | *-sun) os=-sunos4.1.1 ;; *-be) os=-beos ;; *-ibm) os=-aix ;; *-wec) os=-proelf ;; *-winbond) os=-proelf ;; *-oki) os=-proelf ;; *-hp) os=-hpux ;; *-hitachi) os=-hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=-sysv ;; *-cbm) os=-amigaos ;; *-dg) os=-dgux ;; *-dolphin) os=-sysv3 ;; m68k-ccur) os=-rtu ;; m88k-omron*) os=-luna ;; *-next ) os=-nextstep ;; *-sequent) os=-ptx ;; *-crds) os=-unos ;; *-ns) os=-genix ;; i370-*) os=-mvs ;; *-next) os=-nextstep3 ;; *-gould) os=-sysv ;; *-highlevel) os=-bsd ;; *-encore) os=-bsd ;; *-sgi) os=-irix ;; *-siemens) os=-sysv4 ;; *-masscomp) os=-rtu ;; f30[01]-fujitsu | f700-fujitsu) os=-uxpv ;; *-rom68k) os=-coff ;; *-*bug) os=-coff ;; *-apple) os=-macos ;; *-atari*) os=-mint ;; *) os=-none ;; esac fi # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. vendor=unknown case $basic_machine in *-unknown) case $os in -riscix*) vendor=acorn ;; -sunos*) vendor=sun ;; -aix*) vendor=ibm ;; -beos*) vendor=be ;; -hpux*) vendor=hp ;; -mpeix*) vendor=hp ;; -hiux*) vendor=hitachi ;; -unos*) vendor=crds ;; -dgux*) vendor=dg ;; -luna*) vendor=omron ;; -genix*) vendor=ns ;; -mvs* | -opened*) vendor=ibm ;; -ptx*) vendor=sequent ;; -vxsim* | -vxworks* | -windiss*) vendor=wrs ;; -aux*) vendor=apple ;; -hms*) vendor=hitachi ;; -mpw* | -macos*) vendor=apple ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) vendor=atari ;; -vos*) vendor=stratus ;; esac basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` ;; esac echo $basic_machine$os exit 0 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: ./libtecla/install-sh0100755000076400007640000001273607600006116013142 0ustar mcsmcs#!/bin/sh # # install - install a program, script, or datafile # This comes from X11R5 (mit/util/scripts/install.sh). # # Copyright 1991 by the Massachusetts Institute of Technology # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of M.I.T. not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. M.I.T. makes no representations about the # suitability of this software for any purpose. It is provided "as is" # without express or implied warranty. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. It can only install one file at a time, a restriction # shared with many OS's install programs. # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd="$cpprog" shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd="$stripprog" shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: chmodcmd="" else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0 ./libtecla/Makefile.stub0100644000076400007640000000032307707106701013550 0ustar mcsmcsdefault: ./configure $(MAKE) distclean: ./configure --without-man-pages $(MAKE) $@ normal reentrant demos demos_r clean install_lib install_bin install_inc \ install_man install: ./configure $(MAKE) $@ ./libtecla/libtecla.map0100644000076400007640000001011210030524614013375 0ustar mcsmcs# This mapfile (or version script) lists the public symbols that are # publically exported by each version of the tecla library. This file # has the format required by the Sun and Linux linkers, and also acts # as a template from which map files for other systems can be derived # with awk or sed. # # Under Solaris and Linux, this map file is used by ld during shared # library creation. It has two purposes: # # 1. It specifies which symbols in the library are to be made visible # to applications. This has the dual benefits of reducing namespace # polution, and of preventing applications from using private # internal library functions that might change or disappear in # future releases. # # 2. The information listed in this file is recorded in the shared # library, such that when an application is linked against it, the # linker can record a dependency in the application which says # which is the earliest library version which included all of the # symbols that the application needs. This means that if the # application is copied to another system that has an earlier # version of the library, the linker can quickly determine whether # the earlier version contains all of the symbols that it needs. # # Under Linux, mapfiles can also be used to allow multiple # incompatible versions of a given function to exist in a library, # thus supporting applications that were compiled against different # incompatible versions of the library. Since this feature (and the # inclusion of .symver directives) isn't supported by Solaris, it # can't be included in this file. Non backwards compatibility in the # ABI must instead be handled in the more traditional way, by # incrementing the major version number. # # When a new minor release is made, a new tecla_1.x specification # should be added which inherits the symbols of the previous release # and lists newly added functions. For example, below you will find # the following clause: # # tecla_1.3 { # global: # ef_list_expansions; # } tecla_1.2; # # This says that ef_list_expansions is the name of a public function # that was added in the 1.3 release, and that the symbols defined in # the previous tecla_1.2 clause have been inherited by tecla_1.3. # # For more details see the following URL: # # http://www.usenix.org/publications/library/proceedings/als2000/browndavid.html #------------------------------------------------------------------------------- tecla_1.2 { global: cfc_file_start; cfc_literal_escapes; cfc_set_check_fn; cpl_add_completion; cpl_check_exe; cpl_complete_word; cpl_file_completions; cpl_init_FileArgs; cpl_last_error; cpl_list_completions; cpl_record_error; del_CplFileConf; del_ExpandFile; del_GetLine; del_PathCache; del_PcaPathConf; del_WordCompletion; ef_expand_file; ef_last_error; gl_change_terminal; gl_customize_completion; gl_get_line; new_CplFileConf; new_ExpandFile; new_GetLine; new_PathCache; new_PcaPathConf; new_WordCompletion; pca_last_error; pca_lookup_file; pca_path_completions; pca_scan_path; pca_set_check_fn; ppc_file_start; ppc_literal_escapes; local: *; }; tecla_1.3 { global: ef_list_expansions; } tecla_1.2; tecla_1.4 { global: gl_configure_getline; gl_save_history; gl_load_history; gl_group_history; gl_show_history; gl_resize_history; gl_limit_history; gl_clear_history; gl_toggle_history; gl_watch_fd; libtecla_version; gl_terminal_size; gl_state_of_history; gl_range_of_history; gl_size_of_history; gl_lookup_history; gl_echo_mode; gl_replace_prompt; gl_prompt_style; gl_ignore_signal; gl_trap_signal; gl_last_signal; } tecla_1.3; tecla_l.5 { global: gl_inactivity_timeout; gl_completion_action; gl_register_action; gl_display_text; gl_error_message; gl_return_status; gl_set_term_size; gl_list_signals; gl_catch_blocked; gl_io_mode; gl_raw_io; gl_normal_io; gl_tty_signals; gl_abandon_line; gl_handle_signal; gl_pending_io; gl_bind_keyseq; cpl_recall_matches; gl_erase_terminal; } tecla_1.4; tecla_1.6 { global: gl_append_history; gl_automatic_history; gl_query_char; gl_read_char; } tecla_l.5; ./libtecla/version.c0100644000076400007640000000230707362242672012776 0ustar mcsmcs#include "libtecla.h" /*....................................................................... * Return the version number of the tecla library. * * Input: * major int * The major version number of the library * will be assigned to *major. This number is * only incremented when a change to the library is * made that breaks binary (shared library) and/or * compilation backwards compatibility. * minor int * The minor version number of the library * will be assigned to *minor. This number is * incremented whenever new functions are added to * the public API. * micro int * The micro version number of the library will be * assigned to *micro. This number is incremented * whenever internal changes are made that don't * change the public API, such as bug fixes and * performance enhancements. */ void libtecla_version(int *major, int *minor, int *micro) { if(major) *major = TECLA_MAJOR_VER; if(minor) *minor = TECLA_MINOR_VER; if(micro) *micro = TECLA_MICRO_VER; } ./libtecla/update_version0100755000076400007640000000412107362244500014106 0ustar mcsmcs#!/bin/sh #----------------------------------------------------------------------- # Change the version number of the library. This changes the number in # every file that it is known to appear in. # # Usage: # update_version major minor micro #----------------------------------------------------------------------- usage="$0 major minor micro" if [ $# -ne 3 ]; then echo $usage exit 1 fi # Get the three components of the version number. major="$1" minor="$2" micro="$3" # Everything will need to be reconfigured after this change, so # discard any existing configuration. make distclean 2>/dev/null # Check that the version components are all positive integers. for c in $major $minor $micro; do if echo "$c" | awk '{exit $1 ~ /^[0-9]+$/}'; then echo 'Version number components must all be positive integers.' exit 1 fi done # # Update the version number in the configure.in script. # ed -s configure.in << EOF /^MAJOR_VER=\"[0-9][0-9]*\"/ s/^.*$/MAJOR_VER=\"$major\"/ /^MINOR_VER=\"[0-9][0-9]*\"/ s/^.*$/MINOR_VER=\"$minor\"/ /^MICRO_VER=\"[0-9][0-9]*\"/ s/^.*$/MICRO_VER=\"$micro\"/ w q EOF if which autoconf 1>/dev/null 2>&1; then autoconf else echo 'Note that autoconf needs to be run.' fi # # Update the version number in the libtecla header file script. # ed -s libtecla.h << EOF /^#define TECLA_MAJOR_VER [0-9][0-9]*/ s/^.*$/#define TECLA_MAJOR_VER $major/ /^#define TECLA_MINOR_VER [0-9][0-9]*/ s/^.*$/#define TECLA_MINOR_VER $minor/ /^#define TECLA_MICRO_VER [0-9][0-9]*/ s/^.*$/#define TECLA_MICRO_VER $micro/ w q EOF # # Update the version number in the README file. # ed -s README << EOF /version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]* / s/version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/version $major.$minor.$micro/ w q EOF # # Update the version number in the html index file. # ed -s html/index.html << EOF /version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\./ s/version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/version $major.$minor.$micro/g /libtecla-[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\./ s/libtecla-[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\./libtecla-$major.$minor.$micro./g w q EOF ./libtecla/configure.in0100644000076400007640000005363210142301377013451 0ustar mcsmcsdnl This is the input file which autoconf uses to construct a dnl "configure" script for the tecla library. It is a bourne shell dnl script which autoconf pre-processes with the m4 preprocessor to dnl expand autoconf-defined m4 macros such as AC_INIT(). The dnl following line just initializes autoconf. Autoconf interprets the dnl single argument as the name of an arbitrary file, which it uses to dnl ensure that it is being run correctly from the directory which dnl contains the libtecla source code. AC_INIT(getline.c) dnl Here we set the major version number of the tecla library. dnl Incrementing this number implies that a change has been made to dnl the library's public interface, which makes it binary incompatible dnl with programs that were linked with previous shared versions of dnl the tecla library. Incompatible changes of this type should be dnl avoided at all costs, so it is hoped that the major version number dnl won't ever have to change. The major version number must be a dnl small integer number, preferably a single numeric digit. AC_SUBST(MAJOR_VER) MAJOR_VER="1" dnl Set the minor version number of the tecla library. This number dnl should be incremented by one whenever additional functionality, dnl such as new functions or modules, are added to the library. The dnl idea is that a program that was linked with a shared library of dnl the same major version number, but a lower minor version number, dnl will continue to function when the run-time loader links it dnl against the updated version. The minor version number must be a dnl small integer number, which should be reset to 0 whenever the dnl major version number is incremented. AC_SUBST(MINOR_VER) MINOR_VER="6" dnl Set the micro version number of the tecla library. This is dnl incremented whenever modifications to the library are made which dnl make no changes to the public interface, but which fix bugs and/or dnl improve the behind-the-scenes implementation. The micro version dnl number should be reset to 0 whenever the minor version number is dnl incremented. The micro version number must be a small integer dnl number. AC_SUBST(MICRO_VER) MICRO_VER="1" dnl The AC_PROG_CC line looks for a C compiler, and if gcc is chosen, dnl sets the $GCC shell variable to "yes". Make sure that CFLAGS is dnl set to something first, to prevent AC_PROG_CC from substituting -g dnl for the optimization level. CFLAGS="$CFLAGS" AC_PROG_CC dnl Apparently not all implementations of the 'make' command define dnl the MAKE variable. The following directive creates a variable dnl called SET_MAKE which when expanded in a makefile is either empty dnl if the local 'make' command was found to define the MAKE variable, dnl or contains an assignment which will give the MAKE variable the dnl value 'make'. AC_PROG_MAKE_SET dnl The following directive causes autoconf to see if symbolic links dnl are supported on the current filesystem. If so, it sets the dnl variable LN_S to "ln -s". Otherwise it sets LN_S to just "ln". dnl This allows us to create symbolic links where possible, but falls dnl back to creating hard links where symbolic links aren't available. AC_PROG_LN_S dnl The following macro searches for the best implementation of awk dnl on the host system, and records it in the AWK shell variable. AC_PROG_AWK dnl If ranlib is needed on the target system, the RANLIB make variable dnl is set to ranlib. Otherwise it is set to :, which is the do-nothing dnl command of the bourne shell. dnl Note that we do not use AC_PROG_RANLIB because (at least in dnl autoconf 2.53) this does not check for cross-compilation. AC_CHECK_TOOL(RANLIB, ranlib) dnl Set LD as appropriate, especially when cross-compiling AC_CHECK_TOOL(LD, ld) dnl The following directive tells autoconf to figure out the target dnl system type and assign a canonical name for this to the $target dnl shell variable. This is used below in the target-specific case dnl statement. AC_CANONICAL_SYSTEM dnl In early versions of Solaris, some libraries are in /usr/ccs/lib, dnl where gcc doesn't look. The tests below for the curses library dnl would thus fail without this directory being added to the search dnl path. We thus add it here before the tests. Note that in the dnl following, since [ and ] are m4 quotes, and m4 will remove the dnl outermost quotes when it processes this file, we have to double dnl them up here to get [0-6] to appear in the output configure dnl script. case $target_os in solaris2.[[0-6]]|solaris2.[[0-6]].*) LIBS="$LIBS -L/usr/ccs/lib" ;; esac dnl Recent versions of gcc place /usr/local/include at the head of the dnl system include-file search path. This causes problems when include dnl files that have the same name as standard system include files are dnl placed in this directory by third-party packages. To avoid this, dnl move /usr/local/include to the end of the search path. if test "$GCC"_ = "yes"_; then touch foo.c fix=`$CC -E -Wp,-v foo.c 2>&1 | $AWK ' /^#include <...> search starts here:/ {in_list=1;ndir=0} / *\// && in_list {path[[ndir++]] = $1} /^End of search list/ {in_list=0} END { if(path[[0]] ~ /\/usr\/local\/include/) { for(dir=1; dir])], [AC_CHECK_HEADERS(ncurses/curses.h, [AC_CHECK_HEADERS(ncurses/term.h,[],[],[#include ])])]) dnl The following variable lists the targets that will be created if dnl the user runs make without any arguments. Initially we assume dnl that we can create both the normal and the reentrant versions dnl of the library. AC_SUBST(TARGETS) TARGETS="normal reentrant" dnl Check for reentrant functions by attempting to compile and link a dnl temporary program which calls them, being sure to include the dnl appropriate headers and define _POSIX_C_SOURCE, just in case any dnl of the functions are defined as macros. In the following, dnl AC_CACHE_CHECK outputs the message "checking for reentrant dnl functions". If this check has been done before, it assigns the dnl cached yes/no value to tecla_cv_reentrant. Otherwise it uses dnl AC_TRY_LINK() to attempt to compile and link the specified dummy dnl program, and sets tecla_cv_reentrant to yes or no, depending on dnl whether this succeeds. Finally it caches the value of dnl tecla_cv_reentrant in the file config.cache, and writes "yes" or dnl "no" to the terminal. AC_CACHE_CHECK(for reentrant functions, tecla_cv_reentrant, [ KEPT_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=199506L" AC_TRY_LINK([ #include #include #include #include #include ], [ (void) readdir_r(NULL, NULL, NULL); (void) getpwuid_r(geteuid(), NULL, NULL, 0, NULL); (void) getpwnam_r(NULL, NULL, NULL, 0, NULL); ], tecla_cv_reentrant=yes, tecla_cv_reentrant=no) CFLAGS="$KEPT_CFLAGS" ]) dnl If the necessary reentrant functions weren't found to be dnl available, default to only compiling the non-reentrant version of dnl the library. if test $tecla_cv_reentrant = no; then TARGETS="normal" fi dnl If sys/select.h exists, arrange for the HAVE_SYS_SELECT_H C-macro to dnl be defined when compiling the library. AC_CHECK_HEADER(sys/select.h, AC_DEFINE(HAVE_SYS_SELECT_H)) dnl Check for the select system call with the normal arguments, dnl by attempting to compile and link a temporary program which dnl calls it, being sure to include the appropriate headers. dnl In the following, AC_CACHE_CHECK outputs the message dnl "checking for select system call". If this check has been done dnl before, it assigns the cached yes/no value to tecla_cv_select. dnl Otherwise it uses AC_TRY_LINK() to attempt to compile and link dnl the specified dummy program, and sets tecla_cv_select to yes dnl or no, depending on whether this succeeds. Finally it caches dnl the value of tecla_cv_select in the file config.cache, and dnl writes "yes" or "no" to the terminal. AC_CACHE_CHECK(for select system call, tecla_cv_select, [ AC_TRY_LINK([ #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif ], [ fd_set fds; int nready; FD_ZERO(&fds); FD_SET(1, &fds); nready = select(2, &fds, &fds, &fds, NULL); ], tecla_cv_select=yes, tecla_cv_select=no) ]) dnl If the select function was available, arrange for HAVE_SELECT to dnl be defined by CFLAGS. if test $tecla_cv_select = yes; then AC_DEFINE(HAVE_SELECT) fi dnl Check if this system supports the system V pseudo terminal interface. AC_CACHE_CHECK(for SysV pseudo-terminals, tecla_cv_sysv_pty, [ AC_TRY_LINK([ #include #include #include ], [ char *name = ptsname(0); int i1 = grantpt(0); int i2 = unlockpt(0); int i3 = ioctl(0, I_PUSH, "ptem"); return 0; ], tecla_cv_sysv_pty=yes, tecla_cv_sysv_pty=no) ]) dnl If the system-V pseudo-terminal interface is available, arrange dnl for HAVE_SYSV_PTY to be defined by CFLAGS. if test $tecla_cv_sysv_pty = yes; then AC_DEFINE(HAVE_SYSV_PTY) fi dnl The following variable contains the extension to append to dnl "libtecla" and "libtecla_r" when creating shared libraries on the dnl target platform. This is system dependent and is ignored if dnl LINK_SHARED remains an empty string. On most platforms that dnl support shared libaries, this will be .so.$MAJOR_VER, where dnl MAJOR_VER is the major version number described above, which on dnl some systems, tells the run-time loader if the program being dnl loaded is binary compatible with a given version of the library dnl (see the discussion of MAJOR_VER near the top of this file). dnl The following empty default can be overriden on a system by system dnl basis later in this file. AC_SUBST(SHARED_EXT) SHARED_EXT="" dnl When a shared library is installed with the extension $SHARED_EXT, dnl you can optionally produce other copies of this library with dnl different extensions. This is done using symbolic or hard links, dnl depending on what is available on the current filesystem, and the dnl extensions to use for these links are listed in the following dnl variable, separated by spaces. The following empty default can be dnl overriden on a system by system basis later in this file. AC_SUBST(SHARED_ALT) SHARED_ALT="" dnl The following variable lists extra compilation flags needed to dnl create object files that can be included in shared libraries. dnl Normally one would include a flag to tell the C compiler to dnl compile position-independent code. This option commonly includes dnl the acronym 'pic'. AC_SUBST(SHARED_CFLAGS) SHARED_CFLAGS="" dnl On systems that support shared libraries, the following variable dnl provides the command needed to make a shared library. In this dnl variable, $$@ will be replaced with the name of the shared dnl library, $$(LIB_OBJECTS) will be replaced with a space separated dnl list of the object files that are to be included in the library, dnl and libtecla$$(SUFFIX) will be the name of the library being dnl built, minus the system-specific extension (eg. libtecla or dnl libtecla_r). If LINK_SHARED is left as an empty string, shared dnl library creation will not attempted. If your system supports dnl shared library creation, you should override the default value of dnl this variable in the target-specific case statement later in this dnl file. AC_SUBST(LINK_SHARED) LINK_SHARED="" dnl When compiling the reentrant version of the library, the following dnl compiler flags are presented to the compiler, in addition to those dnl that are used when compiling the non-reentrant version of the dnl library. The PREFER_REENTRANT macro is an internal libtecla macro dnl whose presence reports when the reentrant version of the library dnl is being compiled. This allows the code to determine when to dnl disable features that can't portably be implemented reentrantly, dnl such as username completion. The existence of the _POSIX_C_SOURCE dnl macro can't be reliably used for this purpose, since some systems dnl define it by default for all code. AC_SUBST(DEFS_R) DEFS_R="-D_POSIX_C_SOURCE=199506L -DPREFER_REENTRANT" dnl For man pages relating to library features, the following two dnl variables determine in which sub-directory of the top-level man dnl directory the man pages should go, and what file-name extensions dnl these files should have. On systems where the following defaults dnl are not valid, the default values should be overriden in the dnl target-specific case statement later in this file. AC_SUBST(LIBR_MANDIR) AC_SUBST(LIBR_MANEXT) LIBR_MANDIR="man3" LIBR_MANEXT="3" dnl For man pages relating to library functions, the following two dnl variables serve the same purpose as the previously described dnl LIBR_MANDIR and LIBR_MANEXT variables. AC_SUBST(FUNC_MANDIR) AC_SUBST(FUNC_MANEXT) FUNC_MANDIR="man3" FUNC_MANEXT="3" dnl For man pages relating to programs, the following two variables dnl serve the same purpose as the previously described LIBR_MANDIR dnl and LIBR_MANEXT variables. AC_SUBST(PROG_MANDIR) AC_SUBST(PROG_MANEXT) PROG_MANDIR="man1" PROG_MANEXT="1" dnl For man pages on miscellaneous topics, the following two variables dnl serve the same purpose as the previously described LIBR_MANDIR dnl and LIBR_MANEXT variables. AC_SUBST(MISC_MANDIR) AC_SUBST(MISC_MANEXT) MISC_MANDIR="man7" MISC_MANEXT="7" dnl For man pages relating to configuration files, the following two dnl variables serve the same purpose as the previously described dnl LIBR_MANDIR and LIBR_MANEXT variables. AC_SUBST(FILE_MANDIR) AC_SUBST(FILE_MANEXT) FILE_MANDIR="man5" FILE_MANEXT="5" dnl If the application doesn't want the user to have access to the dnl filesystem, it can remove all action functions that list, read or dnl write files, by including the configuration argument dnl --without-file-actions. AC_ARG_WITH(file-actions, AC_HELP_STRING([--with-file-actions], [Should users of gl_get_line() have access to the filesystem (default=yes)]), AC_DEFINE(HIDE_FILE_SYSTEM), ) dnl If the target system either has no file-system, or file-system access dnl isn't needed, libtecla can be made smaller by excluding all file and dnl directory access code. This is done by adding the configuration dnl argument --without-file-system. AC_ARG_WITH(file-system, AC_HELP_STRING([--with-file-system], [Does the target have a filesystem (default=yes)]), AC_DEFINE(WITHOUT_FILE_SYSTEM), ) dnl The following bourne shell case statement is where system dnl dependencies can be added. In particular, if your system supports dnl shared library creation, the following switch is the place to dnl configure it. To do so you will first need to find out what target dnl type was just assigned by the AC_CANONICAL_SYSTEM macro executed dnl previously. The target type of your current system can be dnl determined by cd'ing to the top level directory of the tecla dnl distribution, and typing the command "sh config.guess". This will dnl report what autoconf thinks the system type is. Note that this dnl will be very specific, so if you know that the configuration dnl parameters that you are about to provide apply to different dnl versions of the current system type, you can express this in the dnl case statement by using a wild-card in place of the version dnl number, or by using an | alternation to list one or more version dnl names. Beware that autoconf uses [] as quote characters, so if you dnl want to use a regexp character range like [a-z], you should write dnl this as [[a-z]]. case $target in *solaris*) AC_DEFINE(__EXTENSIONS__) SHARED_EXT=".so.${MAJOR_VER}" SHARED_ALT=".so" LINK_SHARED="$LD"' -G -M $$(srcdir)/libtecla.map -o $$@ -h $$(@F) -z defs -i $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-Kpic" case $CC in */cc|cc) SHARED_CFLAGS="$SHARED_CFLAGS -xstrconst" ;; esac case $target_cpu in sparc) SHARED_CFLAGS="$SHARED_CFLAGS -xregs=no%appl" esac case $target_os in solaris2.[[89]]*|solaris2.1[[0-9]]*) LIBR_MANEXT=3lib FUNC_MANEXT=3tecla LIBR_MANDIR=man$LIBR_MANEXT FUNC_MANDIR=man$FUNC_MANEXT esac MISC_MANDIR="man5" MISC_MANEXT="5" FILE_MANDIR="man4" FILE_MANEXT="4" ;; *linux*) SHARED_EXT=".so.${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}" SHARED_ALT=".so .so.${MAJOR_VER}" dnl See if the installed version of Gnu ld accepts version scripts. AC_CACHE_CHECK([for --version-script in GNU ld], tecla_cv_gnu_ld_script, [ if (echo 'void dummy(void) {return;}' > dummy.c; $CC -c -fpic dummy.c; \ $LD -o dummy.so dummy.o -shared --version-script=$srcdir/libtecla.map) 1>&2 2>/dev/null; then tecla_cv_gnu_ld_script=yes else tecla_cv_gnu_ld_script=no fi rm -f dummy.c dummy.o dummy.so ]) if test $tecla_cv_gnu_ld_script = yes; then VERSION_OPT='--version-script=$$(srcdir)/libtecla.map' else VERSION_OPT='' fi LINK_SHARED="$LD"' -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-fpic" dnl Reenable the inclusion of symbols which get undefined when POSIX_C_SOURCE dnl is specified. CFLAGS="-D_SVID_SOURCE -D_BSD_SOURCE $CFLAGS" ;; *hpux*) SHARED_EXT=".${MAJOR_VER}" SHARED_ALT=".sl" LINK_SHARED="$LD"' -b +h $$(@F) +k +vshlibunsats -o $$@ -c libtecla.map.opt $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="+z" MISC_MANEXT=5 FILE_MANEXT=4 MISC_MANDIR=man$MISC_MANEXT FILE_MANDIR=man$FILE_MANEXT ;; *darwin*) SHARED_EXT=".${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}.dylib" SHARED_ALT=".dylib .${MAJOR_VER}.dylib" LINK_SHARED='$(CC) -o $$@ -dynamiclib -flat_namespace -undefined suppress -compatibility_version '${MAJOR_VER}.${MINOR_VER}' -current_version '${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}' -install_name '${libdir}'/$$@ $$(LIB_OBJECTS)' SHARED_CFLAGS="" ;; *dec-osf*) AC_DEFINE(_OSF_SOURCE) ;; *freebsd*) SHARED_EXT=".so.${MAJOR_VER}" SHARED_ALT=".so" VERSION_OPT='--version-script=$$(srcdir)/libtecla.map' LINK_SHARED='ld -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc' SHARED_CFLAGS="-fpic" ;; mips-sgi-irix*) DEFS_R="$DEFS_R -D_XOPEN_SOURCE=500" if test "$RANLIB"_ = "_"; then RANLIB=":" fi ;; esac dnl The following statement checks to see if the GNU C compiler has dnl been chosen instead of the normal compiler of the host operating dnl system. If it has, and shared library creation has been dnl configured, it replaces the shared-library-specific C compilation dnl flags with those supported by gcc. Also append the gcc run-time dnl library to the shared library link line. if test "$GCC"_ = "yes"_ && test "$LINK_SHARED"_ != "_" ; then SHARED_CFLAGS="-fpic" case $target in sparc-*-solaris*) SHARED_CFLAGS="$SHARED_CFLAGS -mno-app-regs" ;; *darwin*) SHARED_CFLAGS="" ;; esac LINK_SHARED="$LINK_SHARED `gcc -print-libgcc-file-name`" fi dnl The following variable will list which types of libraries, dnl "static", and possibly "shared", are to be created and installed. AC_SUBST(TARGET_LIBS) dnl If shared library creation has been configured, add shared dnl libraries to the list of libraries to be built. if test "$LINK_SHARED"_ != "_"; then TARGET_LIBS="static shared" else TARGET_LIBS="static" LINK_SHARED="@:" fi dnl Set the shell variable and Makefile variable, MAKE_MAN_PAGES, to dnl "yes" if man pages are desired. By default they are, but if the dnl user specifies --with-man-pages=no or --without-man-pages, then dnl they won't be preprocessed by the configure script or installed dnl by the Makefile. AC_SUBST(MAKE_MAN_PAGES) AC_ARG_WITH(man-pages, AC_HELP_STRING([--with-man-pages], [Are man pages desired (default=yes)]), MAKE_MAN_PAGES="$withval", MAKE_MAN_PAGES="yes") dnl Create the list of files to be generated by the configure script. OUTPUT_FILES="Makefile" rm -rf man/man* if test "$MAKE_MAN_PAGES"_ = "yes"_; then for area in libr func misc prog file; do for page in man/$area/*.in; do OUTPUT_FILES="$OUTPUT_FILES `echo $page | sed 's/\.in$//'`" done done fi dnl The following directive must always be the last line of any dnl autoconf script. It causes autoconf to create the configure dnl script, which for each argument of AC_OUTPUT, will look for a dnl filename formed by appending ".in" to the argument, preprocess dnl that file, replacing @VAR@ directives with the corresponding value dnl of the specified shell variable VAR, as set above in this file, dnl and write the resulting output to the filename given in the dnl argument. Note that only shell variables that were exported above dnl with the AC_SUBST() directive will be substituted in @VAR@ dnl directives (some macros like AC_PROG_CC also call AC_SUBST for you dnl for the variables that they output). AC_OUTPUT($OUTPUT_FILES) ./libtecla/INSTALL0100644000076400007640000002353410000461623012161 0ustar mcsmcsTo compile and optionally install the library, it is first necessary to create a makefile for your system, by typing: ./configure The Makefile that this generates is designed to install the files of the library in subdirectories of /usr/local/. If you would prefer to install them under a different directory, you can type: ./configure --prefix /wherever Where you would replace /wherever with your chosen directory. Other command-line options are available, and can be listed by typing: ./configure --help Having run the configure script, you are then ready to make the library. To do this, just type: make What 'make' does depends on whether the configure script knows about your system. If the configure script doesn't know anything specific about your system, it will arrange for 'make' to produce the static tecla library, called libtecla.a, and if possible, the reentrant version of this called libtecla_r.a. If it does know about your system, it will also create shared libraries if possible. If you are on a system that isn't known, and you would like shared libraries to be compiled, please read the file called PORTING to see how this can be achieved. To install the library, its include file and it manual pages, type: make install Note that this will also compile the library if you haven't already done so. Having compiled the library, if you wish, you can test it by running the demo programs. After building the library, you should find two programs, called demo and demo2, in the current directory. The first of the demos programs reads input lines from the user, and writes what was typed back to the screen. While typing a line of input, you can experiment with line editing, tab completion, history recall etc.. For details about these line editing features, see the man page gl_get_line(3). If you haven't installed this yet, you can see it anyway by typing: nroff -man man3/gl_get_line.3 | more The second demo program, called demo2, demonstrates command-completion with the UNIX PATH. If you type in a partial command name, and press TAB, the command name will be completed if possible, and possible completions will be listed if it is ambiguous. When you then enter the line, the demo program then prints out the full pathname of the command that you typed. If you type anything after the command name, filename completion with the tab key reverts to its default behavior of completing filenames in the current directory. COMPILING IN A DIFFERENT DIRECTORY ---------------------------------- If you unpack the distribution in a directory which is visible from multiple hosts which have different architectures, you have the option of compiling the library for the different architectures in different directories. You might for example create a sub-directory for each architecture, under the top level directory of the distribution. You would then log in to a host of one of these architectures, cd to the sub-directory that you created for it, and type: ../configure The configure script then creates a makefile in the current directory which is designed to build the library, object files, demos etc for the architecture of the current host, in the current directory, using the original source code in ../. You then repeat this procedure on hosts of other architectures. The compilation directories don't have to be sub-directories of the top level directory of the distribution. That was just described as an example. They can be anywhere you like. Every rule in the makefiles that are generated by the configure script, cites the paths of the target and source files explicitly, so this procedure should work on any system, without the need for vpath makefile support. EMBEDDING IN OTHER PACKAGE DISTRIBUTIONS ---------------------------------------- If you distribute the library with another package, which has its own heirarchy and configuration procedures, the following installation options may be of interest to you. At first glance, the use of a GNU configure script by the tecla library, may appear to reduce your options for controlling what gets made, and where it gets installed, but this isn't the case, because many of the parameters configured by the configure script are assigned to makefile variables which can be overriden when you run make. Configure script options: If the users of your package won't benefit from having access to the tecla man pages, you can shorten the length of time taken to run the configure script by telling this script not to preprocess the man pages. This is done as follows. ./configure --without-man-pages Note that this option also causes the makefile man-page installation targets to do nothing. Similarly, if you don't want your users to have access to the filesystem while they are editing input lines using gl_get_line(), then use the following configuration argument. ./configure --without-file-actions This will completely remove the "expand-filename", "read-from-file", "read-init-files", and "list-glob" action functions. It will also arrange that the default behavior of actions such as "complete-word" and "list-or-eof" be changed to show no completions, instead of the normal default of showing filename completions. If you are using a system that doesn't have a file-system, such as an embedded system, then libtecla can be built with all code that accesses the filesystem removed. This will make the library a bit smaller, and potentially avoid running into problems of missing system functions related to file-system access. This is done with the following configuration option. ./configure --without-file-system Beware that this not only prevents the user from accessing the filesystem while editing an input line in gl_get_line(), but also removes all public file-related functions, such as the pathname expansion module. When using gl_get_line() in this configuration, the functions that load and save history from and to files, are stubs that report an error if called upon to read files. The gl_configure_getline() function can still be called upon to configure gl_get_line() via its app_string argument, but passing it a filename in either the app_file or user_file arguments will result in an error being reported. Now lets say that you have your own configuration script in the parent directory of the libtecla top-level directory, and that you don't want tecla's man pages to be generated. In your configuration script, you would first need to have a line similar to the following: (cd libtecla; ./configure --without-man-pages) Now, from your makefile or whatever script you use to build your application, you would need to make the tecla library. Assuming that your makefile or build script is in the parent directory of the libtecla distribution, then the following line tells make to just build the non-reentrant, static version of the tecla library, and then to install it and the tecla include file in sub-directories called lib and include in your current directory. (cd libtecla; make LIBDIR=../lib INCDIR=../include TARGETS=normal TARGET_LIBS="static" install_lib install_inc) In this statement the LIBDIR=../lib component means that on installing the library, the make command should place the library in the directory libtecla/../lib. Similarly INCDIR tells make where to place the include files. The install_lib and install_inc targets tell make to install the libraries and the include file. Because the install_man and install_bin targets have been omitted in this example, the man pages and programs aren't installed. If you were to include these additional targets then you could use the MANDIR and BINDIR variables, respectively to control where they were installed. The TARGETS variable is used to specify which of the normal and reentrant versions of the library are compiled. This can contain one or both of the words "normal" and "reentrant". If you don't specify this when you invoke make, the default value generated by the configure script will be used. Depending on whether reentrant POSIX functions are available for compilation of the reentrant version, this will be either "normal" or "normal reentrant". The TARGET_LIBS variable is used to specify which of the static and shared libraries are to be built. This can contain one or both of the words "static" and "shared". If you don't specify this when you invoke make, the default value generated by the configure script will be used. Depending on whether the configure script knows how to create shared libraries on the target system, this will be either "static" or "static shared". Beware that shared libraries aren't supported on many systems, so requiring "shared" will limit which systems you can compile your package on. Also note that unless your package installs the tecla library in a directory which all users of your program will have access to, you should only compile the static version. Instructions for adding shared-library creation rules for new systems are included in the PORTING file. The OPT variable can be used to change the default optimization from the default of "-O" to something else. The DEMOS variable controls whether the demo programs are built. Normally this has the value "demos", which tells the makefile to build the demo programs. Setting it to an empty string stops the demos from being built. The PROGRAMS variable is used to specify which programs are to be built and subsequently installed. All available programs are built by default. Currently there is only one such program, selected by specifying the word "enhance". This program uses tecla-enhanced pseudo-terminals to layer command line editing on top of third party programs. The PROGRAMS_R variable serves the same purpose as the PROGRAMS variable, except that programs listed here are linked with the reentrant version of the library, and should be specified with a _r suffix. Currently this variable is empty by default. Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/enhance.c0100644000076400007640000005201410016602622012673 0ustar mcsmcs#include #include #include #include #include #include #include #include #ifdef HAVE_SELECT #ifdef HAVE_SYS_SELECT_H #include #endif #endif #include #include #include #include #include #if HAVE_SYSV_PTY #include /* System-V stream I/O */ char *ptsname(int fd); int grantpt(int fd); int unlockpt(int fd); #endif #include "libtecla.h" /* * Pseudo-terminal devices are found in the following directory. */ #define PTY_DEV_DIR "/dev/" /* * Pseudo-terminal controller device file names start with the following * prefix. */ #define PTY_CNTRL "pty" /* * Pseudo-terminal slave device file names start with the following * prefix. */ #define PTY_SLAVE "tty" /* * Specify the maximum suffix length for the control and slave device * names. */ #define PTY_MAX_SUFFIX 10 /* * Set the maximum length of the master and slave terminal device filenames, * including space for a terminating '\0'. */ #define PTY_MAX_NAME (sizeof(PTY_DEV_DIR)-1 + \ (sizeof(PTY_SLAVE) > sizeof(PTY_CNTRL) ? \ sizeof(PTY_SLAVE) : sizeof(PTY_CNTRL))-1 \ + PTY_MAX_SUFFIX + 1) /* * Set the maximum length of an input line. */ #define PTY_MAX_LINE 4096 /* * Set the size of the buffer used for accumulating bytes written by the * user's terminal to its stdout. */ #define PTY_MAX_READ 1000 /* * Set the amount of memory used to record history. */ #define PTY_HIST_SIZE 10000 /* * Set the timeout delay used to check for quickly arriving * sequential output from the application. */ #define PTY_READ_TIMEOUT 100000 /* micro-seconds */ static int pty_open_master(const char *prog, int *cntrl, char *slave_name); static int pty_open_slave(const char *prog, char *slave_name); static int pty_child(const char *prog, int slave, char *argv[]); static int pty_parent(const char *prog, int cntrl); static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff); static GL_FD_EVENT_FN(pty_read_from_program); static int pty_write_to_fd(int fd, const char *string, int n); static void pty_child_exited(int sig); static int pty_master_readable(int fd, long usec); /*....................................................................... * Run a program with enhanced terminal editing facilities. * * Usage: * enhance program [args...] */ int main(int argc, char *argv[]) { int cntrl = -1; /* The fd of the pseudo-terminal controller device */ int slave = -1; /* The fd of the pseudo-terminal slave device */ pid_t pid; /* The return value of fork() */ int status; /* The return statuses of the parent and child functions */ char slave_name[PTY_MAX_NAME]; /* The filename of the slave end of the */ /* pseudo-terminal. */ char *prog; /* The name of the program (ie. argv[0]) */ /* * Check the arguments. */ if(argc < 2) { fprintf(stderr, "Usage: %s [arguments...]\n", argv[0]); return 1; }; /* * Get the name of the program. */ prog = argv[0]; /* * If the user has the LC_CTYPE or LC_ALL environment variables set, * enable display of characters corresponding to the specified locale. */ (void) setlocale(LC_CTYPE, ""); /* * If the program is taking its input from a pipe or a file, or * sending its output to something other than a terminal, run the * program without tecla. */ if(!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { if(execvp(argv[1], argv + 1) < 0) { fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[1], strerror(errno)); fflush(stderr); _exit(1); }; }; /* * Open the master side of a pseudo-terminal pair, and return * the corresponding file descriptor and the filename of the * slave end of the pseudo-terminal. */ if(pty_open_master(prog, &cntrl, slave_name)) return 1; /* * Set up a signal handler to watch for the child process exiting. */ signal(SIGCHLD, pty_child_exited); /* * The above signal handler sends the parent process a SIGINT signal. * This signal is caught by gl_get_line(), which resets the terminal * settings, and if the application signal handler for this signal * doesn't abort the process, gl_get_line() returns NULL with errno * set to EINTR. Arrange to ignore the signal, so that gl_get_line() * returns and we have a chance to cleanup. */ signal(SIGINT, SIG_IGN); /* * We will read user input in one process, and run the user's program * in a child process. */ pid = fork(); if(pid < 0) { fprintf(stderr, "%s: Unable to fork child process (%s).\n", prog, strerror(errno)); return 1; }; /* * Are we the parent? */ if(pid!=0) { status = pty_parent(prog, cntrl); close(cntrl); } else { close(cntrl); /* The child doesn't use the slave device */ signal(SIGCHLD, pty_child_exited); if((slave = pty_open_slave(prog, slave_name)) >= 0) { status = pty_child(prog, slave, argv + 1); close(slave); } else { status = 1; }; }; return status; } /*....................................................................... * Open the master side of a pseudo-terminal pair, and return * the corresponding file descriptor and the filename of the * slave end of the pseudo-terminal. * * Input/Output: * prog const char * The name of this program. * cntrl int * The file descriptor of the pseudo-terminal * controller device will be assigned tp *cntrl. * slave_name char * The file-name of the pseudo-terminal slave device * will be recorded in slave_name[], which must have * at least PTY_MAX_NAME elements. * Output: * return int 0 - OK. * 1 - Error. */ static int pty_open_master(const char *prog, int *cntrl, char *slave_name) { char master_name[PTY_MAX_NAME]; /* The filename of the master device */ DIR *dir; /* The directory iterator */ struct dirent *file; /* A file in "/dev" */ /* * Mark the controller device as not opened yet. */ *cntrl = -1; /* * On systems with the Sys-V pseudo-terminal interface, we don't * have to search for a free master terminal. We just open /dev/ptmx, * and if there is a free master terminal device, we are given a file * descriptor connected to it. */ #if HAVE_SYSV_PTY *cntrl = open("/dev/ptmx", O_RDWR); if(*cntrl >= 0) { /* * Get the filename of the slave side of the pseudo-terminal. */ char *name = ptsname(*cntrl); if(name) { if(strlen(name)+1 > PTY_MAX_NAME) { fprintf(stderr, "%s: Slave pty filename too long.\n", prog); return 1; }; strcpy(slave_name, name); /* * If unable to get the slave name, discard the controller file descriptor, * ready to try a search instead. */ } else { close(*cntrl); *cntrl = -1; }; } else { #endif /* * On systems without /dev/ptmx, or if opening /dev/ptmx failed, * we open one master terminal after another, until one that isn't * in use by another program is found. * * Open the devices directory. */ dir = opendir(PTY_DEV_DIR); if(!dir) { fprintf(stderr, "%s: Couldn't open %s (%s)\n", prog, PTY_DEV_DIR, strerror(errno)); return 1; }; /* * Look for pseudo-terminal controller device files in the devices * directory. */ while(*cntrl < 0 && (file = readdir(dir))) { if(strncmp(file->d_name, PTY_CNTRL, sizeof(PTY_CNTRL)-1) == 0) { /* * Get the common extension of the control and slave filenames. */ const char *ext = file->d_name + sizeof(PTY_CNTRL)-1; if(strlen(ext) > PTY_MAX_SUFFIX) continue; /* * Attempt to open the control file. */ strcpy(master_name, PTY_DEV_DIR); strcat(master_name, PTY_CNTRL); strcat(master_name, ext); *cntrl = open(master_name, O_RDWR); if(*cntrl < 0) continue; /* * Attempt to open the matching slave file. */ strcpy(slave_name, PTY_DEV_DIR); strcat(slave_name, PTY_SLAVE); strcat(slave_name, ext); }; }; closedir(dir); #if HAVE_SYSV_PTY }; #endif /* * Did we fail to find a pseudo-terminal pair that we could open? */ if(*cntrl < 0) { fprintf(stderr, "%s: Unable to find a free pseudo-terminal.\n", prog); return 1; }; /* * System V systems require the program that opens the master to * grant access to the slave side of the pseudo-terminal. */ #ifdef HAVE_SYSV_PTY if(grantpt(*cntrl) < 0 || unlockpt(*cntrl) < 0) { fprintf(stderr, "%s: Unable to unlock terminal (%s).\n", prog, strerror(errno)); return 1; }; #endif /* * Success. */ return 0; } /*....................................................................... * Open the slave end of a pseudo-terminal. * * Input: * prog const char * The name of this program. * slave_name char * The filename of the slave device. * Output: * return int The file descriptor of the successfully opened * slave device, or < 0 on error. */ static int pty_open_slave(const char *prog, char *slave_name) { int fd; /* The file descriptor of the slave device */ /* * Place the process in its own process group. In system-V based * OS's, this ensures that when the pseudo-terminal is opened, it * becomes the controlling terminal of the process. */ if(setsid() < 0) { fprintf(stderr, "%s: Unable to form new process group (%s).\n", prog, strerror(errno)); return -1; }; /* * Attempt to open the specified device. */ fd = open(slave_name, O_RDWR); if(fd < 0) { fprintf(stderr, "%s: Unable to open pseudo-terminal slave device (%s).\n", prog, strerror(errno)); return -1; }; /* * On system-V streams based systems, we need to push the stream modules * that implement pseudo-terminal and termio interfaces. At least on * Solaris, which pushes these automatically when a slave is opened, * this is redundant, so ignore errors when pushing the modules. */ #if HAVE_SYSV_PTY (void) ioctl(fd, I_PUSH, "ptem"); (void) ioctl(fd, I_PUSH, "ldterm"); /* * On BSD based systems other than SunOS 4.x, the following makes the * pseudo-terminal the controlling terminal of the child process. * According to the pseudo-terminal example code in Steven's * Advanced programming in the unix environment, the !defined(CIBAUD) * part of the clause prevents this from being used under SunOS. Since * I only have his code with me, and won't have access to the book, * I don't know why this is necessary. */ #elif defined(TIOCSCTTY) && !defined(CIBAUD) if(ioctl(fd, TIOCSCTTY, (char *) 0) < 0) { fprintf(stderr, "%s: Unable to establish controlling terminal (%s).\n", prog, strerror(errno)); close(fd); return -1; }; #endif return fd; } /*....................................................................... * Read input from the controlling terminal of the program, using * gl_get_line(), and feed it to the user's program running in a child * process, via the controller side of the pseudo-terminal. Also pass * data received from the user's program via the conroller end of * the pseudo-terminal, to stdout. * * Input: * prog const char * The name of this program. * cntrl int The file descriptor of the controller end of the * pseudo-terminal. * Output: * return int 0 - OK. * 1 - Error. */ static int pty_parent(const char *prog, int cntrl) { GetLine *gl = NULL; /* The gl_get_line() resource object */ char *line; /* An input line read from the user */ char *rbuff=NULL; /* A buffer for reading from the pseudo terminal */ /* * Allocate the gl_get_line() resource object. */ gl = new_GetLine(PTY_MAX_LINE, PTY_HIST_SIZE); if(!gl) return pty_stop_parent(1, cntrl, gl, rbuff); /* * Allocate a buffer to use to accumulate bytes read from the * pseudo-terminal. */ rbuff = (char *) malloc(PTY_MAX_READ+1); if(!rbuff) return pty_stop_parent(1, cntrl, gl, rbuff); rbuff[0] = '\0'; /* * Register an event handler to watch for data appearing from the * user's program on the controller end of the pseudo terminal. */ if(gl_watch_fd(gl, cntrl, GLFD_READ, pty_read_from_program, rbuff)) return pty_stop_parent(1, cntrl, gl, rbuff); /* * Read input lines from the user and pass them on to the user's program, * by writing to the controller end of the pseudo-terminal. */ while((line=gl_get_line(gl, rbuff, NULL, 0))) { if(pty_write_to_fd(cntrl, line, strlen(line))) return pty_stop_parent(1, cntrl, gl, rbuff); rbuff[0] = '\0'; }; return pty_stop_parent(0, cntrl, gl, rbuff); } /*....................................................................... * This is a private return function of pty_parent(), used to release * dynamically allocated resources, close the controller end of the * pseudo-terminal, and wait for the child to exit. It returns the * exit status of the child process, unless the caller reports an * error itself, in which case the caller's error status is returned. * * Input: * waserr int True if the caller is calling this function because * an error occured. * cntrl int The file descriptor of the controller end of the * pseudo-terminal. * gl GetLine * The resource object of gl_get_line(). * rbuff char * The buffer used to accumulate bytes read from * the pseudo-terminal. * Output: * return int The desired exit status of the program. */ static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff) { int status; /* The return status of the child process */ /* * Close the controller end of the terminal. */ close(cntrl); /* * Delete the resource object. */ gl = del_GetLine(gl); /* * Delete the read buffer. */ if(rbuff) free(rbuff); /* * Wait for the user's program to end. */ (void) wait(&status); /* * Return either our error status, or the return status of the child * program. */ return waserr ? 1 : status; } /*....................................................................... * Run the user's program, with its stdin and stdout connected to the * slave end of the psuedo-terminal. * * Input: * prog const char * The name of this program. * slave int The file descriptor of the slave end of the * pseudo terminal. * argv char *[] The argument vector to pass to the user's program, * where argv[0] is the name of the user's program, * and the last argument is followed by a pointer * to NULL. * Output: * return int If this function returns at all, an error must * have occured when trying to overlay the process * with the user's program. In this case 1 is * returned. */ static int pty_child(const char *prog, int slave, char *argv[]) { struct termios attr; /* The terminal attributes */ /* * We need to stop the pseudo-terminal from echoing everything that we send it. */ if(tcgetattr(slave, &attr)) { fprintf(stderr, "%s: Can't get pseudo-terminal attributes (%s).\n", prog, strerror(errno)); return 1; }; attr.c_lflag &= ~(ECHO); while(tcsetattr(slave, TCSADRAIN, &attr)) { if(errno != EINTR) { fprintf(stderr, "%s: tcsetattr error: %s\n", prog, strerror(errno)); return 1; }; }; /* * Arrange for stdin, stdout and stderr to be connected to the slave device, * ignoring errors that imply that either stdin or stdout is closed. */ while(dup2(slave, STDIN_FILENO) < 0 && errno==EINTR) ; while(dup2(slave, STDOUT_FILENO) < 0 && errno==EINTR) ; while(dup2(slave, STDERR_FILENO) < 0 && errno==EINTR) ; /* * Run the user's program. */ if(execvp(argv[0], argv) < 0) { fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[0], strerror(errno)); fflush(stderr); _exit(1); }; return 0; /* This should never be reached */ } /*....................................................................... * This is the event-handler that is called by gl_get_line() whenever * there is tet waiting to be read from the user's program, via the * controller end of the pseudo-terminal. See libtecla.h for details * about its arguments. */ static GL_FD_EVENT_FN(pty_read_from_program) { char *nlptr; /* A pointer to the last newline in the accumulated string */ char *crptr; /* A pointer to the last '\r' in the accumulated string */ char *nextp; /* A pointer to the next unprocessed character */ /* * Get the read buffer in which we are accumulating a line to be * forwarded to stdout. */ char *rbuff = (char *) data; /* * New data may arrive while we are processing the current read, and * it is more efficient to display this here than to keep returning to * gl_get_line() and have it display the latest prefix as a prompt, * followed by the current input line, so we loop, delaying a bit at * the end of each iteration to check for more data arriving from * the application, before finally returning to gl_get_line() when * no more input is available. */ do { /* * Get the current length of the output string. */ int len = strlen(rbuff); /* * Read the text from the program. */ int nnew = read(fd, rbuff + len, PTY_MAX_READ - len); if(nnew < 0) return GLFD_ABORT; len += nnew; /* * Nul terminate the accumulated string. */ rbuff[len] = '\0'; /* * Find the last newline and last carriage return in the buffer, if any. */ nlptr = strrchr(rbuff, '\n'); crptr = strrchr(rbuff, '\r'); /* * We want to output up to just before the last newline or carriage * return. If there are no newlines of carriage returns in the line, * and the buffer is full, then we should output the whole line. In * all cases a new output line will be started after the latest text * has been output. The intention is to leave any incomplete line * in the buffer, for (perhaps temporary) use as the current prompt. */ if(nlptr) { nextp = crptr && crptr < nlptr ? crptr : nlptr; } else if(crptr) { nextp = crptr; } else if(len >= PTY_MAX_READ) { nextp = rbuff + len; } else { nextp = NULL; }; /* * Do we have any text to output yet? */ if(nextp) { /* * If there was already some text in rbuff before this function * was called, then it will have been used as a prompt. Arrange * to rewrite this prefix, plus the new suffix, by moving back to * the start of the line. */ if(len > 0) (void) pty_write_to_fd(STDOUT_FILENO, "\r", 1); /* * Write everything up to the last newline to stdout. */ (void) pty_write_to_fd(STDOUT_FILENO, rbuff, nextp - rbuff); /* * Start a new line. */ (void) pty_write_to_fd(STDOUT_FILENO, "\r\n", 2); /* * Skip trailing carriage returns and newlines. */ while(*nextp=='\n' || *nextp=='\r') nextp++; /* * Move any unwritten text following the newline, to the start of the * buffer. */ memmove(rbuff, nextp, len - (nextp - rbuff) + 1); }; } while(pty_master_readable(fd, PTY_READ_TIMEOUT)); /* * Make the incomplete line in the output buffer the current prompt. */ gl_replace_prompt(gl, rbuff); return GLFD_REFRESH; } /*....................................................................... * Write a given string to a specified file descriptor. * * Input: * fd int The file descriptor to write to. * string const char * The string to write (of at least 'n' characters). * n int The number of characters to write. * Output: * return int 0 - OK. * 1 - Error. */ static int pty_write_to_fd(int fd, const char *string, int n) { int ndone = 0; /* The number of characters written so far */ /* * Do as many writes as are needed to write the whole string. */ while(ndone < n) { int nnew = write(fd, string + ndone, n - ndone); if(nnew > 0) ndone += nnew; else if(errno != EINTR) return 1; }; return 0; } /*....................................................................... * This is the signal handler that is called when the child process * that is running the user's program exits for any reason. It closes * the slave end of the terminal, so that gl_get_line() in the parent * process sees an end of file. */ static void pty_child_exited(int sig) { raise(SIGINT); } /*....................................................................... * Return non-zero after a given amount of time if there is data waiting * to be read from a given file descriptor. * * Input: * fd int The descriptor to watch. * usec long The number of micro-seconds to wait for input to * arrive before giving up. * Output: * return int 0 - No data is waiting to be read (or select isn't * available). * 1 - Data is waiting to be read. */ static int pty_master_readable(int fd, long usec) { #if HAVE_SELECT fd_set rfds; /* The set of file descriptors to check */ struct timeval timeout; /* The timeout */ FD_ZERO(&rfds); FD_SET(fd, &rfds); timeout.tv_sec = 0; timeout.tv_usec = usec; return select(fd+1, &rfds, NULL, NULL, &timeout) == 1; #else return 0; #endif } ./libtecla/demo3.c0100644000076400007640000006330510036356722012317 0ustar mcsmcs/* * Copyright (c) 2002, 2003, 2004 by Martin C. Shepherd * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include #include #ifdef HAVE_SELECT #ifdef HAVE_SYS_SELECT_H #include #endif #endif #include #include #include #include #include #include "libtecla.h" /* * The SignalActions object provides a way to temporarily install * a signal handler to a given set of signals, and later restore all * of the signal handlers that this displaced. */ typedef struct { int nsignal; /* The number of signals on the host OS */ sigset_t mask; /* The set of signals who's signal handlers */ /* are stored in the following actions[] */ /* array. */ struct sigaction *actions; /* An array of nsignal actions */ } SignalActions; static SignalActions *new_SignalActions(void); static SignalActions *del_SignalActions(SignalActions *si); static int displace_signal_handlers(SignalActions *si, sigset_t *mask, void (*handler)(int)); static int reinstate_signal_handlers(SignalActions *si); /* Return resources, restore the terminal to a usable state and exit */ static void cleanup_and_exit(GetLine *gl, SignalActions *si, int status); /* The function which displays the introductory text of the demo */ static void show_demo_introduction(GetLine *gl); /* A signal-aware version of select() */ static int demo_sigselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout, sigset_t *mask, SignalActions *si); /* * The following variables are accessed from signal handlers. Note * that these variables don't need to be either volatile or * sig_atomic_t because: * * 1. Outside of signal handlers we only access them when signal * delivery is blocked, so we know that no signal handlers can * be accessing them at that time. * * 2. When the signal handlers that set these variables are installed, * the sa_mask member of the sigaction structure is used to ensure * that only one instance of these signal handlers can be running * at a time, so we also know that there can't be simultaneous * accesses to them by multiple signal handlers. */ static GetLine *demo_gl; /* The line editor object */ static sigjmp_buf demo_setjmp_buffer; /* The sigsetjmp() buffer */ static int demo_setjmp_signo = -1; /* The signal that was caught */ /* Signal handlers */ static void demo_signal_handler(int signo); static void demo_setjmp_handler(int signo); /* * Set the amount of time that gl_get_line() should wait for I/O before * returning to let the external event loop continue. */ #define DEMO_IO_TIMEOUT 100000000 /* ns => 100ms */ /* The timeout handler */ static GL_TIMEOUT_FN(demo_timeout_fn); /*....................................................................... * This program demonstrates the use of gl_get_line() from an external * event loop. It takes no arguments. */ int main(int argc, char *argv[]) { int major,minor,micro; /* The version number of the library */ GetLine *gl=NULL; /* The resource object of gl_get_line() */ SignalActions *si=NULL; /* Temporary storage of displaced signal */ /* handlers. */ sigset_t all_signal_mask; /* The set of signals known by gl_get_line() */ /* * This program requires select(). */ #if !defined(HAVE_SELECT) fprintf(stderr, "The select() system call isn't available - aborting.\n"); exit(1); #else /* * Create the line editor, specifying a maximum line length of 500 bytes, * and 10000 bytes to allocate to storage of historical input lines. */ gl = demo_gl = new_GetLine(500, 5000); if(!gl) cleanup_and_exit(gl, si, 1); /* * Allocate an object in which to temporarily record displaced * signal handlers. */ si = new_SignalActions(); if(!si) cleanup_and_exit(gl, si, 1); /* * If the user has the LC_CTYPE or LC_ALL environment variables set, * enable display of characters corresponding to the specified locale. */ (void) setlocale(LC_CTYPE, ""); /* * Lookup and display the version number of the library. */ libtecla_version(&major, &minor, µ); printf( "\n Welcome to the server-mode demo program of libtecla version %d.%d.%d\n", major, minor, micro); /* * Display some introductory text, left-justifying it within the current * width of the terminal and enclosing it in a box of asterixes. */ show_demo_introduction(gl); /* * Load history. */ #ifndef WITHOUT_FILE_SYSTEM (void) gl_load_history(gl, "~/.demo_history", "#"); #endif /* * In this demo, rather than having gl_get_line() return immediately * when it would otherwise have to wait for I/O, we register a timeout * callback which causes gl_get_line() to give up waiting after a short * interval. */ gl_inactivity_timeout(gl, demo_timeout_fn, NULL, 0, DEMO_IO_TIMEOUT); /* * Install our signal handlers for process termination, suspension and * terminal resize signals. Ignore process continuation signals. */ gl_tty_signals(demo_signal_handler, demo_signal_handler, SIG_DFL, demo_signal_handler); /* * Get a list of all of the signals that gl_get_line() currently catches. */ gl_list_signals(gl, &all_signal_mask); /* * Switch gl_get_line() to non-blocking server mode. */ if(gl_io_mode(gl, GL_SERVER_MODE)) cleanup_and_exit(gl, si, 1); /* * Instruct gl_get_line() to unblock any signals that it catches * while waiting for input. Note that in non-blocking server mode, * this is only necessary when using gl_inactivity_timeout() to make * gl_get_line() block for a non-zero amount of time. */ gl_catch_blocked(gl); /* * Enter the event loop. */ while(1) { int nready; /* The number of file-descriptors that are */ /* ready for I/O */ fd_set rfds; /* The set of file descriptors to watch for */ /* readability */ fd_set wfds; /* The set of file descriptors to watch for */ /* writability */ /* * Construct the sets of file descriptors to be watched by select(), * starting from empty sets. */ FD_ZERO(&rfds); FD_ZERO(&wfds); /* * To ensure that no signals are received whos handlers might change * the requirements for the contents of the above signal sets, block * all of the signals that we are handling. */ sigprocmask(SIG_BLOCK, &all_signal_mask, NULL); /* * Depending on which direction of I/O gl_get_line()s is currently * waiting for, add the terminal file descriptor to either the set * of file descriptors to watch for readability, or those to watch * for writability. Note that at the start of a new line, such as * after an error, or the return of a completed line, we need to * wait for writability, so that a prompt can be written. */ switch(gl_pending_io(gl)) { case GLP_READ: FD_SET(STDIN_FILENO, &rfds); break; default: FD_SET(STDIN_FILENO, &wfds); break; }; /* * Wait for I/O to become possible on the selected file descriptors. * The following is a signal-aware wrapper around the select() system * call. This wrapper guarantees that if any of the signals marked in * all_signal_mask arrive after the statement above where we blocked * these signals, it will detect this and abort with nready=-1 and * errno=EINTR. If instead, we just unblocked the above signals just * before calling a normal call to select(), there would be a small * window of time between those two statements in which a signal could * arrive without aborting select(). This would be a problem, since * the functions called by our signal handler may change the type * of I/O that gl_get_line() wants us to wait for in select(). */ nready = demo_sigselect(STDIN_FILENO + 1, &rfds, &wfds, NULL, NULL, &all_signal_mask, si); /* * We can now unblock our signals again. */ sigprocmask(SIG_UNBLOCK, &all_signal_mask, NULL); /* * Did an I/O error occur? */ if(nready < 0 && errno != EINTR) cleanup_and_exit(gl, si, 1); /* * If the terminal file descriptor is now ready for I/O, call * gl_get_line() to continue editing the current input line. */ if(FD_ISSET(STDIN_FILENO, &rfds) || FD_ISSET(STDIN_FILENO, &wfds)) { /* * Start or continue editing an input line. */ char *line = gl_get_line(gl, "$ ", NULL, 0); /* * Did the user finish entering a new line? */ if(line) { /* * Before writing messages to the terminal, start a new line and * switch back to normal terminal I/O. */ gl_normal_io(gl); /* * Display what was entered. */ if(printf("You entered: %s", line) < 0 || fflush(stdout)) break; /* * Implement a few simple commands. */ if(strcmp(line, "exit\n")==0) cleanup_and_exit(gl, si, 0); else if(strcmp(line, "history\n")==0) gl_show_history(gl, stdout, "%N %T %H\n", 0, -1); else if(strcmp(line, "size\n")==0) { GlTerminalSize size = gl_terminal_size(gl, 80, 24); printf("Terminal size = %d columns x %d lines.\n", size.ncolumn, size.nline); } else if(strcmp(line, "clear\n")==0) { if(gl_erase_terminal(gl)) return 1; }; /* * To resume command-line editing, return the terminal to raw, * non-blocking I/O mode. */ gl_raw_io(gl); /* * If gl_get_line() returned NULL because of an error or end-of-file, * abort the program. */ } else if(gl_return_status(gl) == GLR_ERROR || gl_return_status(gl) == GLR_EOF) { cleanup_and_exit(gl, si, 1); }; }; }; #endif return 0; } /*....................................................................... * This function is called to return resources to the system and restore * the terminal to its original state before exiting the process. * * Input: * gl GetLine * The line editor. * si SignalActions * The repository for displaced signal handlers. * status int The exit code of the process. */ static void cleanup_and_exit(GetLine *gl, SignalActions *si, int status) { /* * Restore the terminal to its original state before exiting the program. */ gl_normal_io(gl); /* * Save historical command lines. */ #ifndef WITHOUT_FILE_SYSTEM (void) gl_save_history(gl, "~/.demo_history", "#", -1); #endif /* * Clean up. */ gl = del_GetLine(gl); si = del_SignalActions(si); /* * Exit the process. */ exit(status); } /*....................................................................... * This is a signal-aware wrapper around the select() system call. It * is designed to facilitate reliable signal handling of a given set * of signals, without the race conditions that would usually surround * the use of select(). See the "RELIABLE SIGNAL HANDLING" section of * the gl_get_line(3) man page for further details. * * Provided that the calling function has blocked the specified set of * signals before calling this function, this function guarantees that * select() will be aborted by any signal that arrives between the * time that the caller blocked the specified signals and this * function returns. On return these signals will again be blocked to * prevent any signals that arrive after select() returns, from being * missed by the caller. * * Note that this function is written not to be specific to this * program, and is thus suitable for use in other programs, whether or * not they use gl_get_line(). * * Also note that this function depends on the NSIG preprocessor * constant being >= the maximum number of signals available on the * host operating system. Under BSD and SysV, this macro is set * appropriately in signal.h. On other systems, a reasonably large * guess should be substituted. Although nothing terrible will happen * if a value that is too small is chosen, signal numbers that exceed * the specified value of NSIG will be ignored by this function. A * more robust method than depending on nsig would be to use the * POSIX sigismember() function to count valid signals, and use this * to allocate the array of sigaction structures used to preserve * * * Input: * n int The number of file descriptors to pay * attention to at the start of each of the * following sets of file descriptors. * readfds fd_set * The set of file descriptors to check for * readability, or NULL if not pertinent. * wwritefds fd_set * The set of file descriptors to check for * writability, or NULL if not pertinent. * exceptfds fd_set * The set of file descriptors to check for * the arrival of urgent data, or NULL if * not pertinent. * timeout struct timeval * The maximum time that select() should * wait, or NULL to wait forever. * mask sigset_t * The set of signals to catch. * si SignalHandlers * An object in which to preserve temporary * copies signal handlers. * Output: * return int > 0 The number of entries in all of the * sets of descriptors that are ready * for I/O. * 0 Select() timed out. * -1 Error (see errno). */ static int demo_sigselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout, sigset_t *mask, SignalActions *si) { /* * The reason that the the following variables are marked as volatile * is to prevent the compiler from placing their values in registers * that might not be saved and restored by sigsetjmp(). */ volatile sigset_t old_mask; /* The displaced process signal mask */ volatile int status; /* The return value of select() */ /* * Make sure that all of the specified signals are blocked. This is * redundant if the caller has already blocked signals. */ if(sigprocmask(SIG_BLOCK, mask, (sigset_t *) &old_mask) < 0) return -1; /* * Record the fact that no signal has been caught yet. */ demo_setjmp_signo = -1; /* * Now set up the point where our temporary signal handlers will return * control if a signal is received. */ if(sigsetjmp(demo_setjmp_buffer, 1) == 0) { /* * Now install the temporary signal handlers that cause the above * sigsetjmp() to return non-zero when a signal is detected. */ if(displace_signal_handlers(si, mask, demo_setjmp_handler)) { reinstate_signal_handlers(si); return 1; }; /* * Now that we are ready to catch the signals, unblock them. */ sigprocmask(SIG_UNBLOCK, mask, NULL); /* * At last, call select(). */ status = select(n, readfds, writefds, exceptfds, timeout); /* * Block the specified signals again. */ sigprocmask(SIG_BLOCK, mask, NULL); /* * Record the fact that no signal was caught. */ demo_setjmp_signo = -1; }; /* * We can get to this point in one of two ways. Either no signals were * caught, and the above block ran to completion (with demo_setjmp_signo=-1), * or a signal was caught that caused the above block to be aborted, * in which case demo_setjmp_signo will now equal the number of the signal that * was caught, and sigsetjmp() will have restored the process signal * mask to how it was before it was called (ie. all of the specified * signals blocked). * * First restore the signal handlers to how they were on entry to * this function. */ reinstate_signal_handlers(si); /* * Was a signal caught? */ if(demo_setjmp_signo > 0) { sigset_t new_mask; /* * Send the signal again, then unblock its delivery, so that the application's * signal handler gets invoked. */ raise(demo_setjmp_signo); sigemptyset(&new_mask); sigaddset(&new_mask, demo_setjmp_signo); sigprocmask(SIG_UNBLOCK, &new_mask, NULL); /* * Set the return status to show that a signal was caught. */ errno = EINTR; status = -1; }; /* * Now restore the process signal mask to how it was on entry to this * function. */ sigprocmask(SIG_SETMASK, (sigset_t *) &old_mask, NULL); return status; } /*....................................................................... * This is the main signal handler of this demonstration program. If a * SIGINT is received by the process, it arranges that the next call * to gl_get_line() will abort entry of the current line and start * entering a new one. Otherwise it calls the library function which * handles terminal resize signals and process suspension and process * termination signals. Both of the functions called by this signal * handler are designed to be async-signal safe, provided that the * rules laid out in the gl_io_mode(3) man page are followed. */ static void demo_signal_handler(int signo) { if(signo==SIGINT) gl_abandon_line(demo_gl); else gl_handle_signal(signo, demo_gl, 1); } /*....................................................................... * The following signal handler is installed while select() is being * called from within a block of code protected by sigsetjmp(). It * simply records the signal that was caught in setjmp_signo, then * causes the sigsetjmp() to return non-zero. */ static void demo_setjmp_handler(int signo) { demo_setjmp_signo = signo; siglongjmp(demo_setjmp_buffer, 1); } /*....................................................................... * This optional inactivity timeout function is used in this * demonstration to cause gl_get_line() to wait for a small amount of * time for I/O, before returning and allowing the event loop to * continue. This isn't needed if you want gl_get_line() to return * immediately, rather than blocking. */ static GL_TIMEOUT_FN(demo_timeout_fn) { return GLTO_CONTINUE; } /*....................................................................... * Display introductory text to the user, formatted according to the * current terminal width and enclosed in a box of asterixes. * * Input: * gl GetLine * The resource object of gl_get_line(). */ static void show_demo_introduction(GetLine *gl) { int start; /* The column in which gl_display_text() left the cursor */ int i; /* * Break the indtroductory text into an array of strings, so as to * avoid overflowing any compiler string limits. */ const char *doc[] = { "To the user this program appears to act identically to the main ", "demo program. However whereas the code underlying the main demo ", "program uses gl_get_line() in its default configuration, where each ", "call blocks the caller until the user has entered a complete input ", "line, demo3 uses gl_get_line() in its non-blocking server mode, ", "where it must be called repeatedly from an external ", "event loop to incrementally accept entry of the input ", "line, as and when terminal I/O becomes possible. The well commented ", "source code of demo3, which can be found in demo3.c, thus provides ", "a working example of how to use gl_get_line() in a manner that ", "doesn't block the caller. Documentation of this mode can be found ", "in the gl_io_mode(3) man page.\n" }; /* * Form the top line of the documentation box by filling the area of * the line between a " *" prefix and a "* " suffix with asterixes. */ printf("\n"); gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); /* * Justify the documentation text within margins of asterixes. */ for(start=0,i=0; i= 0; i++) start = gl_display_text(gl, 0, " * ", " * ", ' ', 80, start,doc[i]); /* * Draw the bottom line of the documentation box. */ gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n"); printf("\n"); } /*....................................................................... * This is a constructor function for an object who's role is to allow * a signal handler to be assigned to potentially all available signals, * while preserving a copy of the original signal handlers, for later * restration. * * Output: * return SignalActions * The new object, or NULL on error. */ static SignalActions *new_SignalActions(void) { SignalActions *si; /* The object to be returned */ /* * Allocate the container. */ si = malloc(sizeof(SignalActions)); if(!si) { fprintf(stderr, "new_SignalActions: Insufficient memory.\n"); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_SignalActions(). */ si->nsignal = 0; sigemptyset(&si->mask); si->actions = NULL; /* * Count the number of signals that are available of the host * platform. Note that si->mask has no members set, and that * sigismember() is defined to return -1 if the signal number * isn't valid. */ for(si->nsignal=1; sigismember(&si->mask, si->nsignal) == 0; si->nsignal++) ; /* * Allocate the array of sigaction structures to use to keep a record * of displaced signal handlers. */ si->actions = (struct sigaction *) malloc(sizeof(*si->actions) * si->nsignal); if(!si->actions) { fprintf(stderr, "Insufficient memory for %d sigaction structures.\n", si->nsignal); return del_SignalActions(si); }; return si; } /*....................................................................... * Delete a SignalActions object. * * Input: * si SignalActions * The object to be deleted. * Output: * return SignalActions * The deleted object (always NULL). */ static SignalActions *del_SignalActions(SignalActions *si) { if(si) { if(si->actions) free(si->actions); free(si); }; return NULL; } /*....................................................................... * Replace the signal handlers of all of the signals in 'mask' with * the signal handler 'handler'. * * Input: * si SignalActions * The object in which to record the displaced * signal handlers. * mask sigset_t * The set of signals who's signal handlers * should be displaced. * handler void (*handler)(int) The new signal handler to assign to each * of the signals marked in 'mask'. * Output: * return int 0 - OK. * 1 - Error. */ static int displace_signal_handlers(SignalActions *si, sigset_t *mask, void (*handler)(int)) { int signo; /* A signal number */ struct sigaction action; /* The new signal handler */ /* * Mark the fact that so far we haven't displaced any signal handlers. */ sigemptyset(&si->mask); /* * Set up the description of the new signal handler. Note that * we make sa_mask=mask. This ensures that only one instance of the * signal handler will ever be running at one time. */ action.sa_handler = handler; memcpy(&action.sa_mask, mask, sizeof(*mask)); action.sa_flags = 0; /* * Check each of the available signals to see if it is specified in 'mask'. * If so, install the new signal handler, record the displaced one in * the corresponding element of si->actions[], and make a record in * si->mask that this signal handler has been displaced. */ for(signo=1; signo < si->nsignal; signo++) { if(sigismember(mask, signo)) { if(sigaction(signo, &action, &si->actions[signo]) < 0) { fprintf(stderr, "sigaction error (%s)\n", strerror(errno)); return 1; }; sigaddset(&si->mask, signo); }; }; return 0; } /*....................................................................... * Reinstate any signal handlers displaced by displace_signal_handlers(). * * Input: * sig SignalActions * The object containing the displaced signal * handlers. * Output: * return int 0 - OK. * 1 - Error. */ static int reinstate_signal_handlers(SignalActions *si) { int signo; /* A signal number */ /* * Check each of the available signals to see if it is specified in * si->mask. If so, reinstate the displaced recorded in the * corresponding element of si->actions[], and make a record in * si->mask that this signal handler has been reinstated. */ for(signo=1; signo < si->nsignal; signo++) { if(sigismember(&si->mask, signo)) { if(sigaction(signo, &si->actions[signo], NULL) < 0) { fprintf(stderr, "sigaction error (%s)\n", strerror(errno)); return 1; }; sigdelset(&si->mask, signo); }; }; return 0; } ./libtecla/man/0040755000076400007640000000000007600313516011710 5ustar mcsmcs./libtecla/man/misc/0040755000076400007640000000000010141252407012636 5ustar mcsmcs./libtecla/man/misc/tecla.in0100644000076400007640000014626010036412122014257 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH tecla @MISC_MANEXT@ .SH NAME tecla, teclarc \- The user interface provided by the Tecla library. .SH DESCRIPTION This man page describes the command-line editing features that are available to users of programs that read keyboard input via the Tecla library. Users of the tcsh shell will find the default key-bindings very familiar. Users of the bash shell will also find it quite familiar, but with a few minor differences, most notably in how forward and backward searches through the list of historical commands are performed. There are two major editing modes, one with emacs-like key-bindings and another with vi-like key-bindings. By default emacs mode is enabled, but vi mode can alternatively be selected via the user's configuration file. This file can also be used to change the bindings of individual keys to suit the user's preferences. By default, tab completion is provided. If the application hasn't reconfigured this to complete other types of symbols, then tab completion completes file-names. .SH KEY SEQUENCE NOTATION In the rest of this man page, and also in all Tecla configuration files, key-sequences are expressed as follows. .sp .nf \f3^A\f1 or \f3C-a\f1 This is a control-A, entered by pressing the control key at the same time as the \f3A\f1 key. \f3\\E\f1 or \f3M-\f1 In key-sequences, both of these notations can be entered either by pressing the escape key, then the following key, or by pressing the Meta key at the same time as the following key. Thus the key sequence \f3M-p\f1 can be typed in two ways, by pressing the escape key, followed by pressing \f3p\f1, or by pressing the Meta key at the same time as \f3p\f1. \f3up\f1 This refers to the up-arrow key. \f3down\f1 This refers to the down-arrow key. \f3left\f1 This refers to the left-arrow key. \f3right\f1 This refers to the right-arrow key. \f3a\f1 This is just a normal A key. .fi .sp .SH THE TECLA CONFIGURATION FILE By default, Tecla looks for a file called \f3\&.teclarc\f1 in your home directory (ie. \f3~/.teclarc\f1). If it finds this file, it reads it, interpreting each line as defining a new key binding or an editing configuration option. Since the emacs keybindings are installed by default, if you want to use the non-default vi editing mode, the most important item to go in this file is the following line: .nf edit-mode vi .fi This will re-configure the default bindings for vi-mode. The complete set of arguments that this command accepts are: .sp .nf vi - Install key-bindings like those of the vi editor. emacs - Install key-bindings like those of the emacs editor. This is the default. none - Use just the native line editing facilities provided by the terminal driver. .fi .sp To prevent the terminal bell from being rung, such as when an unrecognized control-sequence is typed, place the following line in the configuration file: .nf nobeep .fi An example of a key binding line in the configuration file is the following. .nf bind M-[2~ insert-mode .fi On many keyboards, the above key sequence is generated when one presses the \f3insert\f1 key, so with this keybinding, one can toggle between the emacs-mode insert and overwrite modes by hitting one key. One could also do it by typing out the above sequence of characters one by one. As explained above, the \f3M-\f1 part of this sequence can be typed either by pressing the escape key before the following key, or by pressing the Meta key at the same time as the following key. Thus if you had set the above key binding, and the insert key on your keyboard didn't generate the above key sequence, you could still type it in either of the following 2 ways. .nf 1. Hit the escape key momentarily, then press '[', then '2', then finally '~'. 2. Press the meta key at the same time as pressing the '[' key, then press '2', then '~'. .fi If you set a keybinding for a key-sequence that is already bound to a function, the new binding overrides the old one. If in the new binding you omit the name of the new function to bind to the key-sequence, the original binding becomes undefined. .sp Starting with versions of libtecla later than 1.3.3 it is now possible to bind keysequences that begin with a printable character. Previously key-sequences were required to start with a control or meta character. .sp Note that the special keywords "up", "down", "left" and "right" refer to the arrow keys, and are thus not treated as keysequences. So, for example, to rebind the up and down arrow keys to use the history search mechanism instead of the simple history recall method, you could place the following in your configuration file: .nf bind up history-search-backwards bind down history-search-backwards .fi .sp To unbind an existing binding, you can do this with the bind command by omitting to name any action to rebind the key sequence to. For example, by not specifying an action function, the following command unbinds the default beginning-of-line action from the ^A key sequence: .nf bind ^A .fi If you create a \f3~/.teclarc\f1 configuration file, but it appears to have no effect on the program, check the documentation of the program to see if the author chose a different name for this file. .SH FILENAME AND TILDE COMPLETION With the default key bindings, pressing the TAB key (aka. \f3^I\f1) results in Tecla attempting to complete the incomplete filename that precedes the cursor. Tecla searches backwards from the cursor, looking for the start of the filename, stopping when it hits either a space or the start of the line. If more than one file has the specified prefix, then Tecla completes the filename up to the point at which the ambiguous matches start to differ, then lists the possible matches. .sp In addition to literally written filenames, Tecla can complete files that start with \f3~/\f1 and \f3~user/\f1 expressions and that contain \f3$envvar\f1 expressions. In particular, if you hit TAB within an incomplete \f3~user\f1, expression, Tecla will attempt to complete the username, listing any ambiguous matches. .sp The completion binding is implemented using the \f3cpl_word_completions()\f1 function, which is also available separately to users of this library. See the \f3cpl_word_completions(@LIBR_MANEXT@)\f1 man page for more details. .SH FILENAME EXPANSION With the default key bindings, pressing \f3^X*\f1 causes Tecla to expand the filename that precedes the cursor, replacing \f3~/\f1 and \f3~user/\f1 expressions with the corresponding home directories, and replacing \f3$envvar\f1 expressions with the value of the specified environment variable, then if there are any wildcards, replacing the so far expanded filename with a space-separated list of the files which match the wild cards. .sp The expansion binding is implemented using the \f3ef_expand_file()\f1 function. See the \f3ef_expand_file(@LIBR_MANEXT@)\f1 man page for more details. .SH RECALLING PREVIOUSLY TYPED LINES Every time that a new line is entered by the user, it is appended to a list of historical input lines maintained within the GetLine resource object. You can traverse up and down this list using the up and down arrow keys. Alternatively, you can do the same with the \f3^P\f1, and \f3^N\f1 keys, and in vi command mode you can alternatively use the k and j characters. Thus pressing up-arrow once, replaces the current input line with the previously entered line. Pressing up-arrow again, replaces this with the line that was entered before it, etc.. Having gone back one or more lines into the history list, one can return to newer lines by pressing down-arrow one or more times. If you do this sufficient times, you will return to the original line that you were entering when you first hit up-arrow. .sp Note that in vi mode, all of the history recall functions switch the library into command mode. .sp In emacs mode the \f3M-p\f1 and \f3M-n\f1 keys work just like the \f3^P\f1 and \f3^N\f1 keys, except that they skip all but those historical lines which share the prefix that precedes the cursor. In vi command mode the upper case \f3K\f1 and \f3J\f1 characters do the same thing, except that the string that they search for includes the character under the cursor as well as what precedes it. .sp Thus for example, suppose that you were in emacs mode, and you had just entered the following list of commands in the order shown: .nf ls ~/tecla/ cd ~/tecla ls -l getline.c emacs ~/tecla/getline.c .fi If you next typed: .nf ls .fi and then hit \f3M-p\f1, then rather than returning the previously typed emacs line, which doesn't start with "ls", Tecla would recall the "ls -l getline.c" line. Pressing \f3M-p\f1 again would recall the "ls ~/tecla/" line. Note that if the string that you are searching for, contains any of the special characters, *, ?, or '[', then it is interpretted as a pattern to be matched. Thus, cotinuing with the above example, after typing in the list of commands shown, if you then typed: .nf *tecla* .fi and hit \f3M-p\f1, then the "emacs ~/tecla/getline.c" line would be recalled first, since it contains the word tecla somewhere in the line, Similarly, hitting \f3M-p\f1 again, would recall the "ls ~/tecla/" line, and hitting it once more would recall the "ls ~/tecla/" line. The pattern syntax is the same as that described for filename expansion, in the \f3ef_expand_file(@LIBR_MANEXT@\f1 man page. .SH HISTORY FILES Authors of programs that use the Tecla library have the option of saving historical command-lines in a file before exiting, and subsequently reading them back in from this file when the program is next started. There is no standard name for this file, since it makes sense for each application to use its own history file, so that commands from different applications don't get mixed up. .SH INTERNATIONAL CHARACTER SETS Since libtecla version 1.4.0, Tecla has been 8-bit clean. This means that all 8-bit characters that are printable in the user's current locale are now displayed verbatim and included in the returned input line. Assuming that the calling program correctly contains a call like the following, .sp .nf setlocale(LC_CTYPE, ""); .fi .sp then the current locale is determined by the first of the environment variables \f3LC_CTYPE\f1, \f3LC_ALL\f1, and \f3LANG\f1, that is found to contain a valid locale name. If none of these variables are defined, or the program neglects to call setlocale, then the default \f3C\f1 locale is used, which is US 7-bit ASCII. On most unix-like platforms, you can get a list of valid locales by typing the command: .sp .nf locale -a .fi .sp at the shell prompt. .sp .SS "Meta keys and locales" Beware that in most locales other than the default C locale, meta characters become printable, and they are then no longer considered to match \f3M-c\f1 style key bindings. This allows international characters to be entered with the compose key without unexpectedly triggering meta key bindings. You can still invoke meta bindings, since there are actually two ways to do this. For example the binding \f3M-c\f1 can also be invoked by pressing the escape key momentarily, then pressing the \f3c\f1 key, and this will work regardless of locale. Moreover, many modern terminal emulators, such as gnome's gnome-terminal's and KDE's konsole terminals, already generate escape pairs like this when you use the meta key, rather than a real meta character, and other emulators usually have a way to request this behavior, so you can continue to use the meta key on most systems. .sp For example, although xterm terminal emulators generate real 8-bit meta characters by default when you use the meta key, they can be configured to output the equivalent escape pair by setting their \f3EightBitInput\f1 X resource to \f3False\f1. You can either do this by placing a line like the following in your \f3~/.Xdefaults\f1 file, .sp .nf XTerm*EightBitInput: False .sp .fi or by starting an xterm with an \f3-xrm '*EightBitInput: False'\f1 command-line argument. In recent versions of xterm you can toggle this feature on and off with the \f3"Meta Sends Escape"\f1 option in the menu that is displayed when you press the left mouse button and the control key within an xterm window. In CDE, dtterms can be similarly coerced to generate escape pairs in place of meta characters, by setting the \f3Dtterm*KshMode\f1 resource to \f3True\f1. .sp .SS "Entering international characters" If you don't have a keyboard that generates all of the international characters that you need, there is usually a compose key that will allow you to enter special characters, or a way to create one. For example, under X windows on unix-like systems, if your keyboard doesn't have a compose key, you can designate a redundant key to serve this purpose with the xmodmap command. For example, on many PC keyboards there is a microsoft-windows key, which is otherwise useless under Linux. On my laptop the \f3xev\f1 program reports that pressing this key generates keycode 115, so to turn this key into a compose key, I do the following: .sp .nf xmodmap -e 'keycode 115 = Multi_key' .fi .sp I can then enter an i with a umlaut over it by typing this key, followed by \f3"\f1, followed by i. .SH THE AVAILABLE KEY BINDING FUNCTIONS The following is a list of the editing functions provided by the Tecla library. The names in the leftmost column of the list can be used in configuration files to specify which function a given key or combination of keys should invoke. They are also used in the next two sections to list the default key-bindings in emacs and vi modes. .nf user-interrupt - Send a SIGINT signal to the parent process. abort - Send a SIGABRT signal to the parent process. suspend - Suspend the parent process. stop-output - Pause terminal output. start-output - Resume paused terminal output. literal-next - Arrange for the next character to be treated as a normal character. This allows control characters to be entered. cursor-right - Move the cursor one character right. cursor-left - Move the cursor one character left. insert-mode - Toggle between insert mode and overwrite mode. beginning-of-line - Move the cursor to the beginning of the line. end-of-line - Move the cursor to the end of the line. delete-line - Delete the contents of the current line. kill-line - Delete everything that follows the cursor. backward-kill-line - Delete all characters between the cursor and the start of the line. forward-word - Move to the end of the word which follows the cursor. forward-to-word - Move the cursor to the start of the word that follows the cursor. backward-word - Move to the start of the word which precedes the cursor. goto-column - Move the cursor to the 1-relative column in the line specified by any preceding digit-argument sequences (see ENTERING REPEAT COUNTS below). find-parenthesis - If the cursor is currently over a parenthesis character, move it to the matching parenthesis character. If not over a parenthesis character move right to the next close parenthesis. forward-delete-char - Delete the character under the cursor. backward-delete-char - Delete the character which precedes the cursor. list-or-eof - This is intended for binding to ^D. When invoked when the cursor is within the line it displays all possible completions then redisplays the line unchanged. When invoked on an empty line, it signals end-of-input (EOF) to the caller of gl_get_line(). del-char-or-list-or-eof - This is intended for binding to ^D. When invoked when the cursor is within the line it invokes forward-delete-char. When invoked at the end of the line it displays all possible completions then redisplays the line unchanged. When invoked on an empty line, it signals end-of-input (EOF) to the caller of gl_get_line(). forward-delete-word - Delete the word which follows the cursor. backward-delete-word - Delete the word which precedes the cursor. upcase-word - Convert all of the characters of the word which follows the cursor, to upper case. downcase-word - Convert all of the characters of the word which follows the cursor, to lower case. capitalize-word - Capitalize the word which follows the cursor. change-case - If the next character is upper case, toggle it to lower case and vice versa. redisplay - Redisplay the line. clear-screen - Clear the terminal, then redisplay the current line. transpose-chars - Swap the character under the cursor with the character just before the cursor. set-mark - Set a mark at the position of the cursor. exchange-point-and-mark - Move the cursor to the last mark that was set, and move the mark to where the cursor used to be. kill-region - Delete the characters that lie between the last mark that was set, and the cursor. copy-region-as-kill - Copy the text between the mark and the cursor to the cut buffer, without deleting the original text. yank - Insert the text that was last deleted, just before the current position of the cursor. append-yank - Paste the current contents of the cut buffer, after the cursor. up-history - Recall the next oldest line that was entered. Note that in vi mode you are left in command mode. down-history - Recall the next most recent line that was entered. If no history recall session is currently active, the next line from a previous recall session is recalled. Note that in vi mode you are left in command mode. history-search-backward - Recall the next oldest line who's prefix matches the string which currently precedes the cursor (in vi command-mode the character under the cursor is also included in the search string). Note that in vi mode you are left in command mode. history-search-forward - Recall the next newest line who's prefix matches the string which currently precedes the cursor (in vi command-mode the character under the cursor is also included in the search string). Note that in vi mode you are left in command mode. history-re-search-backward -Recall the next oldest line who's prefix matches that established by the last invocation of either history-search-forward or history-search-backward. history-re-search-forward - Recall the next newest line who's prefix matches that established by the last invocation of either history-search-forward or history-search-backward. complete-word - Attempt to complete the incomplete word which precedes the cursor. Unless the host program has customized word completion, filename completion is attempted. In vi commmand mode the character under the cursor is also included in the word being completed, and you are left in vi insert mode. expand-filename - Within the command line, expand wild cards, tilde expressions and dollar expressions in the filename which immediately precedes the cursor. In vi commmand mode the character under the cursor is also included in the filename being expanded, and you are left in vi insert mode. list-glob - List any filenames which match the wild-card, tilde and dollar expressions in the filename which immediately precedes the cursor, then redraw the input line unchanged. list-history - Display the contents of the history list for the current history group. If a repeat count of > 1 is specified, only that many of the most recent lines are displayed. See the "ENTERING REPEAT COUNTS" section. read-from-file - Temporarily switch to reading input from the file who's name precedes the cursor. read-init-files - Re-read teclarc configuration files. beginning-of-history - Move to the oldest line in the history list. Note that in vi mode you are left in command mode. end-of-history - Move to the newest line in the history list (ie. the current line). Note that in vi mode this leaves you in command mode. digit-argument - Enter a repeat count for the next key-binding function. For details, see the ENTERING REPEAT COUNTS section. newline - Terminate and return the current contents of the line, after appending a newline character. The newline character is normally '\\n', but will be the first character of the key-sequence that invoked the newline action, if this happens to be a printable character. If the action was invoked by the '\\n' newline character or the '\\r' carriage return character, the line is appended to the history buffer. repeat-history - Return the line that is being edited, then arrange for the next most recent entry in the history buffer to be recalled when Tecla is next called. Repeatedly invoking this action causes successive historical input lines to be re-executed. Note that this action is equivalent to the 'Operate' action in ksh. ring-bell - Ring the terminal bell, unless the bell has been silenced via the \f3nobeep\f1 configuration option (see the THE TECLA CONFIGURATION FILE section). forward-copy-char - Copy the next character into the cut buffer (NB. use repeat counts to copy more than one). backward-copy-char - Copy the previous character into the cut buffer. forward-copy-word - Copy the next word into the cut buffer. backward-copy-word - Copy the previous word into the cut buffer. forward-find-char - Move the cursor to the next occurrence of the next character that you type. backward-find-char - Move the cursor to the last occurrence of the next character that you type. forward-to-char - Move the cursor to the character just before the next occurrence of the next character that the user types. backward-to-char - Move the cursor to the character just after the last occurrence before the cursor of the next character that the user types. repeat-find-char - Repeat the last backward-find-char, forward-find-char, backward-to-char or forward-to-char. invert-refind-char - Repeat the last backward-find-char, forward-find-char, backward-to-char, or forward-to-char in the opposite direction. delete-to-column - Delete the characters from the cursor up to the column that is specified by the repeat count. delete-to-parenthesis - Delete the characters from the cursor up to and including the matching parenthesis, or next close parenthesis. forward-delete-find - Delete the characters from the cursor up to and including the following occurence of the next character typed. backward-delete-find - Delete the characters from the cursor up to and including the preceding occurence of the next character typed. forward-delete-to - Delete the characters from the cursor up to, but not including, the following occurence of the next character typed. backward-delete-to - Delete the characters from the cursor up to, but not including, the preceding occurence of the next character typed. delete-refind - Repeat the last *-delete-find or *-delete-to action. delete-invert-refind - Repeat the last *-delete-find or *-delete-to action, in the opposite direction. copy-to-column - Copy the characters from the cursor up to the column that is specified by the repeat count, into the cut buffer. copy-to-parenthesis - Copy the characters from the cursor up to and including the matching parenthesis, or next close parenthesis, into the cut buffer. forward-copy-find - Copy the characters from the cursor up to and including the following occurence of the next character typed, into the cut buffer. backward-copy-find - Copy the characters from the cursor up to and including the preceding occurence of the next character typed, into the cut buffer. forward-copy-to - Copy the characters from the cursor up to, but not including, the following occurence of the next character typed, into the cut buffer. backward-copy-to - Copy the characters from the cursor up to, but not including, the preceding occurence of the next character typed, into the cut buffer. copy-refind - Repeat the last *-copy-find or *-copy-to action. copy-invert-refind - Repeat the last *-copy-find or *-copy-to action, in the opposite direction. vi-mode - Switch to vi mode from emacs mode. emacs-mode - Switch to emacs mode from vi mode. vi-insert - From vi command mode, switch to insert mode. vi-overwrite - From vi command mode, switch to overwrite mode. vi-insert-at-bol - From vi command mode, move the cursor to the start of the line and switch to insert mode. vi-append-at-eol - From vi command mode, move the cursor to the end of the line and switch to append mode. vi-append - From vi command mode, move the cursor one position right, and switch to insert mode. vi-replace-char - From vi command mode, replace the character under the cursor with the the next character entered. vi-forward-change-char - From vi command mode, delete the next character then enter insert mode. vi-backward-change-char - From vi command mode, delete the preceding character then enter insert mode. vi-forward-change-word - From vi command mode, delete the next word then enter insert mode. vi-backward-change-word - From vi command mode, delete the preceding word then enter insert mode. vi-change-rest-of-line - From vi command mode, delete from the cursor to the end of the line, then enter insert mode. vi-change-line - From vi command mode, delete the current line, then enter insert mode. vi-change-to-bol - From vi command mode, delete all characters between the cursor and the beginning of the line, then enter insert mode. vi-change-to-column - From vi command mode, delete the characters from the cursor up to the column that is specified by the repeat count, then enter insert mode. vi-change-to-parenthesis - Delete the characters from the cursor up to and including the matching parenthesis, or next close parenthesis, then enter vi insert mode. vi-forward-change-find - From vi command mode, delete the characters from the cursor up to and including the following occurence of the next character typed, then enter insert mode. vi-backward-change-find - From vi command mode, delete the characters from the cursor up to and including the preceding occurence of the next character typed, then enter insert mode. vi-forward-change-to - From vi command mode, delete the characters from the cursor up to, but not including, the following occurence of the next character typed, then enter insert mode. vi-backward-change-to - From vi command mode, delete the characters from the cursor up to, but not including, the preceding occurence of the next character typed, then enter insert mode. vi-change-refind - Repeat the last vi-*-change-find or vi-*-change-to action. vi-change-invert-refind - Repeat the last vi-*-change-find or vi-*-change-to action, in the opposite direction. vi-undo - In vi mode, undo the last editing operation. vi-repeat-change - In vi command mode, repeat the last command that modified the line. .fi .SH DEFAULT KEY BINDINGS IN EMACS MODE The following default key bindings, which can be overriden by the Tecla configuration file, are designed to mimic most of the bindings of the unix \f3tcsh\f1 shell, when it is in emacs editing mode. .sp This is the default editing mode of the Tecla library. .sp Under UNIX the terminal driver sets a number of special keys for certain functions. The tecla library attempts to use the same keybindings to maintain consistency. The key sequences shown for the following 6 bindings are thus just examples of what they will probably be set to. If you have used the \f3stty\f1 command to change these keys, then the default bindings should match. .nf ^C -> user-interrupt ^\\ -> abort ^Z -> suspend ^Q -> start-output ^S -> stop-output ^V -> literal-next .fi The cursor keys are refered to by name, as follows. This is necessary because different types of terminals generate different key sequences when their cursor keys are pressed. right -> cursor-right left -> cursor-left up -> up-history down -> down-history The remaining bindings don't depend on the terminal setttings. .nf ^F -> cursor-right ^B -> cursor-left M-i -> insert-mode ^A -> beginning-of-line ^E -> end-of-line ^U -> delete-line ^K -> kill-line M-f -> forward-word M-b -> backward-word ^D -> del-char-or-list-or-eof ^H -> backward-delete-char ^? -> backward-delete-char M-d -> forward-delete-word M-^H -> backward-delete-word M-^? -> backward-delete-word M-u -> upcase-word M-l -> downcase-word M-c -> capitalize-word ^R -> redisplay ^L -> clear-screen ^T -> transpose-chars ^@ -> set-mark ^X^X -> exchange-point-and-mark ^W -> kill-region M-w -> copy-region-as-kill ^Y -> yank ^P -> up-history ^N -> down-history M-p -> history-search-backward M-n -> history-search-forward ^I -> complete-word ^X* -> expand-filename ^X^F -> read-from-file ^X^R -> read-init-files ^Xg -> list-glob ^Xh -> list-history M-< -> beginning-of-history M-> -> end-of-history \\n -> newline \\r -> newline M-o -> repeat-history M-^V -> vi-mode M-0, M-1, ... M-9 -> digit-argument (see below) .fi Note that \f3^I\f1 is what the TAB key generates, and that \f3^@\f1 can be generated not only by pressing the control key and the \f3@\f1 key simultaneously, but also by pressing the control key and the space bar at the same time. .SH DEFAULT KEY BINDINGS IN VI MODE The following default key bindings are designed to mimic the vi style of editing as closely as possible. This means that very few editing functions are provided in the initial character input mode, editing functions instead being provided by the vi command mode. Vi command mode is entered whenever the escape character is pressed, or whenever a key-sequence that starts with a meta character is entered. In addition to mimicing vi, libtecla provides bindings for tab completion, wild-card expansion of file names, and historical line recall. .sp To learn how to tell the Tecla library to use vi mode instead of the default emacs editing mode, see the earlier section entitled THE TECLA CONFIGURATION FILE. .sp Under UNIX the terminal driver sets a number of special keys for certain functions. The Tecla library attempts to use the same keybindings to maintain consistency, binding them both in input mode and in command mode. The key sequences shown for the following 6 bindings are thus just examples of what they will probably be set to. If you have used the \f3stty\f1 command to change these keys, then the default bindings should match. .nf ^C -> user-interrupt ^\\ -> abort ^Z -> suspend ^Q -> start-output ^S -> stop-output ^V -> literal-next M-^C -> user-interrupt M-^\\ -> abort M-^Z -> suspend M-^Q -> start-output M-^S -> stop-output .fi Note that above, most of the bindings are defined twice, once as a raw control code like \f3^C\f1 and then a second time as a meta character like \f3M-^C\f1. The former is the binding for vi input mode, whereas the latter is the binding for vi command mode. Once in command mode all key-sequences that the user types that they don't explicitly start with an escape or a meta key, have their first key secretly converted to a meta character before the key sequence is looked up in the key binding table. Thus, once in command mode, when you type the letter \f3i\f1, for example, the Tecla library actually looks up the binding for \f3M-i\f1. The cursor keys are refered to by name, as follows. This is necessary because different types of terminals generate different key sequences when their cursor keys are pressed. right -> cursor-right left -> cursor-left up -> up-history down -> down-history The cursor keys normally generate a keysequence that start with an escape character, so beware that using the arrow keys will put you into command mode (if you aren't already in command mode). .sp The following are the terminal-independent key bindings for vi input mode. .nf ^D -> list-or-eof ^G -> list-glob ^H -> backward-delete-char ^I -> complete-word \\r -> newline \\n -> newline ^L -> clear-screen ^N -> down-history ^P -> up-history ^R -> redisplay ^U -> backward-kill-line ^W -> backward-delete-word ^X* -> expand-filename ^X^F -> read-from-file ^X^R -> read-init-files ^? -> backward-delete-char .fi The following are the key bindings that are defined in vi command mode, this being specified by them all starting with a meta character. As mentioned above, once in command mode the initial meta character is optional. For example, you might enter command mode by typing Esc, and then press h twice to move the cursor two positions to the left. Both h characters get quietly converted to M-h before being compared to the key-binding table, the first one because Escape followed by a character is always converted to the equivalent meta character, and the second because command mode was already active. .nf M-\\ -> cursor-right (Meta-space) M-$ -> end-of-line M-* -> expand-filename M-+ -> down-history M-- -> up-history M-< -> beginning-of-history M-> -> end-of-history M-^ -> beginning-of-line M-; -> repeat-find-char M-, -> invert-refind-char M-| -> goto-column M-~ -> change-case M-. -> vi-repeat-change M-% -> find-parenthesis M-a -> vi-append M-A -> vi-append-at-eol M-b -> backward-word M-B -> backward-word M-C -> vi-change-rest-of-line M-cb -> vi-backward-change-word M-cB -> vi-backward-change-word M-cc -> vi-change-line M-ce -> vi-forward-change-word M-cE -> vi-forward-change-word M-cw -> vi-forward-change-word M-cW -> vi-forward-change-word M-cF -> vi-backward-change-find M-cf -> vi-forward-change-find M-cT -> vi-backward-change-to M-ct -> vi-forward-change-to M-c; -> vi-change-refind M-c, -> vi-change-invert-refind M-ch -> vi-backward-change-char M-c^H -> vi-backward-change-char M-c^? -> vi-backward-change-char M-cl -> vi-forward-change-char M-c\\ -> vi-forward-change-char (Meta-c-space) M-c^ -> vi-change-to-bol M-c0 -> vi-change-to-bol M-c$ -> vi-change-rest-of-line M-c| -> vi-change-to-column M-c% -> vi-change-to-parenthesis M-dh -> backward-delete-char M-d^H -> backward-delete-char M-d^? -> backward-delete-char M-dl -> forward-delete-char M-d -> forward-delete-char (Meta-d-space) M-dd -> delete-line M-db -> backward-delete-word M-dB -> backward-delete-word M-de -> forward-delete-word M-dE -> forward-delete-word M-dw -> forward-delete-word M-dW -> forward-delete-word M-dF -> backward-delete-find M-df -> forward-delete-find M-dT -> backward-delete-to M-dt -> forward-delete-to M-d; -> delete-refind M-d, -> delete-invert-refind M-d^ -> backward-kill-line M-d0 -> backward-kill-line M-d$ -> kill-line M-D -> kill-line M-d| -> delete-to-column M-d% -> delete-to-parenthesis M-e -> forward-word M-E -> forward-word M-f -> forward-find-char M-F -> backward-find-char M-- -> up-history M-h -> cursor-left M-H -> beginning-of-history M-i -> vi-insert M-I -> vi-insert-at-bol M-j -> down-history M-J -> history-search-forward M-k -> up-history M-K -> history-search-backward M-l -> cursor-right M-L -> end-of-history M-n -> history-re-search-forward M-N -> history-re-search-backward M-p -> append-yank M-P -> yank M-r -> vi-replace-char M-R -> vi-overwrite M-s -> vi-forward-change-char M-S -> vi-change-line M-t -> forward-to-char M-T -> backward-to-char M-u -> vi-undo M-w -> forward-to-word M-W -> forward-to-word M-x -> forward-delete-char M-X -> backward-delete-char M-yh -> backward-copy-char M-y^H -> backward-copy-char M-y^? -> backward-copy-char M-yl -> forward-copy-char M-y\\ -> forward-copy-char (Meta-y-space) M-ye -> forward-copy-word M-yE -> forward-copy-word M-yw -> forward-copy-word M-yW -> forward-copy-word M-yb -> backward-copy-word M-yB -> backward-copy-word M-yf -> forward-copy-find M-yF -> backward-copy-find M-yt -> forward-copy-to M-yT -> backward-copy-to M-y; -> copy-refind M-y, -> copy-invert-refind M-y^ -> copy-to-bol M-y0 -> copy-to-bol M-y$ -> copy-rest-of-line M-yy -> copy-line M-Y -> copy-line M-y| -> copy-to-column M-y% -> copy-to-parenthesis M-^E -> emacs-mode M-^H -> cursor-left M-^? -> cursor-left M-^L -> clear-screen M-^N -> down-history M-^P -> up-history M-^R -> redisplay M-^D -> list-or-eof M-^I -> complete-word M-\\r -> newline M-\\n -> newline M-^X^R -> read-init-files M-^Xh -> list-history M-0, M-1, ... M-9 -> digit-argument (see below) .fi Note that \f3^I\f1 is what the TAB key generates. .SH ENTERING REPEAT COUNTS Many of the key binding functions described previously, take an optional count, typed in before the target keysequence. This is interpreted as a repeat count by most bindings. A notable exception is the goto-column binding, which interprets the count as a column number. .sp By default you can specify this count argument by pressing the meta key while typing in the numeric count. This relies on the \f3digit-argument\f1 action being bound to Meta-0, Meta-1 etc. Once any one of these bindings has been activated, you can optionally take your finger off the meta key to type in the rest of the number, since every numeric digit thereafter is treated as part of the number, unless it is preceded by the \f3literal-next\f1 binding. As soon as a non-digit, or literal digit key is pressed the repeat count is terminated and either causes the just typed character to be added to the line that many times, or causes the next key-binding function to be given that argument. .sp For example, in emacs mode, typing: .sp .nf M-12a .fi .sp causes the letter 'a' to be added to the line 12 times, whereas .sp .nf M-4M-c .fi .sp Capitalizes the next 4 words. .sp In vi command mode the Meta modifier is automatically added to all characters typed in, so to enter a count in vi command-mode, just involves typing in the number, just as it does in the vi editor itself. So for example, in vi command mode, typing: .sp .nf 4w2x .fi .sp moves the cursor four words to the right, then deletes two characters. .sp You can also bind \f3digit-argument\f1 to other key sequences. If these end in a numeric digit, that digit gets appended to the current repeat count. If it doesn't end in a numeric digit, a new repeat count is started with a value of zero, and can be completed by typing in the number, after letting go of the key which triggered the digit-argument action. .SH FILES .nf libtecla.a - The Tecla library libtecla.h - The Tecla header file. ~/.teclarc - The personal Tecla customization file. .fi .SH SEE ALSO .nf libtecla(@LIBR_MANEXT@), gl_get_line(@LIBR_MANEXT@), gl_io_mode(@LIBR_MANEXT@), ef_expand_file(@LIBR_MANEXT@), cpl_complete_word(@LIBR_MANEXT@), pca_lookup_file(@LIBR_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/libr/0040755000076400007640000000000010141252407012633 5ustar mcsmcs./libtecla/man/libr/libtecla.in0100644000076400007640000001517310027466676014770 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH libtecla @LIBR_MANEXT@ .SH NAME libtecla - An interactive command-line input library. .SH SYNOPSIS .nf @CC@ ... -ltecla -lcurses .fi .SH DESCRIPTION The \f3tecla\f1 library provides programs with interactive command line editing facilities, similar to those of the unix \f3tcsh\f1 shell. In addition to simple command-line editing, it supports recall of previously entered command lines, TAB completion of file names or other tokens, and in-line wild-card expansion of filenames. The internal functions which perform file-name completion and wild-card expansion are also available externally for optional use by the calling program. .sp The various parts of the library are documented in the following man pages: .nf tecla(@MISC_MANEXT@) - Use level documentation of the command-line editing facilities provided by \f3gl_get_line()\f1. gl_get_line(@FUNC_MANEXT@) - The interactive line-input module. gl_io_mode(@FUNC_MANEXT@) - How to use \f3gl_get_line()\f1 in an incremental, non-blocking fashion. cpl_complete_word(@FUNC_MANEXT@) - The word completion module. ef_expand_file(@FUNC_MANEXT@) - The filename expansion module. pca_lookup_file(@FUNC_MANEXT@) - A directory-list based filename lookup and completion module. .fi In addition there is one optional application distributed with the library: .nf enhance(@PROG_MANEXT@) - Add command-line editing to third party applications. .fi .SH THREAD SAFETY If the library is compiled with -D_POSIX_C_SOURCE=199506L, reentrant versions of as many functions as possible are used. This includes using getpwuid_r() and getpwnam_r() instead of getpwuid() and getpwnam() when looking up the home directories of specific users in the password file (for ~user/ expansion), and readdir_r() instead of readdir() for reading directory entries when doing filename completion. The reentrant version of the library is usually called libtecla_r.a instead of libtecla.a, so if only the latter is available, it probably isn't the correct version to link with threaded programs. Reentrant functions for iterating through the password file aren't available, so when the library is compiled to be reentrant, TAB completion of incomplete usernames in \f3~username/\f1 expressions is disabled. This doesn't disable expansion of complete \f3~username\f1 expressions, which can be done reentrantly, or expansion of the parts of filenames that follow them, so this doesn't remove much functionality. The terminfo functions setupterm(), tigetstr(), tigetnum() and tputs() also aren't reentrant, but very few programs will want to interact with multiple terminals, so this shouldn't prevent this library from being used in threaded programs. .SH LIBRARY VERSION NUMBER The version number of the library can be queried using the following function. .sp .nf void libtecla_version(int *major, int *minor, int *micro); .fi .sp On return, this function records the three components of the libtecla version number in \f3*major\f1, \f3*minor\f1, \f3*micro\f1. The formal meaning of the three components is as follows. .sp .nf major - Incrementing this number implies that a change has been made to the library's public interface, which makes it binary incompatible with programs that were linked with previous shared versions of the tecla library. minor - This number is incremented by one whenever additional functionality, such as new functions or modules, are added to the library. micro - This is incremented whenever modifications to the library are made which make no changes to the public interface, but which fix bugs and/or improve the behind-the-scenes implementation. .fi .sp .SH TRIVIA In Spanish, a "tecla" is the key of a keyboard. Since this library centers on keyboard input, and given that I wrote much of the library while working in Chile, this seemed like a suitable name. .SH FILES .nf libtecla.a - The tecla library. libtecla.h - The tecla header file. ~/.teclarc - The tecla personal customization file. .fi .SH SEE ALSO .nf gl_get_line(@FUNC_MANEXT@), tecla(@MISC_MANEXT@), gl_io_mode(@FUNC_MANEXT@), ef_expand_file(@FUNC_MANEXT@), cpl_complete_word(@FUNC_MANEXT@), pca_lookup_file(@FUNC_MANEXT@), enhance(@PROG_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) .SH ACKNOWLEDGMENTS .nf Markus Gyger - Lots of assistance, including help with shared libraries, configuration information, particularly for Solaris; modifications to support C++ compilers, improvements for ksh users, faster cursor motion, output buffering, and changes to make gl_get_line() 8-bit clean. Mike MacFaden - Suggestions, feedback and testing that led to many of the major new functions that were added in version 1.4.0. Tim Eliseo - Many vi-mode bindings and fixes. .fi ./libtecla/man/file/0040755000076400007640000000000010141252407012622 5ustar mcsmcs./libtecla/man/file/teclarc.in0100644000076400007640000000004607600315362014572 0ustar mcsmcs.so @MISC_MANDIR@/tecla.@MISC_MANEXT@ ./libtecla/man/prog/0040755000076400007640000000000010141252407012652 5ustar mcsmcs./libtecla/man/prog/enhance.in0100644000076400007640000001020010027466676014613 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH enhance @PROG_MANEXT@ .SH NAME enhance - A program that adds command-line editing to third party programs. .SH SYNOPSIS .nf enhance command [ argument ... ] .fi .SH DESCRIPTION The \f3enhance\f1 program provides enhanced command-line editing facilities to users of third party applications, to which one doesn't have any source code. It does this by placing a pseudo-terminal between the application and the real terminal. It uses the tecla command-line editing library to read input from the real terminal, then forwards each just completed input line to the application via the pseudo-terminal. All output from the application is forwarded back unchanged to the real terminal. .sp Whenever the application stops generating output for more than a tenth of a second, the \f3enhance\f1 program treats the latest incomplete output line as the prompt, and redisplays any incompleted input line that the user has typed after it. Note that the small delay, which is imperceptible to the user, isn't necessary for correct operation of the program. It is just an optimization, designed to stop the input line from being redisplayed so often that it slows down output. .sp Note that the user-level command-line editing facilities provided by the Tecla library are documented in the \f3tecla(@MISC_MANEXT@)\f1 man page .SH DEFICIENCIES The one major problem that hasn't been solved yet, is how to deal with applications that change whether typed input is echo'd by their controlling terminal. For example, programs that ask for a password, such as ftp and telnet, temporarily tell their controlling terminal not to echo what the user types. Since this request goes to the application side of the psuedo terminal, the \f3enhance\f1 program has no way of knowing that this has happened, and continues to echo typed input to its controlling terminal, while the user types their password. .sp Furthermore, before executing the host application, the \f3enhance\f1 program initially sets the pseudo terminal to noecho mode, so that everything that it sends to the program doesn't get redundantly echoed. If a program that switches to noecho mode explicitly restores echoing afterwards, rather than restoring the terminal modes that were previously in force, then subsequently, every time that you enter a new input line, a duplicate copy will be displayed on the next line. .SH FILES .nf libtecla.a - The tecla library. ~/.teclarc - The tecla personal customization file. .fi .SH SEE ALSO tecla(@MISC_MANEXT@), libtecla(@LIBR_MANEXT@) .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/func/0040755000076400007640000000000010141252407012636 5ustar mcsmcs./libtecla/man/func/cfc_set_check_fn.in0100644000076400007640000000006207600313615016414 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/cpl_complete_word.in0100644000076400007640000004504210054242132016666 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH cpl_complete_word @FUNC_MANEXT@ .SH NAME cpl_complete_word, cfc_file_start, cfc_literal_escapes, cfc_set_check_fn, cpl_add_completion, cpl_file_completions, cpl_last_error, cpl_list_completions, cpl_recall_matches, cpl_record_error, del_CplFileConf, del_WordCompletion, new_CplFileConf, new_WordCompletion \- lookup possible completions for a word .SH SYNOPSIS .nf #include #include WordCompletion *new_WordCompletion(void); WordCompletion *del_WordCompletion(WordCompletion *cpl); #define CPL_MATCH_FN(fn) int (fn)(WordCompletion *cpl, \\ void *data, \\ const char *line, \\ int word_end) typedef CPL_MATCH_FN(CplMatchFn); CPL_MATCH_FN(cpl_file_completions); CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line, int word_end, void *data, CplMatchFn *match_fn); CplMatches *cpl_recall_matches(WordCompletion *cpl); int cpl_list_completions(CplMatches *result, FILE *fp, int term_width); int cpl_add_completion(WordCompletion *cpl, const char *line, int word_start, int word_end, const char *suffix, const char *type_suffix, const char *cont_suffix); void cpl_record_error(WordCompletion *cpl, const char *errmsg); const char *cpl_last_error(WordCompletion *cpl); #define CPL_CHECK_FN(fn) int (fn)(void *data, \\ const char *pathname) typedef CPL_CHECK_FN(CplCheckFn); CPL_CHECK_FN(cpl_check_exe); CplFileConf *new_CplFileConf(void); CplFileConf *del_CplFileConf(CplFileConf *cfc); void cfc_literal_escapes(CplFileConf *cfc, int literal); void cfc_file_start(CplFileConf *cfc, int start_index); void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data); .fi .SH DESCRIPTION The \f3cpl_complete_word()\f1 function is part of the tecla library (see the libtecla(@LIBR_MANEXT@) man page). It is usually called behind the scenes by \f3gl_get_line(@FUNC_MANEXT@)\f1, but can also be called separately. Given an input line containing an incomplete word to be completed, it calls a user-provided callback function (or the provided file-completion callback function) to look up all possible completion suffixes for that word. The callback function is expected to look backward in the line, starting from the specified cursor position, to find the start of the word to be completed, then to look up all possible completions of that word and record them, one at a time by calling \f3cpl_add_completion()\f1. .sp Descriptions of the functions of this module are as follows: .sp .nf WordCompletion *new_WordCompletion(void) .fi .sp This function creates the resources used by the \f3cpl_complete_word()\f1 function. In particular, it maintains the memory that is used to return the results of calling \f3cpl_complete_word()\f1. .sp .nf WordCompletion *del_WordCompletion(WordCompletion *cpl) .fi .sp This function deletes the resources that were returned by a previous call to \f3new_WordCompletion()\f1. It always returns \f3NULL\f1 (ie. a deleted object). It does nothing if the \f3cpl\f1 argument is \f3NULL\f1. .sp The callback functions which lookup possible completions should be defined with the following macro (which is defined in libtecla.h). .sp .nf #define CPL_MATCH_FN(fn) int (fn)(WordCompletion *cpl, \\ void *data, \\ const char *line, \\ int word_end) .fi .sp Functions of this type are called by \f3cpl_complete_word()\f1, and all of the arguments of the callback are those that were passed to said function. In particular, the \f3line\f1 argument contains the input line containing the word to be completed, and \f3word_end\f1 is the index of the character that follows the last character of the incomplete word within this string. The callback is expected to look backwards from \f3word_end\f1 for the start of the incomplete word. What constitutes the start of a word clearly depends on the application, so it makes sense for the callback to take on this responsibility. For example, the builtin filename completion function looks backwards until it hits an unescaped space, or the start of the line. Having found the start of the word, the callback should then lookup all possible completions of this word, and record each completion via separate calls to \f3cpl_add_completion()\f1. If the callback needs access to an application-specific symbol table, it can pass it and any other data that it needs, via the \f3data\f1 argument. This removes any need for globals. .sp The callback function should return 0 if no errors occur. On failure it should return 1, and register a terse description of the error by calling \f3cpl_record_error()\f1. .sp .nf void cpl_record_error(WordCompletion *cpl, const char *errmsg); .fi .sp The last error message recorded by calling \f3cpl_record_error()\f1, can subsequently be queried by calling \f3cpl_last_error()\f1, as described later. .sp .nf int cpl_add_completion(WordCompletion *cpl, const char *line, int word_start, int word_end, const char *suffix, const char *type_suffix, const char *cont_suffix); .fi .sp The \f3cpl_add_completion()\f1 function is called zero or more times by the completion callback function to record each possible completion in the specified \f3WordCompletion\f1 object. These completions are subsequently returned by \f3cpl_complete_word()\f1, as described later. The \f3cpl\f1, \f3line\f1, and \f3word_end\f1 arguments should be those that were passed to the callback function. The \f3word_start\f1 argument should be the index within the input line string of the start of the word that is being completed. This should equal \f3word_end\f1 if a zero-length string is being completed. The \f3suffix\f1 argument is the string that would have to be appended to the incomplete word to complete it. If this needs any quoting (eg. the addition of backslashes before special charaters) to be valid within the displayed input line, this should be included. A copy of the suffix string is allocated internally, so there is no need to maintain your copy of the string after \f3cpl_add_completion()\f1 returns. .sp Note that in the array of possible completions which the \f3cpl_complete_word()\f1 function returns, the suffix recorded by \f3cpl_add_completion()\f1 is listed along with the concatentation of this suffix with the word that lies between \f3word_start\f1 and \f3word_end\f1 in the input line. .sp The \f3type_suffix\f1 argument specifies an optional string to be appended to the completion if it is displayed as part of a list of completions by \f3cpl_list_completions()\f1. The intention is that this indicate to the user the type of each completion. For example, the file completion function places a directory separator after completions that are directories, to indicate their nature to the user. Similary, if the completion were a function, you could indicate this to the user by setting \f3type_suffix\f1 to "()". Note that the \f3type_suffix\f1 string isn't copied, so if the argument isn't a literal string between speech marks, be sure that the string remains valid for at least as long as the results of \f3cpl_complete_word()\f1 are needed. .sp The \f3cont_suffix\f1 is a continuation suffix to append to the completed word in the input line if this is the only completion. This is something that isn't part of the completion itself, but that gives the user an indication about how they might continue to extend the token. For example, the file-completion callback function adds a directory separator if the completed word is a directory. If the completed word were a function name, you could similarly aid the user by arranging for an open parenthesis to be appended. .sp .nf CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line, int word_end, void *data, CplMatchFn *match_fn); .fi .sp The \f3cpl_complete_word()\f1 is normally called behind the scenes by \f3gl_get_line(@FUNC_MANEXT@)\f1, but can also be called separately if you separately allocate a \f3WordCompletion\f1 object. It performs word completion, as described at the beginning of this section. Its first argument is a resource object previously returned by \f3new_WordCompletion()\f1. The \f3line\f1 argument is the input line string, containing the word to be completed. The \f3word_end\f1 argument contains the index of the character in the input line, that just follows the last character of the word to be completed. When called by \f3gl_get_line()\f1, this is the character over which the user pressed \f3TAB\f1. The \f3match_fn\f3 argument is the function pointer of the callback function which will lookup possible completions of the word, as described above, and the \f3data\f1 argument provides a way for the application to pass arbitrary data to the callback function. .sp If no errors occur, the \f3cpl_complete_word()\f1 function returns a pointer to a \f3CplMatches\f1 container, as defined below. This container is allocated as part of the \f3cpl\f1 object that was passed to \f3cpl_complete_word()\f1, and will thus change on each call which uses the same \f3cpl\f1 argument. .sp .nf typedef struct { char *completion; /* A matching completion */ /* string */ char *suffix; /* The part of the */ /* completion string which */ /* would have to be */ /* appended to complete the */ /* original word. */ const char *type_suffix; /* A suffix to be added when */ /* listing completions, to */ /* indicate the type of the */ /* completion. */ } CplMatch; typedef struct { char *suffix; /* The common initial part */ /* of all of the completion */ /* suffixes. */ const char *cont_suffix; /* Optional continuation */ /* string to be appended to */ /* the sole completion when */ /* nmatch==1. */ CplMatch *matches; /* The array of possible */ /* completion strings, */ /* sorted into lexical */ /* order. */ int nmatch; /* The number of elements in */ /* the above matches[] */ /* array. */ } CplMatches; .fi .sp If an error occurs during completion, \f3cpl_complete_word()\f1 returns NULL. A description of the error can be acquired by calling the \f3cpl_last_error()\f3 function. .sp .nf const char *cpl_last_error(WordCompletion *cpl); .fi .sp The \f3cpl_last_error()\f3 function returns a terse description of the error which occurred on the last call to \f3cpl_complete_word()\f1 or \f3cpl_add_completion()\f1. .sp .nf CplMatches *cpl_recall_matches(WordCompletion *cpl); .fi .sp As a convenience, the return value of the last call to \f3cpl_complete_word()\f1 can be recalled at a later time by calling \f3cpl_recall_matches()\f1. If \f3cpl_complete_word()\f1 returned \f3NULL\f1, so will \f3cpl_recall_matches()\f1. .sp .nf int cpl_list_completions(CplMatches *result, FILE *fp, int terminal_width); .fi .sp When the \f3cpl_complete_word()\f1 function returns multiple possible completions, the \f3cpl_list_completions()\f1 function can be called upon to list them, suitably arranged across the available width of the terminal. It arranges for the displayed columns of completions to all have the same width, set by the longest completion. It also appends the \f3type_suffix\f1 strings that were recorded with each completion, thus indicating their types to the user. .SH THE BUILT-IN FILENAME-COMPLETION CALLBACK By default the \f3gl_get_line(@FUNC_MANEXT@)\f1 function, passes the following completion callback function to \f3cpl_complete_word()\f1. This function can also be used separately, either by sending it to \f3cpl_complete_word()\f1, or by calling it directly from your own completion callback function. .sp .nf CPL_MATCH_FN(cpl_file_completions); .fi .sp Certain aspects of the behavior of this callback can be changed via its \f3data\f1 argument. If you are happy with its default behavior you can pass \f3NULL\f1 in this argument. Otherwise it should be a pointer to a \f3CplFileConf\f1 object, previously allocated by calling \f3new_CplFileConf()\f1. .sp .nf CplFileConf *new_CplFileConf(void); .fi .sp \f3CplFileConf\f1 objects encapsulate the configuration parameters of \f3cpl_file_completions()\f1. These parameters, which start out with default values, can be changed by calling the accessor functions described below. .sp By default, the \f3cpl_file_completions()\f3 callback function searches backwards for the start of the filename being completed, looking for the first un-escaped space or the start of the input line. If you wish to specify a different location, call \f3cfc_file_start()\f1 with the index at which the filename starts in the input line. Passing start_index=-1 re-enables the default behavior. .sp .nf void cfc_file_start(CplFileConf *cfc, int start_index); .fi .sp By default, when \f3cpl_file_completions()\f1 looks at a filename in the input line, each lone backslash in the input line is interpreted as being a special character which removes any special significance of the character which follows it, such as a space which should be taken as part of the filename rather than delimiting the start of the filename. These backslashes are thus ignored while looking for completions, and subsequently added before spaces, tabs and literal backslashes in the list of completions. To have unescaped backslashes treated as normal characters, call \f3cfc_literal_escapes()\f1 with a non-zero value in its \f3literal\f1 argument. .sp .nf void cfc_literal_escapes(CplFileConf *cfc, int literal); .fi .sp By default, \f3cpl_file_completions()\f1 reports all files who's names start with the prefix that is being completed. If you only want a selected subset of these files to be reported in the list of completions, you can arrange this by providing a callback function which takes the full pathname of a file, and returns \f30\f1 if the file should be ignored, or \f31\f1 if the file should be included in the list of completions. To register such a function for use by \f3cpl_file_completions()\f1, call \f3cfc_set_check_fn()\f1, and pass it a pointer to the function, together with a pointer to any data that you would like passed to this callback whenever it is called. Your callback can make its decisions based on any property of the file, such as the filename itself, whether the file is readable, writable or executable, or even based on what the file contains. .sp .nf #define CPL_CHECK_FN(fn) int (fn)(void *data, \\ const char *pathname) typedef CPL_CHECK_FN(CplCheckFn); void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data); .fi .sp The \f3cpl_check_exe()\f1 function is a provided callback of the above type, for use with \f3cpl_file_completions()\f1. It returns non-zero if the filename that it is given represents a normal file that the user has execute permission to. You could use this to have \f3cpl_file_completions()\f1 only list completions of executable files. .sp When you have finished with a \f3CplFileConf\f1 variable, you can pass it to the \f3del_CplFileConf()\f1 destructor function to reclaim its memory. .sp .nf CplFileConf *del_CplFileConf(CplFileConf *cfc); .fi .sp .SH THREAD SAFETY In multi-threaded programs, you should use the \f3libtecla_r.a\f1 version of the library. This uses POSIX reentrant functions where available (hence the \f3_r\f1 suffix), and disables features that rely on non-reentrant system functions. In the case of this module, the only disabled feature is username completion in \f3~username/\f1 expressions, in \f3cpl_file_completions()\f1. Using the \f3libtecla_r.a\f1 version of the library, it is safe to use the facilities of this module in multiple threads, provided that each thread uses a separately allocated \f3WordCompletion\f1 object. In other words, if two threads want to do word completion, they should each call \f3new_WordCompletion()\f1 to allocate their own completion objects. .SH FILES .nf libtecla.a - The tecla library libtecla.h - The tecla header file. .fi .SH SEE ALSO .nf libtecla(@LIBR_MANEXT@), gl_get_line(@FUNC_MANEXT@), ef_expand_file(@FUNC_MANEXT@), pca_lookup_file(@FUNC_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/func/cpl_file_completions.in0100644000076400007640000000006207600313615017357 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/cpl_last_error.in0100644000076400007640000000006207600313615016200 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/cpl_list_completions.in0100644000076400007640000000006207600313615017413 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/cpl_record_error.in0100644000076400007640000000006207600313615016513 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/del_CplFileConf.in0100644000076400007640000000006207600313615016136 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/del_GetLine.in0100644000076400007640000000005407600313615015342 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_display_text.in0100644000076400007640000000005407600313615016362 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/ef_expand_file.in0100644000076400007640000002375710027466676016153 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH ef_expand_file @FUNC_MANEXT@ .SH NAME ef_expand_file, del_ExpandFile, ef_last_error, ef_list_expansions, new_ExpandFile \- expand filenames containing ~user/$envvar and wildcard expressions .SH SYNOPSIS .nf #include ExpandFile *new_ExpandFile(void); ExpandFile *del_ExpandFile(ExpandFile *ef); FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen); int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width); const char *ef_last_error(ExpandFile *ef); .fi .SH DESCRIPTION The \f3ef_expand_file()\f1 function is part of the tecla library (see the libtecla(@LIBR_MANEXT@) man page). It expands a specified filename, converting \f3~user/\f1 and \f3~/\f1 expressions at the start of the filename to the corresponding home directories, replacing \f3$envvar\f1 with the value of the corresponding environment variable, and then, if there are any wildcards, matching these against existing filenames. Backslashes in the input filename are interpreted as escaping any special meanings of the characters that follow them. Only backslahes that are themselves preceded by backslashes are preserved in the expanded filename. .sp In the presence of wildcards, the returned list of filenames only includes the names of existing files which match the wildcards. Otherwise, the original filename is returned after expansion of tilde and dollar expressions, and the result is not checked against existing files. This mimics the file-globbing behavior of the unix \f3tcsh\f1 shell. .sp The supported wildcards and their meanings are: .nf * - Match any sequence of zero or more characters. ? - Match any single character. [chars] - Match any single character that appears in 'chars'. If 'chars' contains an expression of the form a-b, then any character between a and b, including a and b, matches. The '-' character looses its special meaning as a range specifier when it appears at the start of the sequence of characters. The ']' character also looses its significance as the terminator of the range expression if it appears immediately after the opening '[', at which point it is treated one of the characters of the range. If you want both '-' and ']' to be part of the range, the '-' should come first and the ']' second. [^chars] - The same as [chars] except that it matches any single character that doesn't appear in 'chars'. .fi Note that wildcards never match the initial dot in filenames that start with '.'. The initial '.' must be explicitly specified in the filename. This again mimics the globbing behavior of most unix shells, and its rational is based in the fact that in unix, files with names that start with '.' are usually hidden configuration files, which are not listed by default by the ls command. .sp The following is a complete example of how to use the file expansion function. .nf #include #include int main(int argc, char *argv[]) { ExpandFile *ef; /* The expansion resource object */ char *filename; /* The filename being expanded */ FileExpansion *expn; /* The results of the expansion */ int i; ef = new_ExpandFile(); if(!ef) return 1; for(arg = *(argv++); arg; arg = *(argv++)) { if((expn = ef_expand_file(ef, arg, -1)) == NULL) { fprintf(stderr, "Error expanding %s (%s).\\n", arg, ef_last_error(ef)); } else { printf("%s matches the following files:\\n", arg); for(i=0; infile; i++) printf(" %s\\n", expn->files[i]); } } ef = del_ExpandFile(ef); return 0; } .fi .sp Descriptions of the functions used above are as follows: .sp .nf ExpandFile *new_ExpandFile(void) .fi .sp This function creates the resources used by the \f3ef_expand_file()\f1 function. In particular, it maintains the memory that is used to record the array of matching filenames that is returned by \f3ef_expand_file()\f1. This array is expanded as needed, so there is no built in limit to the number of files that can be matched. .sp .nf ExpandFile *del_ExpandFile(ExpandFile *ef) .fi .sp This function deletes the resources that were returned by a previous call to \f3new_ExpandFile()\f1. It always returns \f3NULL\f1 (ie a deleted object). It does nothing if the \f3ef\f1 argument is \f3NULL\f1. .sp A container of the following type is returned by \f3ef_expand_file()\f1. .sp .nf typedef struct { int exists; /* True if the files in files[] exist */ int nfile; /* The number of files in files[] */ char **files; /* An array of 'nfile' filenames. */ } FileExpansion; .fi .sp .nf FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen) .fi .sp The \f3ef_expand_file()\f1 function performs filename expansion, as documented at the start of this section. Its first argument is a resource object returned by \f3new_ExpandFile()\f1. A pointer to the start of the filename to be matched is passed via the \f3path\f1 argument. This must be a normal \f3NUL\f1 terminated string, but unless a length of -1 is passed in \f3pathlen\f1, only the first \f3pathlen\f1 characters will be used in the filename expansion. If the length is specified as -1, the whole of the string will be expanded. .sp The function returns a pointer to a container who's contents are the results of the expansion. If there were no wildcards in the filename, the \f3nfile\f1 member will be 1, and the \f3exists\f1 member should be queried if it is important to know if the expanded file currently exists or not. If there were wildcards, then the contained \f3files[]\f1 array will contain the names of the \f3nfile\f1 existing files that matched the wildcarded filename, and the \f3exists\f1 member will have the value 1. Note that the returned container belongs to the specified \f3ef\f1 object, and its contents will change on each call, so if you need to retain the results of more than one call to \f3ef_expand_file()\f1, you should either make a private copy of the returned results, or create multiple file-expansion resource objects via multiple calls to \f3new_ExpandFile()\f1. .sp On error, \f3NULL\f1 is returned, and an explanation of the error can be determined by calling \f3ef_last_error(ef)\f1. .sp .nf const char *ef_last_error(ExpandFile *ef) .fi .sp This function returns the message which describes the error that occurred on the last call to \f3ef_expand_file()\f1, for the given \f3(ExpandFile *ef)\f1 resource object. .sp .nf int ef_list_expansions(FileExpansion *result, FILE *fp, int terminal_width); .fi .sp The \f3ef_list_expansions()\f1 function provides a convenient way to list the filename expansions returned by \f3ef_expand_file()\f1. Like the unix \f3ls\f1 command, it arranges the filenames into equal width columns, each column having the width of the largest file. The number of columns used is thus determined by the length of the longest filename, and the specified terminal width. Beware that filenames that are longer than the specified terminal width are printed without being truncated, so output longer than the specified terminal width can occur. The list is written to the stdio stream specified by the \f3fp\f1 argument. .SH THREAD SAFETY In multi-threaded programs, you should use the \f3libtecla_r.a\f1 version of the library. This uses POSIX reentrant functions where available (hence the \f3_r\f1 suffix), and disables features that rely on non-reentrant system functions. Currently there are no features disabled in this module. Using the \f3libtecla_r.a\f1 version of the library, it is safe to use the facilities of this module in multiple threads, provided that each thread uses a separately allocated \f3ExpandFile\f1 object. In other words, if two threads want to do file expansion, they should each call \f3new_ExpandFile()\f1 to allocate their own file-expansion objects. .SH FILES .nf libtecla.a - The tecla library libtecla.h - The tecla header file. .fi .SH SEE ALSO .nf libtecla(@LIBR_MANEXT@), gl_get_line(@FUNC_MANEXT@), cpl_complete_word(@FUNC_MANEXT@), pca_lookup_file(@FUNC_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/func/del_ExpandFile.in0100644000076400007640000000005707600313615016035 0ustar mcsmcs.so @FUNC_MANDIR@/ef_expand_file.@FUNC_MANEXT@ ./libtecla/man/func/del_PathCache.in0100644000076400007640000000006007600313615015630 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/del_PcaPathConf.in0100644000076400007640000000006007600313615016136 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/del_WordCompletion.in0100644000076400007640000000006207600313615016757 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/ef_last_error.in0100644000076400007640000000005707600313615016020 0ustar mcsmcs.so @FUNC_MANDIR@/ef_expand_file.@FUNC_MANEXT@ ./libtecla/man/func/gl_raw_io.in0100644000076400007640000000005307600313616015131 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/ef_list_expansions.in0100644000076400007640000000005707600313615017066 0ustar mcsmcs.so @FUNC_MANDIR@/ef_expand_file.@FUNC_MANEXT@ ./libtecla/man/func/gl_clear_history.in0100644000076400007640000000005407600313615016520 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/new_ExpandFile.in0100644000076400007640000000005707600313616016063 0ustar mcsmcs.so @FUNC_MANDIR@/ef_expand_file.@FUNC_MANEXT@ ./libtecla/man/func/gl_io_mode.in0100644000076400007640000005765110027466676015320 0ustar mcsmcs.\" Copyright (c) 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH gl_io_mode @FUNC_MANEXT@ .SH NAME gl_io_mode, gl_raw_io, gl_normal_io, gl_tty_signals, gl_abandon_line, gl_handle_signal, gl_pending_io \- How to use gl_get_line() from an external event loop. .SH SYNOPSIS .nf #include int gl_io_mode(GetLine *gl, GlIOMode mode); int gl_raw_io(GetLine *gl); int gl_normal_io(GetLine *gl); int gl_tty_signals(void (*term_handler)(int), void (*susp_handler)(int), void (*cont_handler)(int), void (*size_handler)(int)); void gl_abandon_line(GetLine *gl); void gl_handle_signal(int signo, GetLine *gl, int ngl); GlPendingIO gl_pending_io(GetLine *gl); .fi .SH DESCRIPTION The \f3gl_get_line()\f1 function, which is documented separately in the \f3gl_get_line(@FUNC_MANEXT@)\f1 man page, supports two different I/O modes. These are selected by calling the \f3gl_io_mode()\f1 function. .sp .nf int gl_io_mode(GetLine *gl, GlIOMode mode); .fi .sp The \f3mode\f1 argument of this function specifies the new I/O mode, and must be one of the following. .sp .nf GL_NORMAL_MODE - Select the normal blocking-I/O mode. In this mode \f3gl_get_line()\f1 doesn't return until either an error occurs of the user finishes entering a new line. This mode is the focus of the \f3gl_get_line(@FUNC_MANEXT@)\f1 man page. GL_SERVER_MODE - Select non-blocking server I/O mode. In this mode, since non-blocking terminal I/O is used, the entry of each new input line typically requires many calls to \f3gl_get_line()\f1 from an external I/O-driven event loop. This mode is the focus of this man page. .fi .sp Newly created \f3GetLine\f1 objects start in normal I/O mode, so to switch to non-blocking server mode requires an initial call to \f3gl_io_mode()\f1. .SH SERVER I/O MODE In non-blocking server I/O mode, the application is required to have an event loop which calls \f3gl_get_line()\f1 whenever the terminal file descriptor can do the type I/O that \f3gl_get_line()\f1 is waiting for. To determine which type of I/O \f3gl_get_line()\f1 is waiting for, the application calls the \f3gl_pending_io()\f1 function. .sp .nf GlPendingIO gl_pending_io(GetLine *gl); .fi .sp The return value of this function is one of the following two enumerated values. .sp .nf GLP_READ - gl_get_line() is waiting to write a character to the terminal. GLP_WRITE - gl_get_line() is waiting to read a character from the keyboad. .fi .sp If the application is using either the \f3select()\f1 or \f3poll()\f1 system calls to watch for I/O on a group of file descriptors, then it should call the \f3gl_pending_io()\f1 function before each call to these functions to see which direction of I/O it should tell them to watch for, and configure their arguments accordingly. In the case of the \f3select()\f1 system call, this means using the \f3FD_SET()\f1 macro to add the terminal file descriptor either to the set of file descriptors to be watched for readability, or the set to be watched for writability. As in normal I/O mode, the return value of \f3gl_get_line()\f1 is either a pointer to a completed input line, or \f3NULL\f1. However, whereas in normal I/O mode a \f3NULL\f1 return value always means that an error occurred, in non-blocking server mode, \f3NULL\f1 is also returned when \f3gl_get_line()\f1 can't read or write to the terminal without blocking. Thus in non-blocking server mode, in order to determine when a \f3NULL\f1 return value signifies that an error occurred or not, it is necessary to call the \f3gl_return_status()\f1 function. If this function returns the enumerated value, \f3GLR_BLOCKED\f1, as documented in the \f3gl_get_line(@FUNC_MANEXT@)\f1 man page, this means that \f3gl_get_line()\f1 is waiting for I/O, and no error has occurred. When \f3gl_get_line()\f1 returns \f3NULL\f1 and \f3gl_return_status()\f1 indicates that this is due to blocked terminal I/O, the application should call \f3gl_get_line()\f1 again when the type of I/O reported by \f3gl_pending_io()\f1 becomes possible. The \f3prompt\f1, \f3start_line\f1 and \f3start_pos\f1 arguments of \f3gl_get_line()\f1 will be ignored on these calls. If you need to change the prompt of the line that is currently being edited, then you can call the \f3gl_replace_prompt()\f1 function (documented in the \f3gl_get_line(@FUNC_MANEXT@) man page) between calls to \f3gl_get_line()\f1. .SH GIVING UP THE TERMINAL A complication that is unique to non-blocking server mode is that it requires that the terminal be left in raw mode between calls to \f3gl_get_line()\f1. If this weren't the case, the external event loop wouldn't be able to detect individual key-presses, and the basic line editing implemented by the terminal driver would clash with the editing provided by \f3gl_get_line()\f1. What this means is that any time that the terminal needs to be used for other things than entering a new input line with \f3gl_get_line()\f1, it needs to be restored to a usable state. In particular, whenever the process is suspended or terminated, the terminal must be returned to a normal state. If this isn't done, then depending on the characteristics of the shell that was used to invoke the program, the user may end up with a hung terminal. To this end, the \f3gl_normal_io()\f1 function is provided for switching the terminal back to the state that it was in when raw mode was last established. .sp .nf int gl_normal_io(GetLine *gl); .fi .sp What this function does is first flush any pending output to the terminal, then move the cursor to the start of the terminal line which follows the end of the incompletely entered input line. At this point it is safe to suspend or terminate the process, and it is safe for the application to read and write to the terminal. To resume entry of the input line, the application should call the \f3gl_raw_io()\f1 function. .sp .nf int gl_raw_io(GetLine *gl); .fi .sp This function starts a new line, redisplays the partially completed input line (if any), restores the cursor position within this line to where it was when \f3gl_normal_io()\f1 was called, then switches back to raw, non-blocking terminal mode ready to continue entry of the input line when \f3gl_get_line()\f1 is next called. Note that in non-blocking server mode, if \f3gl_get_line()\f1 is called after a call to \f3gl_normal_io()\f1, without an intervening call to \f3gl_raw_io()\f1, \f3gl_get_line()\f1 will call \f3gl_raw_mode()\f1 itself, and the terminal will remain in this mode when \f3gl_get_line()\f1 returns. .SH SIGNAL HANDLING In the previous section it was pointed out that in non-blocking server mode, the terminal must be restored to a sane state whenever a signal is received that either suspends or terminates the process. In normal I/O mode, this is done for you by \f3gl_get_line()\f1, but in non-blocking server mode, since the terminal is left in raw mode between calls to \f3gl_get_line()\f1, this signal handling has to be done by the application. Since there are many signals that can suspend or terminate a process, as well as other signals that are important to \f3gl_get_line()\f1, such as the \f3SIGWINCH\f1 signal, which tells it when the terminal size has changed, the \f3gl_tty_signals()\f1 function is provided for installing signal handlers for all pertinent signals. .sp .nf int gl_tty_signals(void (*term_handler)(int), void (*susp_handler)(int), void (*cont_handler)(int), void (*size_handler)(int)); .fi .sp What this does is use \f3gl_get_line()\f1's internal list of signals to assign specified signal handlers to groups of signals. The arguments of this function are as follows. .sp .nf term_handler - This is the signal handler that is to be used to trap signals that by default terminate any process that receives them (eg. SIGINT or SIGTERM). susp_handler - This is the signal handler that is to be used to trap signals that by default suspend any process that receives them, (eg. SIGTSTP or SIGTTOU). cont_handler - This is the signal handler that is to be used to trap signals that are usually sent when a process resumes after being suspended (usually SIGCONT). Beware that there is nothing to stop a user from sending one of these signals at other times. size_handler - This signal handler is used to trap signals that are sent to processes when their controlling terminals are resized by the user (eg. SIGWINCH). .fi .sp These arguments can all be the same, if so desired, and you can specify \f3SIG_IGN\f1 (ignore this signal) or \f3SIG_DFL\f1 (use the system-provided default signal handler) instead of a function where pertinent. In particular, it is rarely useful to trap \f3SIGCONT\f1, so the \f3cont_handler\f1 argument will usually be \f3SIG_DFL\f1 or \f3SIG_IGN\f1. The \f3gl_tty_signals()\f1 function uses the POSIX \f3sigaction()\f1 function to install these signal handlers, and it is careful to use the \f3sa_mask\f1 member of each sigaction structure to ensure that only one of these signals is ever delivered at a time. This guards against different instances of these signal handlers from simultaneously trying to write to common global data, such as a shared \f3sigsetjmp()\f1 buffer or a signal-received flag. The signal handlers that are installed by this function, should call the \f3gl_handle_signal(). .sp .nf void gl_handle_signal(int signo, GetLine *gl, int ngl); .fi .sp The \f3signo\f1 argument tells this function which signal it is being asked to respond to, and the \f3gl\f1 argument should be a pointer to the first element of an array of \f3ngl\f1 \f3GetLine\f1 objects. If your application only has one of these objects, just pass its pointer as the \f3gl\f1 argument and specify \f3ngl\f1 as \f31\f1. Depending on the signal that is being handled, this function does different things. .SS Terminal resize signals (SIGWINCH) If the signal indicates that the terminal was resized, then it arranges for the next call to \f3gl_get_line()\f1 to ask the terminal for its new size and redraw the input line accordingly. In order that \f3gl_get_line()\f1 be called as soon as possible to do this, \f3gl_handle_signal()\f1 also arranges that the next call to \f3gl_pending_io()\f1 will return \f3GLP_WRITE\f1. Thus if the application waits for I/O in \f3select()\f1 or \f3poll()\f1, then the application needs to ensure that these functions will be reliably aborted when a signal is caught and handled by the application. More on this below. .SH Process termination signals. If the signal that was caught is one of those that by default terminates any process that receives it, then \f3gl_handle_signal()\f1 does the following steps. 1. First it blocks the delivery of all signals that can be blocked (ie. \f3SIGKILL\f1 and \f3SIGSTOP\f1 can't be blocked) 2. Next it calls \f3gl_normal_io()\f1 for each of the \f3ngl\f1 \f3GetLine\f1 objects. Note that this does nothing to any of the \f3GetLine\f1 objects that aren't currently in raw mode. 3. Next it sets the signal handler of the signal to its default, process-termination disposition. 4. Next it re-sends the process the signal that was caught. 5. Finally it unblocks delivery of this signal, which results in the process being terminated. .SH Process suspension signals. If the default disposition of the signal is to suspend the process, the same steps are executed as for process termination signals, except that when the process is later resumed, \f3gl_handle_signal()\f1 continues, and does the following steps. 6. It re-blocks delivery of the signal. 7. It reinstates the signal handler of the signal to the one that was displaced when its default disposition was substituted. 8. For any of the \f3GetLine\f1 objects that were in raw mode when \f3gl_handle_signal()\f1 was called, \f3gl_handle_signal()\f1 then calls \f3gl_raw_io()\f1, to resume entry of the input lines on those terminals. 9. Finally, it restores the signal process mask to how it was when \f3gl_handle_signal()\f1 was called. Note that the process is suspended or terminated using the original signal that was caught, rather than using the uncatchable \f3SIGSTOP\f1 and \f3SIGKILL\f1 signals. This is important, because when a process is suspended or terminated, the parent of the process may wish to use the status value returned by the \f3wait()\f1 system call to figure out which signal was responsible. In particular, most shells use this information to print a corresponding message to the terminal. Users would be rightly confused if when their process received a \f3SIGPIPE\f1 signal, the program responded by sending itself a \f3SIGKILL\f1 signal, and the shell then printed out the provocative statement, "Killed!". .SH INTERRUPTING THE EVENT LOOP If a signal is caught and handled when the application's event loop is waiting in \f3select()\f1 or \f3poll()\f1, these functions will be aborted with \f3errno\f1 set to \f3EINTR\f1. When this happens the event loop should call \f3gl_pending_io()\f1, before calling \f3select()\f1 or \f3poll()\f1 again. It should then arrange for \f3select()\f1 or \f3poll()\f1 to wait for the type of I/O that this reports. This is necessary, because any signal handler which calls \f3gl_handle_signal()\f1, will frequently change the type of I/O that \f3gl_get_line()\f1 is waiting for. Unfortunately, if a signal arrives between the statements which configure the arguments of \f3select()\f1 or \f3poll()\f1 and the calls to these functions, then the signal will not be seen by these functions, which will then not be aborted. If these functions are waiting for keyboard input from the user when the signal is received, and the signal handler arranges to redraw the input line to accomodate a terminal resize or the resumption of the process, then this redisplay will be end up being delayed until the user hits the next key. Apart from puzzling the user, this clearly isn't a serious problem. However there is a way, albeit complicated, to completely avoid this race condition. The following steps illustrate this. 1. Block all of the signals that \f3gl_get_line()\f1 catches, by passing the signal set returned by \f3gl_list_signals()\f1 to \f3sigprocmask()\f1. 2. Call \f3gl_pending_io()\f1 and set up the arguments of \f3select()\f1 or \f3poll()\f1 accordingly. 3. Call \f3sigsetjmp()\f1 with a non-zero \f3savesigs\f1 argument. 4. Initially this \f3sigsetjmp()\f1 statement will return zero, indicating that control isn't resuming there after a matching call to \f3siglongjmp()\f1. 5. Replace all of the handlers of the signals that \f3gl_get_line()\f1 is configured to catch, with a signal handler that first records the number of the signal that was caught, in a file-scope variable, then calls \f3siglongjmp()\f1 with a non-zero value argument, to return execution to the above \f3sigsetjmp()\f1 statement. Registering these signal handlers can conveniently be done using the \f3gl_tty_signals()\f1 function. 6. Set the file-scope variable that the above signal handler uses to record any signal that is caught to -1, so that we can check whether a signal was caught by seeing if it contains a valid signal number. 7. Now unblock the signals that were blocked in step 1. Any signal that was received by the process in between step 1 and now will now be delivered, and trigger our signal handler, as will any signal that is received until we block these signals again. 8. Now call \f3select()\f1 or \f3poll()\f1. 9. When \f3select()\f1 returns, again block the signals that were unblocked in step 7. If a signal is arrived any time during the above steps, our signal handler will be triggered and cause control to return to the \f3sigsetjmp()\f1 statement, where this time, \f3sigsetjmp()\f1 will return non-zero, indicating that a signal was caught. When this happens we simply skip the above block of statements, and continue with the following statements, which are executed regardless of whether or not a signal is caught. Note that when \f3sigsetjmp()\f1 returns, regardless of why it returned, the process signal mask is returned to how it was when \f3sigsetjmp()\f1 was called. Thus the following statements are always executed with all of our signals blocked. 9. Reinstate the signal handlers that were displaced in step 5. 10. Check wether a signal was caught, by checking the file-scope variable that the signal handler records signal numbers in. 11. If a signal was caught, send this signal to the application again, and unblock just this signal, so that it invokes the signal handler which we just reinstated in step 10. 12. Unblock all of the signals that were blocked in step 7. Since this is complicated, note that \f3demo3.c\f1 includes a working example of how to do this. The method used there however, is more general than the above. What it provides is a wrapper function around \f3select()\f1 which encompasses steps 3 to 11. In this wrapper, rather than use \f3gl_list_signals()\f1 to figure out the signals to block, and and \f3gl_tty_signals()\f1 to assign and revert signal handlers, one of its arguments is a \f3sigset_t\f1 which specifies which signals to block and assign signal handlers to. This function thus doesn't depend on \f3gl_get_line()\f1 and can thus be used in other situations where race-condition-free signal handling is required. .SH SIGNALS CAUGHT BY GL_GET_LINE Since the application is expected to handle signals in non-blocking server mode, \f3gl_get_line()\f1 doesn't attempt to duplicate this when it is being called. If one of the signals that it is configured to catch is sent to the application while \f3gl_get_line()\f1 is being called, \f3gl_get_line()\f1 reinstates the caller's signal handlers, then just before returning, re-sends the signal to the process to let the application's signal handler handle it. If the process isn't terminated by this signal, \f3gl_get_line()\f1 returns \f3NULL\f1, and a following call to \f3gl_return_status()\f1 returns the enumerated value \f3GLR_SIGNAL\f1. .SH ABORTING LINE INPUT Often, rather than letting it terminate the process, applications respond to the SIGINT user-interrupt signal by aborting the current input line. The way to do this in non-blocking server-I/O mode is to not call \f3gl_handle_signal()\f1 when this signal is caught, but instead to call the \f3gl_abandon_line()\f1. .sp .nf void gl_abandon_line(GetLine *gl); .fi .sp This function arranges that when \f3gl_get_line()\f1 is next called, it first flushes any pending output to the terminal, then discardes the current input line, outputs a new prompt on the next line, and finally starts accepting input of a new input line from the user. .SH SIGNAL SAFE FUNCTIONS Provided that certain rules are followed, the following functions can have been written to be safely callable from signal handlers. Other functions in this library should not be called from signal handlers. .sp .nf gl_normal_io() gl_raw_io() gl_handle_signal() gl_abandon_line() .fi .sp In order for this to be true, all signal handlers that call these functions must be registered in such a way that only one instance of any one of them can be running at one time. The way to do this is to use the POSIX \f3sigaction()\f1 function to register all signal handlers, and when doing this, use the \f3sa_mask\f1 member of the corresponding sigaction structure, to indicate that all of the signals who's handlers invoke the above functions, should be blocked when the current signal is being handled. This prevents two signal handlers from operating on a \f3GetLine\f1 object at the same time. To prevent signal handlers from accessing a \f3GetLine\f1 object while \f3gl_get_line()\f1 or any of its associated public functions are operating on it, all public functions associated with \f3gl_get_line()\f1, including \f3gl_get_line()\f1 itself, temporarily block the delivery of signals when they are accessing \f3GetLine\f1 objects. Beware that the only signals that they block are the signals that \f3gl_get_line()\f1 is currently configured to catch, so be sure that if you call any of the above functions from signal handlers, that the signals that these handlers are assigned to are configured to be caught by \f3gl_get_line()\f1 (see \f3gl_trap_signal()\f1). .SH USING TIMEOUTS TO POLL If instead of using \f3select()\f1 or \f3poll()\f1 to wait for I/O, your application just needs to get out of \f3gl_get_line()\f1 periodically to briefly do something else before returning to accept input from the user, this can be done in non-blocking server mode by using the \f3gl_inactivity_timeout()\f1 function (see \f3gl_get_line(@FUNC_MANEXT@)\f1), to specify that a callback function that returns \f3GLTO_CONTINUE\f1 should be called whenever \f3gl_get_line()\f1 has been waiting for I/O for more than a specified amount of time. When this callback is triggered, \f3gl_get_line()\f1 will return \f3NULL\f1, and a following call to \f3gl_return_status()\f1 will return \f3GLR_BLOCKED\f1. Beware that \f3gl_get_line()\f1 won't return until the user hasn't typed a key for the specified interval, so if the interval is long, and the user keeps typing, \f3gl_get_line()\f1 may not return for a while. In other words there is no guarantee that it will return in the time specified. .SH THE SERVER DEMO PROGRAM The \f3demo3\f1 program that is distributed with the library, provides a working example of how to use non-blocking server I/O mode in a real program. As far as the user is concerned, this program operates identically to the main demo program (called \f3demo\f1), except that whereas the main demo program uses the normal blocking I/O mode, \f3demo3\f1 using non-blocking I/O and an external event loop. The source code can be found in \f3demo3.c\f1, and the comments therein explain the various steps. .SH FILES .nf libtecla.a - The tecla library libtecla.h - The tecla header file. .fi .SH SEE ALSO .nf libtecla(@LIBR_MANEXT@), gl_get_line(@FUNC_MANEXT@), tecla(@MISC_MANEXT@), ef_expand_file(@FUNC_MANEXT@), cpl_complete_word(@FUNC_MANEXT@), pca_lookup_file(@FUNC_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/func/pca_last_error.in0100644000076400007640000000006007600313616016164 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/new_GetLine.in0100644000076400007640000000005407600313616015370 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/new_PathCache.in0100644000076400007640000000006007600313616015656 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/new_WordCompletion.in0100644000076400007640000000006207600313616017005 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/pca_scan_path.in0100644000076400007640000000006007600313616015750 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/pca_set_check_fn.in0100644000076400007640000000006007600313616016423 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/pca_path_completions.in0100644000076400007640000000006007600313616017360 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/ppc_file_start.in0100644000076400007640000000006007600313616016163 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/ppc_literal_escapes.in0100644000076400007640000000006007600313616017166 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/cpl_recall_matches.in0100644000076400007640000000006207632055543017002 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/gl_bind_keyseq.in0100644000076400007640000000005407632055571016157 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_abandon_line.in0100644000076400007640000000005307600313615016261 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/gl_get_line.in0100644000076400007640000026377010054232413015450 0ustar mcsmcs.\" Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH gl_get_line @FUNC_MANEXT@ .SH NAME gl_get_line, new_GetLine, del_GetLine, gl_customize_completion, gl_change_terminal, gl_configure_getline, gl_load_history, gl_save_history, gl_group_history, gl_show_history, gl_watch_fd, gl_inactivity_timeout, gl_terminal_size, gl_set_term_size, gl_resize_history, gl_limit_history, gl_clear_history, gl_toggle_history, gl_lookup_history, gl_state_of_history, gl_range_of_history, gl_size_of_history, gl_echo_mode, gl_replace_prompt, gl_prompt_style, gl_ignore_signal, gl_trap_signal, gl_last_signal, gl_completion_action, gl_display_text, gl_return_status, gl_error_message, gl_catch_blocked, gl_list_signals, gl_bind_keyseq, gl_erase_terminal, gl_automatic_history, gl_append_history, gl_query_char, gl_read_char \- allow the user to compose an input line .SH SYNOPSIS .nf #include #include GetLine *new_GetLine(size_t linelen, size_t histlen); GetLine *del_GetLine(GetLine *gl); char *gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); int gl_query_char(GetLine *gl, const char *prompt, char defchar); int gl_read_char(GetLine *gl); int gl_customize_completion(GetLine *gl, void *data, CplMatchFn *match_fn); int gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term); int gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file); int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin, const char *keyseq, const char *action); int gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines); int gl_load_history(GetLine *gl, const char *filename, const char *comment); int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data); int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *callback, void *data, unsigned long sec, unsigned long nsec); int gl_group_history(GetLine *gl, unsigned stream); int gl_show_history(GetLine *gl, FILE *fp, const char *fmt, int all_groups, int max_lines); int gl_resize_history(GetLine *gl, size_t bufsize); void gl_limit_history(GetLine *gl, int max_lines); void gl_clear_history(GetLine *gl, int all_groups); void gl_toggle_history(GetLine *gl, int enable); GlTerminalSize gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline); int gl_set_term_size(GetLine *gl, int ncolumn, int nline); int gl_lookup_history(GetLine *gl, unsigned long id, GlHistoryLine *hline); void gl_state_of_history(GetLine *gl, GlHistoryState *state); void gl_range_of_history(GetLine *gl, GlHistoryRange *range); void gl_size_of_history(GetLine *gl, GlHistorySize *size); void gl_echo_mode(GetLine *gl, int enable); void gl_replace_prompt(GetLine *gl, const char *prompt); void gl_prompt_style(GetLine *gl, GlPromptStyle style); int gl_ignore_signal(GetLine *gl, int signo); int gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value); int gl_last_signal(GetLine *gl); int gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq); int gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq); int gl_display_text(GetLine *gl, int indentation, const char *prefix, const char *suffix, int fill_char, int def_width, int start, const char *string); GlReturnStatus gl_return_status(GetLine *gl); const char *gl_error_message(GetLine *gl, char *buff, size_t n); void gl_catch_blocked(GetLine *gl); int gl_list_signals(GetLine *gl, sigset_t *set); int gl_append_history(GetLine *gl, const char *line); int gl_automatic_history(GetLine *gl, int enable); .fi .SH DESCRIPTION The \f3gl_get_line()\f1 function is part of the tecla library (see the \f3libtecla(@LIBR_MANEXT@)\f1 man page). If the user is typing at a terminal, each call prompts them for an line of input, then provides interactive editing facilities, similar to those of the unix \f3tcsh\f1 shell. In addition to simple command-line editing, it supports recall of previously entered command lines, TAB completion of file names, and in-line wild-card expansion of filenames. Documentation of both the user-level command-line editing features and all user configuration options, can be found in the \f3tecla(@MISC_MANEXT@)\f1 man page. This man page concerns itself with documentation for programmers interested in using this library in their application. .sp .SH AN EXAMPLE The following shows a complete example of how to use the \f3gl_get_line()\f1 function to get input from the user: .nf #include #include #include int main(int argc, char *argv[]) { char *line; /* The line that the user typed */ GetLine *gl; /* The gl_get_line() resource object */ setlocale(LC_CTYPE, ""); /* Adopt the user's choice */ /* of character set. */ gl = new_GetLine(1024, 2048); if(!gl) return 1; while((line=gl_get_line(gl, "$ ", NULL, -1)) != NULL && strcmp(line, "exit\\n") != 0) printf("You typed: %s\\n", line); gl = del_GetLine(gl); return 0; } .fi .sp In the example, first the resources needed by the \f3gl_get_line()\f1 function are created by calling \f3new_GetLine()\f1. This allocates the memory used in subsequent calls to the \f3gl_get_line()\f1 function, including the history buffer for recording previously entered lines. Then one or more lines are read from the user, until either an error occurs, or the user types \f3exit\f1. Then finally the resources that were allocated by \f3new_GetLine()\f1, are returned to the system by calling \f3del_GetLine()\f1. Note the use of the \f3NULL\f1 return value of \f3del_GetLine()\f1 to make \f3gl\f1 \f3NULL\f1. This is a safety precaution. If the program subsequently attempts to pass \f3gl\f1 to \f3gl_get_line()\f1, said function will complain, and return an error, instead of attempting to use the deleted resource object. .sp .SH THE FUNCTIONS USED IN THE EXAMPLE The descriptions of the functions used in the example are as follows: .sp .nf GetLine *new_GetLine(size_t linelen, size_t histlen) .fi .sp This function creates the resources used by the \f3gl_get_line()\f1 function and returns an opaque pointer to the object that contains them. The maximum length of an input line is specified via the \f3linelen\f1 argument, and the number of bytes to allocate for storing history lines is set by the \f3histlen\f1 argument. History lines are stored back-to-back in a single buffer of this size. Note that this means that the number of history lines that can be stored at any given time, depends on the lengths of the individual lines. If you want to place an upper limit on the number of lines that can be stored, see the \f3gl_limit_history()\f1 function described later. If you don't want history at all, specify \f3histlen\f1 as zero, and no history buffer will be allocated. .sp On error, a message is printed to \f3stderr\f1 and \f3NULL\f1 is returned. .sp .nf GetLine *del_GetLine(GetLine *gl) .fi .sp This function deletes the resources that were returned by a previous call to \f3new_GetLine()\f1. It always returns \f3NULL\f1 (ie a deleted object). It does nothing if the \f3gl\f1 argument is \f3NULL\f1. .sp .nf char *gl_get_line(GetLine *gl, const char *prompt, const char *start_line, int start_pos); .fi .sp The \f3gl_get_line()\f1 function can be called any number of times to read input from the user. The \f3gl\f1 argument must have been previously returned by a call to \f3new_GetLine()\f1. The \f3prompt\f1 argument should be a normal \f3NUL\f1 terminated string, specifying the prompt to present the user with. By default prompts are displayed literally, but if enabled with the \f3gl_prompt_style()\f1 function (see later), prompts can contain directives to do underlining, switch to and from bold fonts, or turn highlighting on and off. If you want to specify the initial contents of the line, for the user to edit, pass the desired string via the \f3start_line\f1 argument. You can then specify which character of this line the cursor is initially positioned over, using the \f3start_pos\f1 argument. This should be -1 if you want the cursor to follow the last character of the start line. If you don't want to preload the line in this manner, send \f3start_line\f1 as \f3NULL\f1, and set \f3start_pos\f1 to -1. Note that the line pointer returned by one call to \f3gl_get_line()\f1 can be passed back to the next call to \f3gl_get_line()\f1 via the \f3start_line\f1. This allows the application to take the last entered line, and if it contains an error, to then present it back to the user for re-editing, with the cursor initially positioned where the error was encountered. The \f3gl_get_line()\f1 function returns a pointer to the line entered by the user, or \f3NULL\f1 on error or at the end of the input. The returned pointer is part of the specified \f3gl\f1 resource object, and thus should not be free'd by the caller, or assumed to be unchanging from one call to the next. When reading from a user at a terminal, there will always be a newline character at the end of the returned line. When standard input is being taken from a pipe or a file, there will similarly be a newline unless the input line was too long to store in the internal buffer. In the latter case you should call \f3gl_get_line()\f1 again to read the rest of the line. Note that this behavior makes \f3gl_get_line()\f1 similar to \f3fgets()\f1. In fact when \f3stdin\f1 isn't connected to a terminal,\f3gl_get_line()\f1 just calls \f3fgets()\f1. .SH THE RETURN STATUS OF GL_GET_LINE As described above, the \f3gl_get_line()\f1 function has two possible return values; a pointer to the completed input line, or \f3NULL\f1. Extra information about what caused \f3gl_get_line()\f1 to return is available both by inspecting \f3errno\f1, and by calling the \f3gl_return_status()\f1 function. .sp .nf GlReturnStatus gl_return_status(GetLine *gl); .fi .sp The following are the possible enumerated values that this function returns. .sp .nf GLR_NEWLINE - The last call to \f3gl_get_line()\f1 successfully returned a completed input line. GLR_BLOCKED - \f3gl_get_line()\f1 was in non-blocking server mode, and returned early to avoid blocking the process while waiting for terminal I/O. The \f3gl_pending_io()\f1 function can be used to see what type of I/O \f3gl_get_line()\f1 was waiting for. (see the \f3gl_io_mode(@FUNC_MANEXT@)\f1 man page for details). GLR_SIGNAL - A signal was caught by \f3gl_get_line()\f1 that had an after-signal disposition of \f3GLS_ABORT\f1 (See \f3gl_trap_signal()\f1). GLR_TIMEOUT - The inactivity timer expired while \f3gl_get_line()\f1 was waiting for input, and the timeout callback function returned \f3GLTO_ABORT\f1. See \f3gl_inactivity_timeout()\f1 for information about timeouts. GLR_FDABORT - An application I/O callack returned \f3GLFD_ABORT\f1 (see \f3gl_watch_fd()\f1). GLR_EOF - End of file reached. This can happen when input is coming from a file or a pipe, instead of the terminal. It also occurs if the user invokes the \f3list-or-eof\f1 or \f3del-char-or-list-or-eof\f1 actions at the start of a new line. GLR_ERROR - An unexpected error caused \f3gl_get_line()\f1 to abort (consult \f3errno\f1 and/or \f3gl_error_message()\f1 for details. .fi .sp When \f3gl_return_status()\f1 returns \f3GLR_ERROR\f1, and the value of \f3errno\f1 isn't sufficient to explain what happened, you can use the \f3gl_error_message()\f1 function to request a description of the last error that occurred. .sp .nf const char *gl_error_message(GetLine *gl, char *buff, size_t n); .fi .sp The return value is a pointer to the message that occurred. If the \f3buff\f1 argument is \f3NULL\f1, this will be a pointer to a buffer within \f3gl\f1, who's value will probably change on the next call to any function associated with \f3gl_get_line()\f1. Otherwise, if a non-\f3NULL\f1 \f3buff\f1 argument is provided, the error message, including a \f3'\\0'\f1 terminator, will be written within the first \f3n\f1 elements of this buffer, and the return value will be a pointer to the first element of this buffer. If the message won't fit in the provided buffer, it will be truncated to fit. .SH OPTIONAL PROMPT FORMATTING Whereas by default the prompt string that you specify is displayed literally, without any special interpretation of the characters within it, the \f3gl_prompt_style()\f1 function can be used to enable optional formatting directives within the prompt. .sp .nf void gl_prompt_style(GetLine *gl, GlPromptStyle style); .fi .sp The \f3style\f1 argument, which specifies the formatting style, can take any of the following values: .sp .nf GL_FORMAT_PROMPT - In this style, the formatting directives described below, when included in prompt strings, are interpreted as follows: %B - Display subsequent characters with a bold font. %b - Stop displaying characters with the bold font. %F - Make subsequent characters flash. %f - Turn off flashing characters. %U - Underline subsequent characters. %u - Stop underlining characters. %P - Switch to a pale (half brightness) font. %p - Stop using the pale font. %S - Highlight subsequent characters (also known as standout mode). %s - Stop highlighting characters. %V - Turn on reverse video. %v - Turn off reverse video. %% - Display a single % character. For example, in this mode, a prompt string like \f3"%UOK%u$ "\f1 would display the prompt \f3"OK$ "\f1, but with the \f3OK\f1 part underlined. Note that although a pair of characters that starts with a % character, but doesn't match any of the above directives is displayed literally, if a new directive is subsequently introduced which does match, the displayed prompt will change, so it is better to always use %% to display a literal %. Also note that not all terminals support all of these text attributes, and that some substitute a different attribute for missing ones. GL_LITERAL_PROMPT - In this style, the prompt string is printed literally. This is the default style. .fi .SH ALTERNATE CONFIGURATION SOURCES As mentioned above, by default users have the option of configuring the behavior of \f3gl_get_line()\f1 via a configuration file called \f3\&.teclarc\f1 in their home directories. The fact that all applications share this same configuration file is both an advantage and a disadvantage. In most cases it is an advantage, since it encourages uniformity, and frees the user from having to configure each application separately. In some applications, however, this single means of configuration is a problem. This is particularly true of embedded software, where there's no filesystem to read a configuration file from, and also in applications where a radically different choice of keybindings is needed to emulate a legacy keyboard interface. To cater for such cases, the following function allows the application to control where configuration information is read from. .sp .nf int gl_configure_getline(GetLine *gl, const char *app_string, const char *app_file, const char *user_file); .fi .sp It allows the configuration commands that would normally be read from a user's \f3~/.teclarc\f1 file, to be read from any or none of, a string, an application specific configuration file, and/or a user-specific configuration file. If this function is called before the first call to \f3gl_get_line()\f1, the default behavior of reading \f3~/.teclarc\f1 on the first call to \f3gl_get_line()\f1 is disabled, so all configuration must be achieved using the configuration sources specified with this function. If \f3app_string != NULL\f1, then it is interpreted as a string containing one or more configuration commands, separated from each other in the string by embedded newline characters. If \f3app_file != NULL\f1 then it is interpreted as the full pathname of an application-specific configuration file. If \f3user_file != NULL\f1 then it is interpreted as the full pathname of a user-specific configuration file, such as \f3~/.teclarc\f1. For example, in the following call, .sp .nf gl_configure_getline(gl, "edit-mode vi \\n nobeep", "/usr/share/myapp/teclarc", "~/.teclarc"); .fi .sp the \f3app_string\f1 argument causes the calling application to start in vi edit-mode, instead of the default emacs mode, and turns off the use of the terminal bell by the library. It then attempts to read system-wide configuration commands from an optional file called \f3/usr/share/myapp/teclarc\f1, then finally reads user-specific configuration commands from an optional \f3\&.teclarc\f1 file in the user's home directory. Note that the arguments are listed in ascending order of priority, with the contents of \f3app_string\f1 being potentially overriden by commands in \f3app_file\f1, and commands in \f3app_file\f1 potentially being overriden by commands in \f3user_file\f1. .sp You can call this function as many times as needed, the results being cumulative, but note that copies of any filenames specified via the \f3app_file\f1 and \f3user_file\f1 arguments are recorded internally for subsequent use by the \f3read-init-files\f1 key-binding function, so if you plan to call this function multiple times, be sure that the last call specifies the filenames that you want re-read when the user requests that the configuration files be re-read. .sp Individual key sequences can also be bound and unbound using the \f3gl_bind_keyseq()\f1 function. .sp .nf int gl_bind_keyseq(GetLine *gl, GlKeyOrigin origin, const char *keyseq, const char *action); .fi .sp The \f3origin\f1 argument specifies the priority of the binding, according to who it is being established for, and must be one of the following two values. .sp .nf GL_USER_KEY - The user requested this key-binding. GL_APP_KEY - This is a default binding set by the application. .fi .sp When both user and application bindings for a given key-sequence have been specified, the user binding takes precedence. The application's binding is subsequently reinstated if the user's binding is later unbound via either another to this function, or a call to \f3gl_configure_getline()\f1. The \f3keyseq\f1 argument specifies the key-sequence to be bound or unbound, and is expressed in the same way as in a \f3~/.teclarc\f1 configuration file. The \f3action\f1 argument must either be a string containing the name of the action to bind the key-sequence to, or it must be \f3NULL\f1 or "" to unbind the key-sequence. .SH CUSTOMIZED WORD COMPLETION If in your application, you would like to have TAB completion complete other things in addition to or instead of filenames, you can arrange this by registering an alternate completion callback function, via a call to the \f3gl_customize_completion()\f1 function. .sp .nf int gl_customize_completion(GetLine *gl, void *data, CplMatchFn *match_fn); .fi .sp The \f3data\f1 argument provides a way for your application to pass arbitrary, application-specific information to the callback function. This is passed to the callback every time that it is called. It might for example, point to the symbol table from which possible completions are to be sought. The \f3match_fn\f1 argument specifies the callback function to be called. The \f3CplMatchFn\f1 function type is defined in \f3libtecla.h\f1, as is a \f3CPL_MATCH_FN()\f1 macro that you can use to declare and prototype callback functions. The declaration and responsibilities of callback functions are described in depth in the \f1cpl_complete_word(@FUNC_MANEXT@)\f1 man page. .sp In brief, the callback function is responsible for looking backwards in the input line, back from the point at which the user pressed TAB, to find the start of the word being completed. It then must lookup possible completions of this word, and record them one by one in the \f3WordCompletion\f1 object that is passed to it as an argument, by calling the \f3cpl_add_completion()\f1 function. If the callback function wishes to provide filename completion in addition to its own specific completions, it has the option of itself calling the builtin file-name completion callback. This also, is documented in the \f3cpl_complete_word(@FUNC_MANEXT@)\f1 man page. .sp Note that if you would like \f3gl_get_line()\f1 to return the current input line when a successful completion is been made, you can arrange this when you call \f3cpl_add_completion()\f1, by making the last character of the continuation suffix a newline character. If you do this, the input line will be updated to display the completion, together with any contiuation suffix up to the newline character, then \f3gl_get_line()\f1 will return this input line. .sp If, for some reason, your callback function needs to write something to the terminal, it must call \f3gl_normal_io()\f1 before doing so. This will start a new line after the input line that is currently being edited, reinstate normal terminal I/O, and tell \f3gl_get_line()\f1 that the input line will need to be redrawn when the callback returns. .SH ADDING COMPLETION ACTIONS In the previous section the ability to customize the behavior of the only default completion action, \f3complete-word\f1, was described. In this section the ability to install additional action functions, so that different types of word completion can be bound to different key-sequences, is described. This is achieved by using the \f3gl_completion_action()\f1 function. .sp .nf int gl_completion_action(GetLine *gl, void *data, CplMatchFn *match_fn, int list_only, const char *name, const char *keyseq); .fi .sp The \f3data\f1 and \f3match_fn\f1 arguments are as described in the \f3cpl_complete_word\f1 man page, and specify the callback function that should be invoked to identify possible completions. The \f3list_only\f1 argument determines whether the action that is being defined should attempt to complete the word as far as possible in the input line before displaying any possible ambiguous completions, or whether it should simply display the list of possible completions without touching the input line. The former option is selected by specifying a value of \f30\f1, and the latter by specifying a value of \f31\f1. The \f3name\f1 argument specifies the name by which configuration files and future invokations of this function should refer to the action. This must either be the name of an existing completion action to be changed, or be a new unused name for a new action. Finally, the \f3keyseq\f1 argument specifies the default key-sequence to bind the action to. If this is \f3NULL\f1, no new keysequence will be bound to the action. Beware that in order for the user to be able to change the key-sequence that is bound to actions that are installed in this manner, when you call \f3gl_completion_action()\f1 to install a given action for the first time, you should do this between calling \f3new_GetLine()\f1 and the first call to \f3gl_get_line()\f1. Otherwise, when the user's configuration file is read on the first call to \f3gl_get_line()\f1, the name of the your additional action won't be known, and any reference to it in the configuration file will generate an error. As discussed for \f3gl_customize_completion()\f1, if your callback function, for some reason, needs to write anything to the terminal, it must call \f3gl_normal_io()\f1 before doing so. .SH DEFINING CUSTOM ACTIONS Although the built-in key-binding actions are sufficient for the needs of most applications, occasionally a specialized application may need to define one or more custom actions, bound to application-specific key-sequences. For example, a sales application would benefit from having a key-sequence that displayed the part name that corresponded to a part number preceding the cursor. Such a feature is clearly beyond the scope of the built-in action functions. So for such special cases, the \f3gl_register_action()\f1 function is provided. .sp .nf int gl_register_action(GetLine *gl, void *data, GlActionFn *fn, const char *name, const char *keyseq); .fi .sp This function lets the application register an external function, \f3fn\f1, that will thereafter be called whenever either the specified key-sequence, \f3keyseq\f1, is entered by the user, or the user enters any other key-sequence that the user subsequently binds to the specified action name, \f3name\f1, in their configuration file. The \f3data\f1 argument can be a pointer to anything that the application wishes to have passed to the action function, \f3fn\f1, whenever that function is invoked. The action function, \f3fn\f1, should be declared using the following macro, which is defined in \f3libtecla.h\f1. .sp .nf #define GL_ACTION_FN(fn) GlAfterAction (fn)(GetLine *gl, \\ void *data, int count, size_t curpos, \\ const char *line) .fi .sp The \f3gl\f1 and \f3data\f1 arguments are those that were previously passed to \f3gl_register_action()\f1 when the action function was registered. The \f3count\f1 argument is a numeric argument which the user has the option of entering using the \f3digit-argument\f1 action, before invoking the action. If the user doesn't enter a number, then the \f3count\f1 argument is set to 1. Nominally this argument is interpreted as a repeat count, meaning that the action should be repeated that many times. In practice however, for some actions a repeat count makes little sense. In such cases, actions can either simply ignore the \f3count\f1 argument, or use its value for a different purpose. A copy of the current input line is passed in the read-only \f3line\f1 argument. The current cursor position within this string is given by the index contained in the \f3curpos\f1 argument. Note that direct manipulation of the input line and the cursor position is not permitted. This is because the rules dicated by various modes, such as vi mode versus emacs mode, no-echo mode, and insert mode versus overstrike mode etc, make it too complex for an application writer to write a conforming editing action, as well as constrain future changes to the internals of \f3gl_get_line()\f1. A potential solution to this dilema would be to allow the action function to edit the line using the existing editing actions. This is currently under consideration. If the action function wishes to write text to the terminal, without this getting mixed up with the displayed text of the input line, or read from the terminal without having to handle raw terminal I/O, then before doing either of these operations, it must temporarily suspend line editing by calling the \f3gl_normal_io()\f1 function. This function flushes any pending output to the terminal, moves the cursor to the start of the line that follows the last terminal line of the input line, then restores the terminal to a state that is suitable for use with the C stdio facilities. The latter includes such things as restoring the normal mapping of \f3\\n\f1 to \f3\\r\\n\f1, and, when in server mode, restoring the normal blocking form of terminal I/O. Having called this function, the action function can read from and write to the terminal without the fear of creating a mess. It isn't necessary for the action function to restore the original editing environment before it returns. This is done automatically by \f3gl_get_line()\f1 after the action function returns. The following is a simple example of an action function which writes the sentence "Hello world" on a new terminal line after the line being edited. When this function returns, the input line is redrawn on the line that follows the "Hello world" line, and line editing resumes. .sp .nf static GL_ACTION_FN(say_hello_fn) { if(gl_normal_io(gl)) /* Temporarily suspend editing */ return GLA_ABORT; printf("Hello world\\n"); return GLA_CONTINUE; } .fi .sp Action functions must return one of the following values, to tell \f3gl_get_line()\f1 how to procede. .sp .nf GLA_ABORT - Cause gl_get_line() to return NULL. GLA_RETURN - Cause gl_get_line() to return the completed input line. GLA_CONTINUE - Resume command-line editing. .fi .sp Note that the \f3name\f1 argument of \f3gl_register_action()\f1 specifies the name by which a user can refer to the action in their configuration file. This allows them to re-bind the action to an alternate key-seqeunce. In order for this to work, it is necessary to call \f3gl_register_action()\f1 between calling \f3new_GetLine()\f1 and the first call to \f3gl_get_line()\f1. .SH HISTORY FILES To save the contents of the history buffer before quitting your application, and subsequently restore them when you next start the application, the following functions are provided. .sp .nf int gl_save_history(GetLine *gl, const char *filename, const char *comment, int max_lines); int gl_load_history(GetLine *gl, const char *filename, const char *comment); .fi .sp The \f3filename\f1 argument specifies the name to give the history file when saving, or the name of an existing history file, when loading. This may contain home-directory and environment variable expressions, such as "~/.myapp_history" or "$HOME/.myapp_history". .sp Along with each history line, extra information about it, such as when it was entered by the user, and what its nesting level is, is recorded as a comment preceding the line in the history file. Writing this as a comment allows the history file to double as a command file, just in case you wish to replay a whole session using it. Since comment prefixes differ in different languages, the \f3comment\f1 argument is provided for specifying the comment prefix. For example, if your application were a unix shell, such as the bourne shell, you would specify "#" here. Whatever you choose for the comment character, you must specify the same prefix to \f3gl_load_history()\f1 that you used when you called \f3gl_save_history()\f1 to write the history file. .sp The \f3max_lines\f1 must be either -1 to specify that all lines in the history list be saved, or a positive number specifying a ceiling on how many of the most recent lines should be saved. .sp Both fuctions return non-zero on error, after writing an error message to stderr. Note that \f3gl_load_history()\f1 does not consider the non-existence of a file to be an error. .SH MULTIPLE HISTORY LISTS If your application uses a single \f3GetLine\f1 object for entering many different types of input lines, you may wish \f3gl_get_line()\f1 to distinguish the different types of lines in the history list, and only recall lines that match the current type of line. To support this requirement, \f3gl_get_line()\f1 marks lines being recorded in the history list with an integer identifier chosen by the application. Initially this identifier is set to \f10\f3 by \f3new_GetLine()\f1, but it can be changed subsequently by calling \f3gl_group_history()\f1. .sp .nf int gl_group_history(GetLine *gl, unsigned id); .fi .sp The integer identifier \f3id\f1 can be any number chosen by the application, but note that \f3gl_save_history()\f1 and \f3gl_load_history()\f1 preserve the association between identifiers and historical input lines between program invokations, so you should choose fixed identifiers for the different types of input line used by your application. .sp Whenever \f3gl_get_line()\f1 appends a new input line to the history list, the current history identifier is recorded with it, and when it is asked to recall a historical input line, it only recalls lines that are marked with the current identifier. .SH DISPLAYING HISTORY The history list can be displayed by calling \f3gl_show_history()\f1. .sp .nf int gl_show_history(GetLine *gl, FILE *fp, const char *fmt, int all_groups, int max_lines); .fi .sp This displays the current contents of the history list to the stdio output stream \f3fp\f1. If the \f3max_lines\f1 argument is greater than or equal to zero, then no more than this number of the most recent lines will be displayed. If the \f3all_groups\f1 argument is non-zero, lines from all history groups are displayed. Otherwise just those of the currently selected history group are displayed. The format string argument, \f3fmt\f1, determines how the line is displayed. This can contain arbitrary characters which are written verbatim, interleaved with any of the following format directives: .nf %D - The date on which the line was originally entered, formatted like 2001-11-20. %T - The time of day when the line was entered, formatted like 23:59:59. %N - The sequential entry number of the line in the history buffer. %G - The number of the history group which the line belongs to. %% - A literal % character. %H - The history line itself. .fi Thus a format string like \f3"%D %T %H\n"\f1 would output something like: .nf 2001-11-20 10:23:34 Hello world .fi Note the inclusion of an explicit newline character in the format string. .SH LOOKING UP HISTORY The \f3gl_lookup_history()\f1 function allows the calling application to look up lines in the history list. .sp .nf typedef struct { const char *line; /* The requested historical */ /* line. */ unsigned group; /* The history group to which */ /* the line belongs. */ time_t timestamp; /* The date and time at which */ /* the line was originally */ /* entered. */ } GlHistoryLine; int gl_lookup_history(GetLine *gl, unsigned long id, GlHistoryLine *hline); .fi .sp The \f3id\f1 argument indicates which line to look up, where the first line that was entered in the history list after \f3new_GetLine()\f1 was called, is denoted by 0, and subsequently entered lines are denoted with successively higher numbers. Note that the range of lines currently preserved in the history list can be queried by calling the \f3gl_range_of_history()\f1 function, described later. If the requested line is in the history list, the details of the line are recorded in the variable pointed to by the \f3hline\f1 argument, and \f31\f1 is returned. Otherwise \f30\f1 is returned, and the variable pointed to by \f3hline\f1 is left unchanged. .sp Beware that the string returned in \f3hline->line\f1 is part of the history buffer, so it must not be modified by the caller, and will be recycled on the next call to any function that takes \f3gl\f1 as its argument. Therefore you should make a private copy of this string if you need to keep it around. .SH MANUAL HISTORY ARCHIVAL By default, whenever a line is entered by the user, it is automatically appended to the history list, just before \f3gl_get_line()\f1 returns the line to the caller. This is convenient for the majority of applications, but there are also applications that need finer grained control over what gets added to the history list. In such cases, the automatic addition of entered lines to the history list can be turned off by calling the \f3gl_automatic_history()\f1 function. .sp .nf int gl_automatic_history(GetLine *gl, int enable); .fi .sp If this function is called with its \f3enable\f1 argument set to \f30\f1, \f3gl_get_line()\f1 won't automatically archive subsequently entered lines. Automatic archiving can be reenabled at a later time, by calling this function again, with its \f3enable\f1 argument set to 1. While automatic history archiving is disabled, the calling application can use the \f3gl_append_history()\f1 to append lines to the history list as needed. .sp .nf int gl_append_history(GetLine *gl, const char *line); .fi .sp The \f3line\f1 argument specifies the line to be added to the history list. This must be a normal \f3'\0'\f1 terminated string. If this string contains any newline characters, the line that gets archived in the history list will be terminated by the first of these. Otherwise it will be terminated by the \f3'\0'\f1 terminator. If the line is longer than the maximum input line length, that was specified when \f3new_GetLine()\f1 was called, when the line is recalled, it will get truncated to the actual \f3gl_get_line()\f1 line length. If successful, \f3gl_append_history()\f1 returns 0. Otherwise it returns non-zero, and sets \f3errno\f1 to one of the following values. .sp .nf EINVAL - One of the arguments passed to gl_append_history() was NULL. ENOMEM - The specified line was longer than the allocated size of the history buffer (as specified when new_GetLine() was called), so it couldn't be archived. .fi .sp A textual description of the error can optionally be obtained by calling \f3gl_error_message()\f1. Note that after such an error, the history list remains in a valid state to receive new history lines, so there is little harm in simply ignoring the return status of \f3gl_append_history()\f1. .SH MISCELLANEOUS HISTORY CONFIGURATION If you wish to change the size of the history buffer that was originally specified in the call to \f3new_GetLine()\f1, you can do so with the \f3gl_resize_history()\f1 function. .sp .nf int gl_resize_history(GetLine *gl, size_t histlen); .fi .sp The \f3histlen\f1 argument specifies the new size in bytes, and if you specify this as 0, the buffer will be deleted. .sp As mentioned in the discussion of \f3new_GetLine()\f1, the number of lines that can be stored in the history buffer, depends on the lengths of the individual lines. For example, a 1000 byte buffer could equally store 10 lines of average length 100 bytes, or 2 lines of average length 50 bytes. Although the buffer is never expanded when new lines are added, a list of pointers into the buffer does get expanded when needed to accomodate the number of lines currently stored in the buffer. To place an upper limit on the number of lines in the buffer, and thus a ceiling on the amount of memory used in this list, you can call the \f3gl_limit_history()\f1 function. .sp .nf void gl_limit_history(GetLine *gl, int max_lines); .fi .sp The \f3max_lines\f1 should either be a positive number \f3>= 0\f1, specifying an upper limit on the number of lines in the buffer, or be \f3-1\f1 to cancel any previously specified limit. When a limit is in effect, only the \f3max_lines\f1 most recently appended lines are kept in the buffer. Older lines are discarded. .sp To discard lines from the history buffer, use the \f3gl_clear_history()\f1 function. .sp .nf void gl_clear_history(GetLine *gl, int all_groups); .fi .sp The \f3all_groups\f1 argument tells the function whether to delete just the lines associated with the current history group (see \f3gl_group_history()\f1), or all historical lines in the buffer. .sp The \f3gl_toggle_history()\f1 function allows you to toggle history on and off without losing the current contents of the history list. .sp .nf void gl_toggle_history(GetLine *gl, int enable); .fi .sp Setting the \f3enable\f1 argument to 0 turns off the history mechanism, and setting it to 1 turns it back on. When history is turned off, no new lines will be added to the history list, and history lookup key-bindings will act as though there is nothing in the history buffer. .SH QUERYING HISTORY INFORMATION The configured state of the history list can be queried with the \f3gl_history_state()\f1 function. .sp .nf typedef struct { int enabled; /* True if history is enabled */ unsigned group; /* The current history group */ int max_lines; /* The current upper limit on the */ /* number of lines in the history */ /* list, or -1 if unlimited. */ } GlHistoryState; void gl_state_of_history(GetLine *gl, GlHistoryState *state); .fi .sp On return, the status information is recorded in the variable pointed to by the \f3state\f1 argument. .sp The \f3gl_range_of_history()\f1 function returns the number and range of lines in the history list. .sp .nf typedef struct { unsigned long oldest; /* The sequential entry number */ /* of the oldest line in the */ /* history list. */ unsigned long newest; /* The sequential entry number */ /* of the newest line in the */ /* history list. */ int nlines; /* The number of lines in the */ /* history list. */ } GlHistoryRange; void gl_range_of_history(GetLine *gl, GlHistoryRange *range); .fi .sp The return values are recorded in the variable pointed to by the \f3range\f1 argument. If the \f3nlines\f1 member of this structure is greater than zero, then the \f3oldest\f1 and \f3newest\f1 members report the range of lines in the list, and \f3newest=oldest+nlines-1\f1. Otherwise they are both zero. .sp The \f3gl_size_of_history()\f1 function returns the total size of the history buffer and the amount of the buffer that is currently occupied. .sp .nf typedef struct { size_t size; /* The size of the history buffer */ /* (bytes). */ size_t used; /* The number of bytes of the */ /* history buffer that are */ /* currently occupied. */ } GlHistorySize; void gl_size_of_history(GetLine *gl, GlHistorySize *size); .fi .sp On return, the size information is recorded in the variable pointed to by the \f3size\f1 argument. .SH CHANGING TERMINALS The \f3new_GetLine()\f1 constructor function assumes that input is to be read from \f3stdin\f1, and output written to \f3stdout\f1. The following function allows you to switch to different input and output streams. .sp .nf int gl_change_terminal(GetLine *gl, FILE *input_fp, FILE *output_fp, const char *term); .fi .sp The \f3gl\f1 argument is the object that was returned by \f3new_GetLine()\f1. The \f3input_fp\f1 argument specifies the stream to read from, and \f3output_fp\f1 specifies the stream to be written to. Only if both of these refer to a terminal, will interactive terminal input be enabled. Otherwise \f3gl_get_line()\f1 will simply call \f3fgets()\f1 to read command input. If both streams refer to a terminal, then they must refer to the same terminal, and the type of this terminal must be specified via the \f3term\f1 argument. The value of the \f3term\f1 argument is looked up in the terminal information database (terminfo or termcap), in order to determine which special control sequences are needed to control various aspects of the terminal. \f3new_GetLine()\f1 for example, passes the return value of \f3getenv("TERM")\f1 in this argument. Note that if one or both of \f3input_fp\f1 and \f3output_fp\f1 don't refer to a terminal, then it is legal to pass \f3NULL\f1 instead of a terminal type. .sp Note that if you want to pass file descriptors to \f3gl_change_terminal()\f1, you can do this by creating stdio stream wrappers using the POSIX \f3fdopen()\f1 function. .SH EXTERNAL EVENT HANDLING By default, \f3gl_get_line()\f1 doesn't return until either a complete input line has been entered by the user, or an error occurs. In programs that need to watch for I/O from other sources than the terminal, there are two options. .sp .nf 1. Use the functions described in the \f3gl_io_mode(@FUNC_MANEXT@)\f1 man page to switch \f3gl_get_line()\f1 into non-blocking server mode. In this mode, \f3gl_get_line()\f1 becomes a non-blocking, incremental line-editing function that can safely be called from an external event loop. Although this is a very versatile method, it involves taking on some responsibilities that are normally performed behind the scenes by \f3gl_get_line()\f1. 2. While \f3gl_get_line()\f1 is waiting for keyboard input from the user, you can ask it to also watch for activity on arbitrary file descriptors, such as network sockets, pipes etc, and have it call functions of your choosing when activity is seen. This works on any system that has the \f3select()\f1 system call, which is most, if not all flavors of unix. .fi .sp Registering a file descriptor to be watched by \f3gl_get_line()\f1 involves calling the \f3gl_watch_fd()\f1 function. .sp .nf int gl_watch_fd(GetLine *gl, int fd, GlFdEvent event, GlFdEventFn *callback, void *data); .fi .sp If this returns non-zero, then it means that either your arguments are invalid, or that this facility isn't supported on the host system. .sp The \f3fd\f1 argument is the file descriptor to be watched. The \f3event\f1 argument specifies what type of activity is of interest, chosen from the following enumerated values: .sp .nf GLFD_READ - Watch for the arrival of data to be read. GLFD_WRITE - Watch for the ability to write to the file descriptor without blocking. GLFD_URGENT - Watch for the arrival of urgent out-of-band data on the file descriptor. .fi .sp The \f3callback\f1 argument is the function to call when the selected activity is seen. It should be defined with the following macro, which is defined in libtecla.h. .sp .nf #define GL_FD_EVENT_FN(fn) GlFdStatus (fn)(GetLine *gl, \\ void *data, int fd, \\ GlFdEvent event) .fi .sp The \f3data\f1 argument of the \f3gl_watch_fd()\f1 function is passed to the callback function for its own use, and can point to anything you like, including \f3NULL\f1. The file descriptor and the event argument are also passed to the callback function, and this potentially allows the same callback function to be registered to more than one type of event and/or more than one file descriptor. The return value of the callback function should be one of the following values. .sp .nf GLFD_ABORT - Tell gl_get_line() to abort. When this happens, \f3gl_get_line()\f1 returns \f3NULL\f1, and a following call to \f3gl_return_status()\f1 will return \f3GLR_FDABORT\f1. Note that if the application needs \f3errno\f1 always to have a meaningful value when \f3gl_get_line()\f1 returns \f3NULL\f1, the callback function should set \f3errno\f1 appropriately. GLFD_REFRESH - Redraw the input line then continue waiting for input. Return this if your callback wrote to the terminal. GLFD_CONTINUE - Continue to wait for input, without redrawing the line. .fi .sp Note that before calling the callback, \f3gl_get_line()\f1 blocks most signals, and leaves its own signal handlers installed, so if you need to catch a particular signal you will need to both temporarily install your own signal handler, and unblock the signal. Be sure to re-block the signal (if it was originally blocked) and reinstate the original signal handler, if any, before returning. .sp If the callback function needs to read or write to the terminal, it should ideally first call \f3gl_normal_io(gl)\f1 to temporarily suspend line editing. This will restore the terminal to canonical, blocking-I/O, mode, and move the cursor to the start of a new terminal line. Later, when the callback returns, \f3gl_get_line()\f1 will notice that \f3gl_normal_io()\f1 was called, redisplay the input line and resume editing. Note that in this case the return values, \f3GLFD_REFRESH\f1 and \f3GLFD_CONTINUE\f1 are equivalent. .sp To support cases where the callback function calls a third-party function which occasionally and unpredictably writes to the terminal, the automatic conversion of \f3"\n"\f1 to \f3"\r\n"\f1 is re-enabled before the callback function is called. If the callack knows that the third-party function wrote to the terminal, it should then return the \f3GLFD_REFRESH\f1 return value, to tell \f3gl_get_line()\f1 to redisplay the input line. .sp To remove a callback function that you previously registered for a given file descriptor and event, simply call \f3gl_watch_fd()\f1 with the same file descriptor and \f3event\f1 arguments, but with a \f3callback\f1 argument of \f30\f1. The \f3data\f1 argument is ignored in this case. .SH SETTING AN INACTIVITY TIMEOUT On systems with the \f3select()\f1 system call, the \f3gl_inactivity_timeout()\f1 function can be used to set or cancel an inactivity timeout. Inactivity in this case refers both to keyboard input, and to I/O on any file descriptors registered by prior and subsequent calls to \f3gl_watch_fd()\f1. On oddball systems that don't have \f3select()\f1, this call has no effect. .sp .nf int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *callback, void *data, unsigned long sec, unsigned long nsec); .fi .sp The timeout is specified in the form of an integral number of seconds and an integral number of nanoseconds, via the \f3sec\f1 and \f3nsec\f1 arguments respectively. Subsequently, whenever no activity is seen for this time period, the function specified via the \f3callback\f1 argument is called. The \f3data\f1 argument of \f3gl_inactivity_timeout()\f1 is passed verbatim to this callback function whenever it is invoked, and can thus be used to pass arbitrary application-specific information to the callback. The following macro is provided in \f3libtecla.h\f1 for applications to use to declare and prototype timeout callback functions. .sp .nf #define GL_TIMEOUT_FN(fn) \\ GlAfterTimeout (fn)(GetLine *gl, void *data) .fi .sp On returning, the application's callback is expected to return one of the following enumerators to tell \f3gl_get_line()\f1 how to procede after the timeout has been handled by the callback. .sp .nf GLTO_ABORT - Tell gl_get_line() to abort. When this happens, \f3gl_get_line()\f1 will return \f3NULL\f1, and a following call to \f3gl_return_status()\f1 will return \f3GLR_TIMEOUT\f1. Note that if the application needs \f3errno\f1 always to have a meaningful value when \f3gl_get_line()\f1 returns \f3NULL\f1, the callback function should set \f3errno\f1 appropriately. GLTO_REFRESH - Redraw the input line, then continue waiting for input. You should return this value if your callback wrote to the terminal without having first called \f3gl_normal_io(gl)\f1. GLTO_CONTINUE - In normal blocking-I/O mode, continue to wait for input, without redrawing the user's input line. In non-blocking server I/O mode (see gl_io_mode(@FUNC_MANEXT@)), cause \f3gl_get_line()\f1 to act as though I/O blocked. This means that \f3gl_get_line()\f1 will immediately return \f3NULL\f1, and a following call to \f3gl_return_status()\f1 will return \f3GLR_BLOCKED\f1. .fi .sp Note that before calling the callback, \f3gl_get_line()\f1 blocks most signals, and leaves its own signal handlers installed, so if you need to catch a particular signal you will need to both temporarily install your own signal handler, and unblock the signal. Be sure to re-block the signal (if it was originally blocked) and reinstate the original signal handler, if any, before returning. .sp If the callback function needs to read or write to the terminal, it should ideally first call \f3gl_normal_io(gl)\f1 to temporarily suspend line editing. This will restore the terminal to canonical, blocking-I/O, mode, and move the cursor to the start of a new terminal line. Later, when the callback returns, \f3gl_get_line()\f1 will notice that \f3gl_normal_io()\f1 was called, redisplay the input line and resume editing. Note that in this case the return values, \f3GLTO_REFRESH\f1 and \f3GLTO_CONTINUE\f1 are equivalent. .sp To support cases where the callback function calls a third-party function which occasionally and unpredictably writes to the terminal, the automatic conversion of \f3"\n"\f1 to \f3"\r\n"\f1 is re-enabled before the callback function is called. If the callack knows that the third-party function wrote to the terminal, it should then return the \f3GLTO_REFRESH\f1 return value, to tell \f3gl_get_line()\f1 to redisplay the input line. .sp Note that although the timeout argument includes a nano-second component, few computer clocks presently have resolutions that are finer than a few milliseconds, so asking for less than a few milliseconds is equivalent to requesting zero seconds on a lot of systems. If this would be a problem, you should base your timeout selection on the actual resolution of the host clock (eg. by calling \f3sysconf(_SC_CLK_TCK)\f1). .sp To turn off timeouts, simply call \f3gl_inactivity_timeout()\f1 with a \f3callback\f1 argument of \f30\f1. The \f3data\f1 argument is ignored in this case. .SH SIGNAL HANDLING DEFAULTS By default, the \f3gl_get_line()\f1 function intercepts a number of signals. This is particularly important for signals which would by default terminate the process, since the terminal needs to be restored to a usable state before this happens. In this section, the signals that are trapped by default, and how \f3gl_get_line()\f1 responds to them, is described. Changing these defaults is the topic of the following section. .sp When the following subset of signals are caught, \f3gl_get_line()\f1 first restores the terminal settings and signal handling to how they were before \f3gl_get_line()\f1 was called, resends the signal, to allow the calling application's signal handlers to handle it, then if the process still exists, \f3gl_get_line()\f1 returns \f3NULL\f1 and sets \f3errno\f1 as specified below. .sp .nf SIGINT - This signal is generated both by the keyboard interrupt key (usually ^C), and the keyboard break key. errno=EINTR SIGHUP - This signal is generated when the controlling terminal exits. errno=ENOTTY SIGPIPE - This signal is generated when a program attempts to write to a pipe who's remote end isn't being read by any process. This can happen for example if you have called \f3gl_change_terminal()\f1 to redirect output to a pipe hidden under a pseudo terminal. errno=EPIPE SIGQUIT - This signal is generated by the keyboard quit key (usually ^\\). errno=EINTR SIGABRT - This signal is generated by the standard C, abort() function. By default it both terminates the process and generates a core dump. errno=EINTR SIGTERM - This is the default signal that the UN*X kill command sends to processes. errno=EINTR .fi .sp Note that in the case of all of the above signals, POSIX mandates that by default the process is terminated, with the addition of a core dump in the case of the \f3SIGQUIT\f1 signal. In other words, if the calling application doesn't override the default handler by supplying its own signal handler, receipt of the corresponding signal will terminate the application before \f3gl_get_line()\f1 returns. .sp If gl_get_line() aborts with errno set to EINTR, you can find out what signal caused it to abort, by calling the following function. .sp .nf int gl_last_signal(const GetLine *gl); .fi .sp This returns the numeric code (eg. \f3SIGINT\f1) of the last signal that was received during the most recent call to \f3gl_get_line()\f1, or \f3-1\f1 if no signals were received. .sp On systems that support it, when a SIGWINCH (window change) signal is received, \f3gl_get_line()\f1 queries the terminal to find out its new size, redraws the current input line to accomodate the new size, then returns to waiting for keyboard input from the user. Unlike other signals, this signal isn't resent to the application. .sp Finally, the following signals cause \f3gl_get_line()\f1 to first restore the terminal and signal environment to that which prevailed before \f3gl_get_line()\f1 was called, then resend the signal to the application. If the process still exists after the signal has been delivered, then \f3gl_get_line()\f1 then re-establishes its own signal handlers, switches the terminal back to raw mode, redisplays the input line, and goes back to awaiting terminal input from the user. .sp .nf SIGCONT - This signal is generated when a suspended process is resumed. SIGPOLL - On SVR4 systems, this signal notifies the process of an asynchronous I/O event. Note that under 4.3+BSD, SIGIO and SIGPOLL are the same. On other systems, SIGIO is ignored by default, so \f3gl_get_line()\f1 doesn't trap it by default. SIGPWR - This signal is generated when a power failure occurs (presumably when the system is on a UPS). SIGALRM - This signal is generated when a timer expires. SIGUSR1 - An application specific signal. SIGUSR2 - Another application specific signal. SIGVTALRM - This signal is generated when a virtual timer expires (see man setitimer(2)). SIGXCPU - This signal is generated when a process exceeds its soft CPU time limit. SIGXFSZ - This signal is generated when a process exceeds its soft file-size limit. SIGTSTP - This signal is generated by the terminal suspend key, which is usually ^Z, or the delayed terminal suspend key, which is usually ^Y. SIGTTIN - This signal is generated if the program attempts to read from the terminal while the program is running in the background. SIGTTOU - This signal is generated if the program attempts to write to the terminal while the program is running in the background. .fi .sp Obviously not all of the above signals are supported on all systems, so code to support them is conditionally compiled into the tecla library. .sp Note that if \f3SIGKILL\f1 or \f3SIGPOLL\f1, which by definition can't be caught, or any of the hardware generated exception signals, such as \f3SIGSEGV\f1, \f3SIGBUS\f1 and \f3SIGFPE\f1, are received and unhandled while \f3gl_get_line()\f1 has the terminal in raw mode, the program will be terminated without the terminal having been restored to a usable state. In practice, job-control shells usually reset the terminal settings when a process relinquishes the controlling terminal, so this is only a problem with older shells. .SH CUSTOMIZED SIGNAL HANDLING The previous section listed the signals that \f3gl_get_line()\f1 traps by default, and described how it responds to them. This section describes how to both add and remove signals from the list of trapped signals, and how to specify how \f3gl_get_line()\f1 should respond to a given signal. .sp If you don't need \f3gl_get_line()\f1 to do anything in response to a signal that it normally traps, you can tell to \f3gl_get_line()\f1 to ignore that signal by calling \f3gl_ignore_signal()\f1. .sp .nf int gl_ignore_signal(GetLine *gl, int signo); .fi .sp The \f3signo\f1 argument is the number of the signal (eg. \f3SIGINT\f1) that you want to have ignored. If the specified signal isn't currently one of those being trapped, this function does nothing. .sp The \f3gl_trap_signal()\f1 function allows you to either add a new signal to the list that \f3gl_get_line()\f1 traps, or modify how it responds to a signal that it already traps. .sp .nf int gl_trap_signal(GetLine *gl, int signo, unsigned flags, GlAfterSignal after, int errno_value); .fi .sp The \f3signo\f1 argument is the number of the signal that you wish to have trapped. The \f3flags\f1 argument is a set of flags which determine the environment in which the application's signal handler is invoked, the \f3after\f1 argument tells \f3gl_get_line()\f1 what to do after the application's signal handler returns, and \f3errno_value\f1 tells \f3gl_get_line()\f1 what to set \f3errno\f1 to if told to abort. .sp The \f3flags\f1 argument is a bitwise OR of zero or more of the following enumerators: .sp .nf GLS_RESTORE_SIG - Restore the caller's signal environment while handling the signal. GLS_RESTORE_TTY - Restore the caller's terminal settings while handling the signal. GLS_RESTORE_LINE - Move the cursor to the start of the line following the input line before invoking the application's signal handler. GLS_REDRAW_LINE - Redraw the input line when the application's signal handler returns. GLS_UNBLOCK_SIG - Normally, if the calling program has a signal blocked (man sigprocmask), gl_get_line() does not trap that signal. This flag tells gl_get_line() to trap the signal and unblock it for the duration of the call to gl_get_line(). GLS_DONT_FORWARD - If this flag is included, the signal will not be forwarded to the signal handler of the calling program. .fi .sp Two commonly useful flag combinations are also enumerated as follows: .sp .nf GLS_RESTORE_ENV = GLS_RESTORE_SIG | GLS_RESTORE_TTY | GLS_REDRAW_LINE GLS_SUSPEND_INPUT = GLS_RESTORE_ENV | GLS_RESTORE_LINE .fi .sp If your signal handler, or the default system signal handler for this signal, if you haven't overridden it, never either writes to the terminal, nor suspends or terminates the calling program, then you can safely set the \f3flags\f1 argument to \f30\f1. .sp If your signal handler always writes to the terminal, reads from it, or suspends or terminates the program, you should specify the \f3flags\f1 argument as \f3GL_SUSPEND_INPUT\f1, so that: .sp .nf 1. The cursor doesn't get left in the middle of the input line. 2. So that the user can type in input and have it echoed. 3. So that you don't need to end each output line with \f3\\r\\n\f1, instead of just \f3\\n\f1. .fi .sp The \f3GL_RESTORE_ENV\f1 combination is the same as \f3GL_SUSPEND_INPUT\f1, except that it doesn't move the cursor, and if your signal handler doesn't read or write anything to the terminal, the user won't see any visible indication that a signal was caught. This can be useful if you have a signal handler that only occasionally writes to the terminal, where using \f3GL_SUSPEND_LINE\f1 would cause the input line to be unnecessarily duplicated when nothing had been written to the terminal. Such a signal handler, when it does write to the terminal, should be sure to start a new line at the start of its first write, by writing a '\\n' character, and should be sure to leave the cursor on a new line before returning. If the signal arrives while the user is entering a line that only occupies a signal terminal line, or if the cursor is on the last terminal line of a longer input line, this will have the same effect as \f3GL_SUSPEND_INPUT\f1. Otherwise it will start writing on a line that already contains part of the displayed input line. This doesn't do any harm, but it looks a bit ugly, which is why the \f3GL_SUSPEND_INPUT\f1 combination is better if you know that you are always going to be writting to the terminal. .sp The \f3after\f1 argument, which determines what \f3gl_get_line()\f1 does after the application's signal handler returns (if it returns), can take any one of the following values: .sp .nf GLS_RETURN - Return the completed input line, just as though the user had pressed the return key. GLS_ABORT - Cause \f3gl_get_line()\f1 to abort. When this happens, \f3gl_get_line()\f1 returns \f3NULL\f1, and a following call to \f3gl_return_status()\f1 will return \f3GLR_SIGNAL\f1. Note that if the application needs \f3errno\f1 always to have a meaningful value when \f3gl_get_line()\f1 returns \f3NULL\f1, the callback function should set \f3errno\f1 appropriately. GLS_CONTINUE - Resume command line editing. .fi .sp The \f3errno_value\f1 argument is intended to be combined with the \f3GLS_ABORT\f1 option, telling \f3gl_get_line()\f1 what to set the standard \f3errno\f1 variable to before returning \f3NULL\f1 to the calling program. It can also, however, be used with the \f3GL_RETURN\f1 option, in case you wish to have a way to distinguish between an input line that was entered using the return key, and one that was entered by the receipt of a signal. .SH RELIABLE SIGNAL HANDLING Signal handling is suprisingly hard to do reliably without race conditions. In \f3gl_get_line()\f1 a lot of care has been taken to allow applications to perform reliable signal handling around \f3gl_get_line()\f1. This section explains how to make use of this. As an example of the problems that can arise if the application isn't written correctly, imagine that one's application has a SIGINT signal handler that sets a global flag. Now suppose that the application tests this flag just before invoking \f3gl_get_line()\f1. If a SIGINT signal happens to be received in the small window of time between the statement that tests the value of this flag, and the statement that calls \f3gl_get_line()\f1, then \f3gl_get_line()\f1 will not see the signal, and will not be interrupted. As a result, the application won't be able to respond to the signal until the user gets around to finishing entering the input line and \f3gl_get_line()\f1 returns. Depending on the application, this might or might not be a disaster, but at the very least it would puzzle the user. The way to avoid such problems is to do the following. 1. If needed, use the \f3gl_trap_signal()\f1 function to configure \f3gl_get_line()\f1 to abort when important signals are caught. 2. Configure \f3gl_get_line()\f1 such that if any of the signals that it catches are blocked when \f3gl_get_line()\f1 is called, they will be unblocked automatically during times when \f3gl_get_line()\f1 is waiting for I/O. This can be done either on a per signal basis, by calling the \f3gl_trap_signal()\f1 function, and specifying the \f3GLS_UNBLOCK\f1 attribute of the signal, or globally by calling the \f3gl_catch_blocked()\f1 function. .sp .nf void gl_catch_blocked(GetLine *gl); .fi .sp This function simply adds the \f3GLS_UNBLOCK\f1 attribute to all of the signals that it is currently configured to trap. 3. Just before calling \f3gl_get_line()\f1, block delivery of all of the signals that \f3gl_get_line()\f1 is configured to trap. This can be done using the POSIX \f3sigprocmask()\f1 function in conjunction with the \f3gl_list_signals()\f1 function. .sp .nf int gl_list_signals(GetLine *gl, sigset_t *set); .fi .sp This function returns the set of signals that it is currently configured to catch in the \f3set\f1 argument, which is in the form required by \f3sigprocmask()\f1. 4. In the example, one would now test the global flag that the signal handler sets, knowing that there is now no danger of this flag being set again until \f3gl_get_line()\f1 unblocks its signals while performing I/O. 5. Eventually \f3gl_get_line()\f1 returns, either because a signal was caught, an error occurred, or the user finished entering their input line. 6. Now one would check the global signal flag again, and if it is set, respond to it, and zero the flag. 7. Use \f3sigprocmask()\f1 to unblock the signals that were blocked in step 3. The same technique can be used around certain POSIX signal-aware functions, such as \f3sigsetjmp()\f1 and \f3sigsuspend()\f1, and in particular, the former of these two functions can be used in conjunction with \f3siglongjmp()\f1 to implement race-condition free signal handling around other long-running system calls. The way to do this, is explained next, by showing how \f3gl_get_line()\f1 manages to reliably trap signals around calls to functions like \f3read()\f1 and \f3select()\f1 without race conditions. The first thing that \f3gl_get_line()\f1 does, whenever it is called, is to use the POSIX \f3sigprocmask()\f1 function to block the delivery of all of the signals that it is currently configured to catch. This is redundant if the application has already blocked them, but it does no harm. It undoes this step just before returning. Whenever \f3gl_get_line()\f1 needs to call \f3read()\f1 or \f3select()\f1 to wait for input from the user, it first calls the POSIX \f3sigsetjmp()\f1 function, being sure to specify a non-zero value for its \f3savesigs\f1 argument. The reason for the latter argument will become clear shortly. If \f3sigsetjmp()\f1 returns zero, \f3gl_get_line()\f1 then does the following. .sp .nf a. It uses the POSIX \f3sigaction()\f1 function to register a temporary signal handler to all of the signals that it is configured to catch. This signal handler does two things. 1. It records the number of the signal that was received in a file-scope variable. 2. It then calls the POSIX \f3siglongjmp()\f1 function using the buffer that was passed to \f3sigsetjmp()\f1 for its first argument, and a non-zero value for its second argument. When this signal handler is registered, the \f3sa_mask\f1 member of the \f3struct sigaction act\f1 argument of the call to \f3sigaction()\f1 is configured to contain all of the signals that \f3gl_get_line()\f1 is catching. This ensures that only one signal will be caught at once by our signal handler, which in turn ensures that multiple instances of our signal handler don't tread on each other's toes. b. Now that the signal handler has been set up, \f3gl_get_line()\f1 unblocks all of the signals that it is configured to catch. c. It then calls the \f3read()\f1 or \f3select()\f1 system calls to wait for keyboard input. d. If this system call returns (ie. no signal is received), \f3gl_get_line()\f1 blocks delivery of the signals of interest again. e. It then reinstates the signal handlers that were displaced by the one that was just installed. .fi .sp Alternatively, if \f3sigsetjmp()\f1 returns non-zero, this means that one of the signals being trapped was caught while the above steps were executing. When this happens, \f3gl_get_line()\f1 does the following. First, note that when a call to \f3siglongjmp()\f1 causes \f3sigsetjmp()\f1 to return, provided that the \f3savesigs\f1 argument of \f3sigsetjmp()\f1 was non-zero, as specified above, the signal process mask is restored to how it was when \f3sigsetjmp()\f1 was called. This is the important difference between \f3sigsetjmp()\f1 and the older problematic \f3setjmp()\f1, and is the essential ingredient that makes it possible to avoid signal handling race conditions. Because of this we are guaranteed that all of the signals that we blocked before calling \f3sigsetjmp()\f1 are blocked again as soon as any signal is caught. The following statements, which are then executed, are thus guaranteed to be executed without any further signals being caught. 1. If so instructed by the \f3gl_get_line()\f1 configuration attributes of the signal that was caught, \f3gl_get_line()\f1 restores the terminal attributes to the state that they had when \f3gl_get_line()\f1 was called. This is particularly important for signals that suspend or terminate the process, since otherwise the terminal would be left in an unusable state. 2. It then reinstates the application's signal handlers. 3. Then it uses the C standard-library \f3raise()\f1 function to re-send the application the signal that was caught. 3. Next it unblocks delivery of the signal that we just sent. This results in the signal that was just sent via \f3raise()\f1, being caught by the application's original signal handler, which can now handle it as it sees fit. 4. If the signal handler returns (ie. it doesn't terminate the process), \f3gl_get_line()\f1 blocks delivery of the above signal again. 5. It then undoes any actions performed in the first of the above steps, and redisplays the line, if the signal configuration calls for this. 6. \f3gl_get_line()\f1 then either resumes trying to read a character, or aborts, depending on the configuration of the signal that was caught. What the above steps do in essence is to take asynchronously delivered signals and handle them synchronously, one at a time, at a point in the code where \f3gl_get_line()\f1 has complete control over its environment. .SH THE TERMINAL SIZE On most systems the combination of the \f3TIOCGWINSZ\f1 ioctl and the \f3SIGWINCH\f1 signal is used to maintain an accurate idea of the terminal size. The terminal size is newly queried every time that \f3gl_get_line()\f1 is called and whenever a \f3SIGWINCH\f1 signal is received. .sp On the few systems where this mechanism isn't available, at startup \f3new_GetLine()\f1 first looks for the \f3LINES\f1 and \f3COLUMNS\f1 environment variables. If these aren't found, or they contain unusable values, then if a terminal information database like terminfo or termcap is available, the default size of the terminal is looked up in this database. If this too fails to provide the terminal size, a default size of 80 columns by 24 lines is used. .sp Even on systems that do support \f3ioctl(TIOCGWINSZ)\f1, if the terminal is on the other end of a serial line, the terminal driver generally has no way of detecting when a resize occurs or of querying what the current size is. In such cases no \f3SIGWINCH\f1 is sent to the process, and the dimensions returned by \f3ioctl(TIOCGWINSZ)\f1 aren't correct. The only way to handle such instances is to provide a way for the user to enter a command that tells the remote system what the new size is. This command would then call the \f3gl_set_term_size()\f1 function to tell \f3gl_get_line()\f1 about the change in size. .sp .nf int gl_set_term_size(GetLine *gl, int ncolumn, int nline); .fi .sp The \f3ncolumn\f1 and \f3nline\f1 arguments are used to specify the new dimensions of the terminal, and must not be less than 1. On systems that do support \f3ioctl(TIOCGWINSZ)\f1, this function first calls \f3ioctl(TIOCSWINSZ)\f1 to tell the terminal driver about the change in size. In non-blocking server-I/O mode, if a line is currently being input, the input line is then redrawn to accomodate the changed size. Finally the new values are recorded in \f3gl\f1 for future use by \f3gl_get_line()\f1. .sp The \f3gl_terminal_size()\f1 function allows you to query the current size of the terminal, and install an alternate fallback size for cases where the size isn't available. Beware that the terminal size won't be available if reading from a pipe or a file, so the default values can be important even on systems that do support ways of finding out the terminal size. .sp .nf typedef struct { int nline; /* The terminal has nline lines */ int ncolumn; /* The terminal has ncolumn columns */ } GlTerminalSize; GlTerminalSize gl_terminal_size(GetLine *gl, int def_ncolumn, int def_nline); .fi .sp This function first updates \f3gl_get_line()\f1's fallback terminal dimensions, then records its findings in the return value. .sp The \f3def_ncolumn\f1 and \f3def_nline\f1 specify the default number of terminal columns and lines to use if the terminal size can't be determined via \f3ioctl(TIOCGWINSZ)\f1 or environment variables. .SH HIDING WHAT YOU TYPE When entering sensitive information, such as passwords, it is best not to have the text that you are entering echoed on the terminal. Furthermore, such text should not be recorded in the history list, since somebody finding your terminal unattended could then recall it, or somebody snooping through your directories could see it in your history file. With this in mind, the \f3gl_echo_mode()\f1 function allows you to toggle on and off the display and archival of any text that is subsequently entered in calls to \f3gl_get_line()\f1. .sp .nf int gl_echo_mode(GetLine *gl, int enable); .fi .sp The \f3enable\f1 argument specifies whether entered text should be visible or not. If it is \f30\f1, then subsequently entered lines will not be visible on the terminal, and will not be recorded in the history list. If it is \f31\f1, then subsequent input lines will be displayed as they are entered, and provided that history hasn't been turned off via a call to \f3gl_toggle_history()\f1, then they will also be archived in the history list. Finally, if the \f3enable\f1 argument is \f3-1\f1, then the echoing mode is left unchanged, which allows you to non-destructively query the current setting via the return value. In all cases, the return value of the function is \f30\f1 if echoing was disabled before the function was called, and \f31\f1 if it was enabled. .sp When echoing is turned off, note that although tab completion will invisibly complete your prefix as far as possible, ambiguous completions will not be displayed. .SH SINGLE CHARACTER QUERIES Using \f3gl_get_line()\f1 to query the user for a single character reply, is inconvenient for the user, since they must hit the enter or return key before the character that they typed is returned to the program. Thus the \f3gl_query_char()\f1 function has been provided for single character queries like this. .sp .nf int gl_query_char(GetLine *gl, const char *prompt, char defchar); .fi .sp This function displays the specified prompt at the start of a new line, and waits for the user to type a character. When the user types a character, \f3gl_query_char()\f1 displays it to the right of the prompt, starts a newline, then returns the character to the calling program. The return value of the function is the character that was typed. If the read had to be aborted for some reason, \f3EOF\f1 is returned instead. In the latter case, the application can call the previously documented \f3gl_return_status()\f1, to find out what went wrong. This could, for example, have been the reception of a signal, or the optional inactivity timer going off. If the user simply hits enter, the value of the \f3defchar\f1 argument is substituted. This means that when the user hits either newline or return, the character specified in \f3defchar\f1, is displayed after the prompt, as though the user had typed it, as well as being returned to the calling application. If such a replacement is not important, simply pass \f3'\n'\f1 as the value of \f3defchar\f1. If the entered character is an unprintable character, it is displayed symbolically. For example, control-A is displayed as ^A, and characters beyond 127 are displayed in octal, preceded by a backslash. As with \f3gl_get_line()\f1, echoing of the entered character can be disabled using the \f3gl_echo_mode()\f1 function. If the calling process is suspended while waiting for the user to type their response, the cursor is moved to the line following the prompt line, then when the process resumes, the prompt is redisplayed, and \f3gl_query_char()\f1 resumes waiting for the user to type a character. Note that in non-blocking server mode, (see gl_io_mode(@FUNC_MANEXT@)), if an incomplete input line is in the process of being read when \f3gl_query_char()\f1 is called, the partial input line is discarded, and erased from the terminal, before the new prompt is displayed. The next call to \f3gl_get_line()\f1 will thus start editing a new line. .SH READING RAW CHARACTERS Whereas the \f3gl_query_char()\f1 function visibly prompts the user for a character, and displays what they typed, the \f3gl_read_char()\f1 function reads a signal character from the user, without writing anything to the terminal, or perturbing any incompletely entered input line. This means that it can be called not only from between calls to \f3gl_get_line()\f1, but also from callback functions that the application has registered to be called by \f3gl_get_line()\f1. .sp .nf int gl_read_char(GetLine *gl); .fi .sp On success, the return value of \f3gl_read_char()\f1 is the character that was read. On failure, \f3EOF\f1 is returned, and the \f3gl_return_status()\f1 function can be called to find out what went wrong. Possibilities include the optional inactivity timer going off, the receipt of a signal that is configured to abort gl_get_line(), or terminal I/O blocking, when in non-blocking server-I/O mode. Beware that certain keyboard keys, such as function keys, and cursor keys, usually generate at least 3 characters each, so a single call to \f3gl_read_char()\f1 won't be enough to identify such keystrokes. .SH CLEARING THE TERMINAL The calling program can clear the terminal by calling \f3gl_erase_terminal()\f1. In non-blocking server-I/O mode, this function also arranges for the current input line to be redrawn from scratch when \f3gl_get_line()\f1 is next called. .sp .nf int gl_erase_terminal(GetLine *gl); .fi .sp .SH DISPLAYING TEXT DYNAMICALLY Between calls to \f3gl_get_line()\f1, the \f3gl_display_text()\f1 function provides a convenient way to display paragraphs of text, left-justified and split over one or more terminal lines according to the constraints of the current width of the terminal. Examples of the use of this function may be found in the demo programs, where it is used to display introductions. In those examples the advanced use of optional prefixes, suffixes and filled lines to draw a box around the text is also illustrated. .sp .nf int gl_display_text(GetLine *gl, int indentation, const char *prefix, const char *suffix, int fill_char, int def_width, int start, const char *string); .fi .sp If \f3gl\f1 isn't currently connected to a terminal, for example if the output of a program that uses \f3gl_get_line()\f1 is being piped to another program or redirected to a file, then the value of the \f3def_width\f1 parameter is used as the terminal width. The \f3indentation\f1 argument specifies the number of characters to use to indent each line of ouput. The \f3fill_char\f1 argument specifies the character that will be used to perform this indentation. The \f3prefix\f1 argument can either be \f3NULL\f1, or be a string to place at the beginning of each new line (after any indentation). Similarly, the \f3suffix\f1 argument can either be \f3NULL\f1, or be a string to place at the end of each line. The suffix is placed flush against the right edge of the terminal, and any space between its first character and the last word on that line is filled with the character specified via the \f3fill_char\f1 argument. Normally the fill-character is a space. The \f3start\f1 argument tells \f3gl_display_text()\f1 how many characters have already been written to the current terminal line, and thus tells it the starting column index of the cursor. Since the return value of \f3gl_display_text()\f1 is the ending column index of the cursor, by passing the return value of one call to the \f3start\f1 argument of the next call, a paragraph that is broken between more than one string can be composed by calling \f3gl_display_text()\f1 for each successive portion of the paragraph. Note that literal newline characters are necessary at the end of each paragraph to force a new line to be started. On error, \f3gl_display_text()\f1 returns -1. .SH CALLBACK FUNCTION FACILITIES Unless otherwise stated, callback functions, such as tab completion callbacks and event callbacks should not call any functions in this module. The following functions, however, are designed specifically to be used by callback functions. .sp Calling the \f3gl_replace_prompt()\f1 function from a callback tells \f3gl_get_line()\f1 to display a different prompt when the callback returns. Except in non-blocking server mode, it has no effect if used between calls to \f3gl_get_line()\f1. In non-blocking server mode (see the \f3gl_io_mode(@FUNC_MANEXT@)\f1 man page, when used between two calls to \f3gl_get_line()\f1 that are operating on the same input line, the current input line will be re-drawn with the new prompt on the following call to \f3gl_get_line()\f1. .sp .nf void gl_replace_prompt(GetLine *gl, const char *prompt); .fi .sp .SH INTERNATIONAL CHARACTER SETS Since libtecla version 1.4.0, \f3gl_get_line()\f1 has been 8-bit clean. This means that all 8-bit characters that are printable in the user's current locale are now displayed verbatim and included in the returned input line. Assuming that the calling program correctly contains a call like the following, .sp .nf setlocale(LC_CTYPE, ""); .fi .sp then the current locale is determined by the first of the environment variables \f3LC_CTYPE\f1, \f3LC_ALL\f1, and \f3LANG\f1, that is found to contain a valid locale name. If none of these variables are defined, or the program neglects to call setlocale, then the default \f3C\f1 locale is used, which is US 7-bit ASCII. On most unix-like platforms, you can get a list of valid locales by typing the command: .sp .nf locale -a .fi .sp at the shell prompt. Further documentation on how the user can make use of this to enter international characters can be found in the \f3tecla(@MISC_MANEXT@)\f1 man page. .SH THREAD SAFETY In a multi-threaded program, you should use the libtecla_r.a version of the library. This uses reentrant versions of system functions, where available. Unfortunately neither terminfo nor termcap were designed to be reentrant, so you can't safely use the functions of the getline module in multiple threads (you can use the separate file-expansion and word-completion modules in multiple threads, see the corresponding man pages for details). However due to the use of POSIX reentrant functions for looking up home directories etc, it is safe to use this module from a single thread of a multi-threaded program, provided that your other threads don't use any termcap or terminfo functions. .SH FILES .nf libtecla.a - The tecla library libtecla.h - The tecla header file. ~/.teclarc - The personal tecla customization file. .fi .SH SEE ALSO .nf libtecla(@LIBR_MANEXT@), gl_io_mode(@FUNC_MANEXT@), tecla(@MISC_MANEXT@), ef_expand_file(@FUNC_MANEXT@), cpl_complete_word(@FUNC_MANEXT@), pca_lookup_file(@FUNC_MANEXT@) .fi .SH AUTHOR Martin Shepherd (mcs@astro.caltech.edu) ./libtecla/man/func/gl_echo_mode.in0100644000076400007640000000005407600313615015573 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_lookup_history.in0100644000076400007640000000005407600313615016743 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_set_term_size.in0100644000076400007640000000005407600313616016526 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/new_CplFileConf.in0100644000076400007640000000006207600313616016164 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/gl_terminal_size.in0100644000076400007640000000005407600313616016517 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_handle_signal.in0100644000076400007640000000005307600313615016440 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/gl_toggle_history.in0100644000076400007640000000005407600313616016714 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_trap_signal.in0100644000076400007640000000005407600313616016155 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_return_status.in0100644000076400007640000000005407600313616016574 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_list_signals.in0100644000076400007640000000005407600313615016344 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_error_message.in0100644000076400007640000000005407600313615016506 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_tty_signals.in0100644000076400007640000000005307600313616016211 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/gl_normal_io.in0100644000076400007640000000005307600313615015627 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/gl_watch_fd.in0100644000076400007640000000005407600313616015431 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_resize_history.in0100644000076400007640000000005407600313616016734 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/libtecla_version.in0100644000076400007640000000005107601225075016513 0ustar mcsmcs.so @LIBR_MANDIR@/libtecla.@LIBR_MANEXT@ ./libtecla/man/func/gl_save_history.in0100644000076400007640000000005407600313616016371 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_last_signal.in0100644000076400007640000000005407600313615016151 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_pending_io.in0100644000076400007640000000005307600313615015763 0ustar mcsmcs.so @FUNC_MANDIR@/gl_io_mode.@FUNC_MANEXT@ ./libtecla/man/func/gl_configure_getline.in0100644000076400007640000000005407600313615017341 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_group_history.in0100644000076400007640000000005407600313615016566 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_ignore_signal.in0100644000076400007640000000005407600313615016471 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/cfc_file_start.in0100644000076400007640000000006207600313615016135 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/gl_limit_history.in0100644000076400007640000000005407600313615016550 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_show_history.in0100644000076400007640000000005407600313616016413 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_size_of_history.in0100644000076400007640000000005407600313616017071 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_load_history.in0100644000076400007640000000005407600313615016351 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_prompt_style.in0100644000076400007640000000005407600313615016412 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_range_of_history.in0100644000076400007640000000005407600313615017212 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/new_PcaPathConf.in0100644000076400007640000000006007600313616016164 0ustar mcsmcs.so @FUNC_MANDIR@/pca_lookup_file.@FUNC_MANEXT@ ./libtecla/man/func/gl_catch_blocked.in0100644000076400007640000000005407600313615016416 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_state_of_history.in0100644000076400007640000000005407600313616017237 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/cfc_literal_escapes.in0100644000076400007640000000006207600313615017140 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/cpl_add_completion.in0100644000076400007640000000006207600313615017005 0ustar mcsmcs.so @FUNC_MANDIR@/cpl_complete_word.@FUNC_MANEXT@ ./libtecla/man/func/gl_change_terminal.in0100644000076400007640000000005407600313615016771 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_completion_action.in0100644000076400007640000000005407600313615017357 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_customize_completion.in0100644000076400007640000000005407600313615020124 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/gl_inactivity_timeout.in0100644000076400007640000000005407600313615017602 0ustar mcsmcs.so @FUNC_MANDIR@/gl_get_line.@FUNC_MANEXT@ ./libtecla/man/func/pca_lookup_file.in0100644000076400007640000003356410027466676016353 0ustar mcsmcs.\" Copyright (c) 2001, 2002, 2003, 2004 by Martin C. Shepherd .\" .\" All rights reserved. .\" .\" Permission is hereby granted, free of charge, to any person obtaining a .\" copy of this software and associated documentation files (the .\" "Software"), to deal in the Software without restriction, including .\" without limitation the rights to use, copy, modify, merge, publish, .\" distribute, and/or sell copies of the Software, and to permit persons .\" to whom the Software is furnished to do so, provided that the above .\" copyright notice(s) and this permission notice appear in all copies of .\" the Software and that both the above copyright notice(s) and this .\" permission notice appear in supporting documentation. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS .\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .\" OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR .\" HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL .\" INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING .\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, .\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION .\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" Except as contained in this notice, the name of a copyright holder .\" shall not be used in advertising or otherwise to promote the sale, use .\" or other dealings in this Software without prior written authorization .\" of the copyright holder. .TH pca_lookup_file @FUNC_MANEXT@ .SH NAME pca_lookup_file, del_PathCache, del_PcaPathConf, new_PathCache, new_PcaPathConf, pca_last_error, pca_path_completions, pca_scan_path, pca_set_check_fn, ppc_file_start, ppc_literal_escapes \- lookup a file in a list of directories .SH SYNOPSIS .nf #include PathCache *new_PathCache(void); PathCache *del_PathCache(PathCache *pc); int pca_scan_path(PathCache *pc, const char *path); void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn, void *data); char *pca_lookup_file(PathCache *pc, const char *name, int name_len, int literal); const char *pca_last_error(PathCache *pc); CPL_MATCH_FN(pca_path_completions); .fi .SH DESCRIPTION The \f3PathCache\f1 object is part of the tecla library (see the libtecla(@LIBR_MANEXT@) man page). .sp \f3PathCache\f1 objects allow an application to search for files in any colon separated list of directories, such as the unix execution PATH environment variable. Files in absolute directories are cached in a \f3PathCache\f1 object, whereas relative directories are scanned as needed. Using a \f3PathCache\f1 object, you can look up the full pathname of a simple filename, or you can obtain a list of the possible completions of a given filename prefix. By default all files in the list of directories are targets for lookup and completion, but a versatile mechanism is provided for only selecting specific types of files. The obvious application of this facility is to provide Tab-completion and lookup of executable commands in the unix PATH, so an optional callback which rejects all but executable files, is provided. .sp .SH AN EXAMPLE Under UNIX, the following example program looks up and displays the full pathnames of each of the command names on the command line. .sp .nf #include #include #include int main(int argc, char *argv[]) { int i; /* * Create a cache for executable files. */ PathCache *pc = new_PathCache(); if(!pc) exit(1); /* * Scan the user's PATH for executables. */ if(pca_scan_path(pc, getenv("PATH"))) { fprintf(stderr, "%s\\n", pca_last_error(pc)); exit(1); } /* * Arrange to only report executable files. */ pca_set_check_fn(pc, cpl_check_exe, NULL); /* * Lookup and display the full pathname of each of the * commands listed on the command line. */ for(i=1; i #include #include #include #include "ioutil.h" #include "chrqueue.h" #include "freelist.h" #include "errmsg.h" /* * Set the number of bytes allocated to each node of the list of * character buffers. This facility is designed principally as * an expandible I/O output buffer, so use the stdio buffer size * where available. */ #ifdef BUFSIZ #define GL_CQ_SIZE BUFSIZ #else #define GL_CQ_SIZE 512 #endif /* * The queue is contained in a list of fixed sized buffers. New nodes * are appended to this list as needed to accomodate newly added bytes. * Old nodes at the head of the list are removed as they are emptied. */ typedef struct CqCharBuff CqCharBuff; struct CqCharBuff { CqCharBuff *next; /* The next node in the list of buffers */ char bytes[GL_CQ_SIZE]; /* The fixed size buffer of this node */ }; /* * Define the structure that is used to contain a list of character * buffers. */ struct GlCharQueue { ErrMsg *err; /* A buffer in which to record error messages */ FreeList *bufmem; /* A free-list of CqCharBuff structures */ struct { CqCharBuff *head; /* The head of the list of output buffers */ CqCharBuff *tail; /* The tail of the list of output buffers */ } buffers; int nflush; /* The total number of characters that have been */ /* flushed from the start of the queue since */ /* _glq_empty_queue() was last called. */ int ntotal; /* The total number of characters that have been */ /* appended to the queue since _glq_empty_queue() */ /* was last called. */ }; /*....................................................................... * Create a new GlCharQueue object. * * Output: * return GlCharQueue * The new object, or NULL on error. */ GlCharQueue *_new_GlCharQueue(void) { GlCharQueue *cq; /* The object to be returned */ /* * Allocate the container. */ cq = malloc(sizeof(GlCharQueue)); if(!cq) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_GlCharQueue(). */ cq->err = NULL; cq->bufmem = NULL; cq->buffers.head = NULL; cq->buffers.tail = NULL; cq->nflush = cq->ntotal = 0; /* * Allocate a place to record error messages. */ cq->err = _new_ErrMsg(); if(!cq->err) return _del_GlCharQueue(cq); /* * Allocate the freelist of CqCharBuff structures. */ cq->bufmem = _new_FreeList(sizeof(CqCharBuff), 1); if(!cq->bufmem) return _del_GlCharQueue(cq); return cq; } /*....................................................................... * Delete a GlCharQueue object. * * Input: * cq GlCharQueue * The object to be deleted. * Output: * return GlCharQueue * The deleted object (always NULL). */ GlCharQueue *_del_GlCharQueue(GlCharQueue *cq) { if(cq) { cq->err = _del_ErrMsg(cq->err); cq->bufmem = _del_FreeList(cq->bufmem, 1); free(cq); }; return NULL; } /*....................................................................... * Append an array of n characters to a character queue. * * Input: * cq GlCharQueue * The queue to append to. * chars const char * The array of n characters to be appended. * n int The number of characters in chars[]. * write_fn GL_WRITE_FN * The function to call to output characters, * or 0 to simply discard the contents of the * queue. This will be called whenever the * buffer becomes full. If it fails to release * any space, the buffer will be extended. * data void * Anonymous data to pass to write_fn(). * Output: * return int The number of characters successfully * appended. This will only be < n on error. */ int _glq_append_chars(GlCharQueue *cq, const char *chars, int n, GlWriteFn *write_fn, void *data) { int ndone = 0; /* The number of characters appended so far */ /* * Check the arguments. */ if(!cq || !chars) { errno = EINVAL; return 0; }; /* * The appended characters may have to be split between multiple * buffers, so loop for each buffer. */ while(ndone < n) { int ntodo; /* The number of characters remaining to be appended */ int nleft; /* The amount of space remaining in cq->buffers.tail */ int nnew; /* The number of characters to append to cq->buffers.tail */ /* * Compute the offset at which the next character should be written * into the tail buffer segment. */ int boff = cq->ntotal % GL_CQ_SIZE; /* * Since we don't allocate a new buffer until we have at least one * character to write into it, if boff is 0 at this point, it means * that we hit the end of the tail buffer segment on the last append, * so we need to allocate a new one. * * If allocating this new node will require a call to malloc(), as * opposed to using a currently unused node in the freelist, first try * flushing the current contents of the buffer to the terminal. When * write_fn() uses blocking I/O, this stops the buffer size ever getting * bigger than a single buffer node. When it is non-blocking, it helps * to keep the amount of memory, but it isn't gauranteed to do so. */ if(boff == 0 && _idle_FreeListNodes(cq->bufmem) == 0) { switch(_glq_flush_queue(cq, write_fn, data)) { case GLQ_FLUSH_DONE: break; case GLQ_FLUSH_AGAIN: errno = 0; /* Don't confuse the caller */ break; default: return ndone; /* Error */ }; boff = cq->ntotal % GL_CQ_SIZE; }; /* * Since we don't allocate a new buffer until we have at least one * character to write into it, if boff is 0 at this point, it means * that we hit the end of the tail buffer segment on the last append, * so we need to allocate a new one. */ if(boff == 0) { /* * Allocate the new node. */ CqCharBuff *node = (CqCharBuff *) _new_FreeListNode(cq->bufmem); if(!node) { _err_record_msg(cq->err, "Insufficient memory to buffer output.", END_ERR_MSG); return ndone; }; /* * Initialize the node. */ node->next = NULL; /* * Append the new node to the tail of the list. */ if(cq->buffers.tail) cq->buffers.tail->next = node; else cq->buffers.head = node; cq->buffers.tail = node; }; /* * How much room is there for new characters in the current tail node? */ nleft = GL_CQ_SIZE - boff; /* * How many characters remain to be appended? */ ntodo = n - ndone; /* * How many characters should we append to the current tail node? */ nnew = nleft < ntodo ? nleft : ntodo; /* * Append the latest prefix of nnew characters. */ memcpy(cq->buffers.tail->bytes + boff, chars + ndone, nnew); cq->ntotal += nnew; ndone += nnew; }; /* * Return the count of the number of characters successfully appended. */ return ndone; } /*....................................................................... * Discard the contents of a queue of characters. * * Input: * cq GlCharQueue * The queue to clear. */ void _glq_empty_queue(GlCharQueue *cq) { if(cq) { /* * Return all list nodes to their respective free-lists. */ _rst_FreeList(cq->bufmem); /* * Mark the lists as empty. */ cq->buffers.head = cq->buffers.tail = NULL; cq->nflush = cq->ntotal = 0; }; } /*....................................................................... * Return a count of the number of characters currently in the queue. * * Input: * cq GlCharQueue * The queue of interest. * Output: * return int The number of characters in the queue. */ int _glq_char_count(GlCharQueue *cq) { return (cq && cq->buffers.head) ? (cq->ntotal - cq->nflush) : 0; } /*....................................................................... * Write as many characters as possible from the start of a character * queue via a given output callback function, removing those written * from the queue. * * Input: * cq GlCharQueue * The queue to write characters from. * write_fn GL_WRITE_FN * The function to call to output characters, * or 0 to simply discard the contents of the * queue. * data void * Anonymous data to pass to write_fn(). * Output: * return GlFlushState The status of the flush operation: * GLQ_FLUSH_DONE - The flush operation * completed successfully. * GLQ_FLUSH_AGAIN - The flush operation * couldn't be completed * on this call. Call this * function again when the * output channel can accept * further output. * GLQ_FLUSH_ERROR Unrecoverable error. */ GlqFlushState _glq_flush_queue(GlCharQueue *cq, GlWriteFn *write_fn, void *data) { /* * Check the arguments. */ if(!cq) { errno = EINVAL; return GLQ_FLUSH_ERROR; }; /* * If possible keep writing until all of the chained buffers have been * emptied and removed from the list. */ while(cq->buffers.head) { /* * Are we looking at the only node in the list? */ int is_tail = cq->buffers.head == cq->buffers.tail; /* * How many characters more than an exact multiple of the buffer-segment * size have been added to the buffer so far? */ int nmodulo = cq->ntotal % GL_CQ_SIZE; /* * How many characters of the buffer segment at the head of the list * have been used? Note that this includes any characters that have * already been flushed. Also note that if nmodulo==0, this means that * the tail buffer segment is full. The reason for this is that we * don't allocate new tail buffer segments until there is at least one * character to be added to them. */ int nhead = (!is_tail || nmodulo == 0) ? GL_CQ_SIZE : nmodulo; /* * How many characters remain to be flushed from the buffer * at the head of the list? */ int nbuff = nhead - (cq->nflush % GL_CQ_SIZE); /* * Attempt to write this number. */ int nnew = write_fn(data, cq->buffers.head->bytes + cq->nflush % GL_CQ_SIZE, nbuff); /* * Was anything written? */ if(nnew > 0) { /* * Increment the count of the number of characters that have * been flushed from the head of the queue. */ cq->nflush += nnew; /* * If we succeded in writing all of the contents of the current * buffer segment, remove it from the queue. */ if(nnew == nbuff) { /* * If we just emptied the last node left in the list, then the queue is * now empty and should be reset. */ if(is_tail) { _glq_empty_queue(cq); } else { /* * Get the node to be removed from the head of the list. */ CqCharBuff *node = cq->buffers.head; /* * Make the node that follows it the new head of the queue. */ cq->buffers.head = node->next; /* * Return it to the freelist. */ node = (CqCharBuff *) _del_FreeListNode(cq->bufmem, node); }; }; /* * If the write blocked, request that this function be called again * when space to write next becomes available. */ } else if(nnew==0) { return GLQ_FLUSH_AGAIN; /* * I/O error. */ } else { _err_record_msg(cq->err, "Error writing to terminal", END_ERR_MSG); return GLQ_FLUSH_ERROR; }; }; /* * To get here the queue must now be empty. */ return GLQ_FLUSH_DONE; } /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in any of the public functions of this * module. * * Input: * cq GlCharQueue * The container of the history list. * Output: * return const char * A pointer to the internal buffer in which * the error message is temporarily stored. */ const char *_glq_last_error(GlCharQueue *cq) { return cq ? _err_get_msg(cq->err) : "NULL GlCharQueue argument"; } ./libtecla/errmsg.c0100644000076400007640000001144710027466660012612 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include "errmsg.h" /* * Encapsulate the error reporting buffer in an opaque object. */ struct ErrMsg { char msg[ERR_MSG_LEN+1]; /* An error message */ }; /*....................................................................... * Create a new error-message object. * * Output: * return ErrMsg * The new object, or NULL on error. */ ErrMsg *_new_ErrMsg(void) { ErrMsg *err; /* The object to be returned */ /* * Allocate the container. */ err = malloc(sizeof(ErrMsg)); if(!err) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_ErrMsg(). */ err->msg[0] = '\0'; return err; } /*....................................................................... * Delete an error-message object. * * Input: * err ErrMsg * The object to be deleted. * Output: * return ErrMsg * The deleted object (always NULL). */ ErrMsg *_del_ErrMsg(ErrMsg *err) { if(err) { free(err); }; return NULL; } /*....................................................................... * Record the concatenation of a list of string arguments in an error * message object. The last argument must be END_ERR_MSG to terminate * the argument list. * * Input: * err ErrMsg * The error-message container. * ... const char * Zero or more strings to be concatenated in buff[]. * ... const char * The last argument must always be END_ERR_MSG to * terminate the argument list. */ void _err_record_msg(ErrMsg *err, ...) { va_list ap; /* The variable argument list */ const char *s; /* The string being printed */ size_t msglen = 0; /* The total length of the message */ /* * Nowhere to record the result? */ if(!err) { errno = EINVAL; return; }; /* * Concatenate the list of argument strings in err->msg[]. */ va_start(ap, err); while((s = va_arg(ap, const char *)) != END_ERR_MSG) { /* * How much room is left in the output buffer (note that the output * buffer has ERR_MSG_LEN+1 elements). */ int nleft = ERR_MSG_LEN - msglen; /* * How long is the next string to be appended? */ size_t slen = strlen(s); /* * If there is any room left, append as much of the string * as will fit. */ if(nleft > 0) { int nnew = slen < nleft ? slen : nleft; strncpy(err->msg + msglen, s, nnew); msglen += nnew; }; }; va_end(ap); /* * Terminate the message. */ err->msg[msglen] = '\0'; return; } /*....................................................................... * Return a pointer to the error message buffer. * * Input: * err ErrMsg * The container of the error message buffer. * Output: * return char * The current error message, or NULL if err==NULL. */ char *_err_get_msg(ErrMsg *err) { return err ? err->msg : NULL; } /*....................................................................... * Replace the current error message with an empty string. * * Input: * err ErrMsg * The container of the error message buffer. */ void _err_clear_msg(ErrMsg *err) { if(err) err->msg[0] = '\0'; } ./libtecla/errmsg.h0100644000076400007640000000562410027466660012617 0ustar mcsmcs#ifndef errmsg_h #define errmsg_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Set the longest expected length of an error message (excluding its * '\0' terminator. Since any message over a nominal terminal width of * 80 characters is going to look a mess, it makes no sense to support * huge lengths. Note that many uses of strings declared with this * macro assume that it will be at least 81, so don't reduce it below * this limit. */ #define ERR_MSG_LEN 128 /* * Provide an opaque typedef to the error-message object. */ typedef struct ErrMsg ErrMsg; /* * The following token is used to terminate the argument lists of calls * to _err_record_msg(). */ #define END_ERR_MSG ((const char *)0) /* * Allocate a new error-message buffer. */ ErrMsg *_new_ErrMsg(void); /* * Delete an error message buffer. */ ErrMsg *_del_ErrMsg(ErrMsg *err); /* * Concatenate a list of string arguments into the specified buffer, buff[], * which has an allocated size of buffdim characters. * The last argument must be END_ERR_MSG to terminate the argument list. */ void _err_record_msg(ErrMsg *err, ...); /* * Replace the current error message with an empty string. */ void _err_clear_msg(ErrMsg *err); /* * Return a pointer to the error message buffer. This is * a '\0' terminated character array containing ERR_MSG_LEN+1 * elements. */ char *_err_get_msg(ErrMsg *err); #endif ./libtecla/expand.h0100644000076400007640000000372710027466660012601 0ustar mcsmcs#ifndef expand_h #define expand_h /* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * This header is not for use by external applicatons. It contains * internal immplementation features of the libtecla library, which * may change incompatibly between releases. */ /* * Print a list of expansions via a callback function. */ int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn, void *data, int term_width); #endif ./libtecla/ioutil.c0100644000076400007640000003026010027466660012612 0ustar mcsmcs/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include "ioutil.h" static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n); /*....................................................................... * Display a left-justified string over multiple terminal lines, * taking account of the specified width of the terminal. Optional * indentation and an option prefix string can be specified to be * displayed at the start of each new terminal line used, and if * needed, a single paragraph can be broken across multiple calls. * Note that literal newlines in the input string can be used to force * a newline at any point, and that in order to allow individual * paragraphs to be written using multiple calls to this function, * unless an explicit newline character is specified at the end of the * string, a newline will not be started at the end of the last word * in the string. Note that when a new line is started between two * words that are separated by spaces, those spaces are not output, * whereas when a new line is started because a newline character was * found in the string, only the spaces before the newline character * are discarded. * * Input: * write_fn GlWriteFn * The callback function to use to write the * output. * data void * A pointer to arbitrary data to be passed to * write_fn() whenever it is called. * fp FILE * The stdio stream to write to. * indentation int The number of fill characters to use to * indent the start of each new terminal line. * prefix const char * An optional prefix string to write after the * indentation margin at the start of each new * terminal line. You can specify NULL if no * prefix is required. * suffix const char * An optional suffix string to draw at the end * of the terminal line. The line will be padded * where necessary to ensure that the suffix ends * in the last column of the terminal line. If * no suffix is desired, specify NULL. * fill_char int The padding character to use when indenting * and filling up to the suffix. * term_width int The width of the terminal being written to. * start int The number of characters already written to * the start of the current terminal line. This * is primarily used to allow individual * paragraphs to be written over multiple calls * to this function, but can also be used to * allow you to start the first line of a * paragraph with a different prefix or * indentation than those specified above. * string const char * The string to be written. * Output: * return int On error -1 is returned. Otherwise the * return value is the terminal column index at * which the cursor was left after writing the * final word in the string. Successful return * values can thus be passed verbatim to the * 'start' arguments of subsequent calls to * _io_display_text() to allow the printing of a * paragraph to be broken across multiple calls * to _io_display_text(). */ int _io_display_text(GlWriteFn *write_fn, void *data, int indentation, const char *prefix, const char *suffix, int fill_char, int term_width, int start, const char *string) { int ndone; /* The number of characters written from string[] */ int nnew; /* The number of characters to be displayed next */ int was_space; /* True if the previous character was a space or tab */ int last = start; /* The column number of the last character written */ int prefix_len; /* The length of the optional line prefix string */ int suffix_len; /* The length of the optional line prefix string */ int margin_width; /* The total number of columns used by the indentation */ /* margin and the prefix string. */ int i; /* * Check the arguments? */ if(!string || !write_fn) { errno = EINVAL; return -1; }; /* * Enforce sensible values on the arguments. */ if(term_width < 0) term_width = 0; if(indentation > term_width) indentation = term_width; else if(indentation < 0) indentation = 0; if(start > term_width) start = term_width; else if(start < 0) start = 0; /* * Get the length of the prefix string. */ prefix_len = prefix ? strlen(prefix) : 0; /* * Get the length of the suffix string. */ suffix_len = suffix ? strlen(suffix) : 0; /* * How many characters are devoted to indenting and prefixing each line? */ margin_width = indentation + prefix_len; /* * Write as many terminal lines as are needed to display the whole string. */ for(ndone=0; string[ndone]; start=0) { last = start; /* * Write spaces from the current position in the terminal line to the * width of the requested indentation margin. */ if(indentation > 0 && last < indentation) { if(_io_pad_line(write_fn, data, fill_char, indentation - last)) return -1; last = indentation; }; /* * If a prefix string has been specified, display it unless we have * passed where it should end in the terminal output line. */ if(prefix_len > 0 && last < margin_width) { int pstart = last - indentation; int plen = prefix_len - pstart; if(write_fn(data, prefix+pstart, plen) != plen) return -1; last = margin_width; }; /* * Locate the end of the last complete word in the string before * (term_width - start) characters have been seen. To handle the case * where a single word is wider than the available space after the * indentation and prefix margins, always make sure that at least one * word is printed after the margin, regardless of whether it won't * fit on the line. The two exceptions to this rule are if an embedded * newline is found in the string or the end of the string is reached * before any word has been seen. */ nnew = 0; was_space = 0; for(i=ndone; string[i] && (last+i-ndone < term_width - suffix_len || (nnew==0 && last==margin_width)); i++) { if(string[i] == '\n') { if(!was_space) nnew = i-ndone; break; } else if(isspace((int) string[i])) { if(!was_space) { nnew = i-ndone+1; was_space = 1; }; } else { was_space = 0; }; }; /* * Does the end of the string delimit the last word that will fit on the * output line? */ if(nnew==0 && string[i] == '\0') nnew = i-ndone; /* * Write the new line. */ if(write_fn(data, string+ndone, nnew) != nnew) return -1; ndone += nnew; last += nnew; /* * Start a newline unless we have reached the end of the input string. * In the latter case, in order to give the caller the chance to * concatenate multiple calls to _io_display_text(), omit the newline, * leaving it up to the caller to write this. */ if(string[ndone] != '\0') { /* * If a suffix has been provided, pad out the end of the line with spaces * such that the suffix will end in the right-most terminal column. */ if(suffix_len > 0) { int npad = term_width - suffix_len - last; if(npad > 0 && _io_pad_line(write_fn, data, fill_char, npad)) return -1; last += npad; if(write_fn(data, suffix, suffix_len) != suffix_len) return -1; last += suffix_len; }; /* * Start a new line. */ if(write_fn(data, "\n", 1) != 1) return -1; /* * Skip any spaces and tabs that follow the last word that was written. */ while(string[ndone] && isspace((int)string[ndone]) && string[ndone] != '\n') ndone++; /* * If the terminating character was a literal newline character, * skip it in the input string, since we just wrote it. */ if(string[ndone] == '\n') ndone++; last = 0; }; }; /* * Return the column number of the last character printed. */ return last; } /*....................................................................... * Write a given number of spaces to the specified stdio output string. * * Input: * write_fn GlWriteFn * The callback function to use to write the * output. * data void * A pointer to arbitrary data to be passed to * write_fn() whenever it is called. * c int The padding character. * n int The number of spaces to be written. * Output: * return int 0 - OK. * 1 - Error. */ static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n) { enum {FILL_SIZE=20}; char fill[FILL_SIZE+1]; /* * Fill the buffer with the specified padding character. */ memset(fill, c, FILL_SIZE); fill[FILL_SIZE] = '\0'; /* * Write the spaces using the above literal string of spaces as * many times as needed to output the requested number of spaces. */ while(n > 0) { int nnew = n <= FILL_SIZE ? n : FILL_SIZE; if(write_fn(data, fill, nnew) != nnew) return 1; n -= nnew; }; return 0; } /*....................................................................... * The following is an output callback function which uses fwrite() * to write to the stdio stream specified via its callback data argument. * * Input: * data void * The stdio stream to write to, specified via a * (FILE *) pointer cast to (void *). * s const char * The string to be written. * n int The length of the prefix of s[] to attempt to * write. * Output: * return int The number of characters written from s[]. This * should normally be a number in the range 0 to n. * To signal that an I/O error occurred, return -1. */ GL_WRITE_FN(_io_write_stdio) { int ndone; /* The total number of characters written */ int nnew; /* The number of characters written in the latest write */ /* * The callback data is the stdio stream to write to. */ FILE *fp = (FILE *) data; /* * Because of signals we may need to do more than one write to output * the whole string. */ for(ndone=0; ndone