gl_get_line(3)
π gl_get_line β the libtecla era π
okay bestie, sit down, hydrate, because we're about to read a <cyan>Library Functions Manual</cyan> page together and it's a LONG one. this is gl_get_line(3) and it's literally the moment your little program learns to let a human type at it. π«Ά
β¨ 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 π
like, all of these functions, one vibe: let the human type a line and have it be nice.
πΈ 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 π¦
so gl_get_line() is part of the tecla library (peep the libtecla(3) man page, bestie π). if the user is typing at a terminal, every single call prompts them for a line of input and then hands them interactive editing facilities, kind of exactly like the unix tcsh shell does. and it's not just boring backspace-and-go β it supports:
- recall of previously entered command lines
TABcompletion of file names- in-line wild-card expansion of filenames
and the docs for both the user-level command-line editing features and all the user configuration options live over in the tecla(7) man page. this man page is the programmer one β it's for you, the dev who wants to use this library in their app. π«΅β¨
π AN EXAMPLE π
here's a whole complete example of using gl_get_line() 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;
}
okay so what's happening here π
first, the resources that gl_get_line() needs get created by calling new_GetLine(). that allocates all the memory used in subsequent gl_get_line() calls β including the history buffer for recording previously entered lines. then one or more lines get read from the user, over and over, until either an error happens or the user types exit. then finally the resources that new_GetLine() allocated get handed back to the system by calling del_GetLine(). π
note the use of the NULL return value of del_GetLine() to make gl NULL. that's a safety precaution, periodt. if the program later tries to pass gl to gl_get_line(), said function will complain and return an error instead of attempting to use the deleted resource object. iconic defensive coding tbh π‘οΈ
π 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 goes in via the
linelenargument - the number of bytes to allocate for storing history lines goes in via the
histlenargument
history lines are stored back-to-back in a single buffer of this size. note that this means the number of history lines that can be stored at any given time depends on the lengths of the individual lines β it's not a line count, bestie, it's a byte budget. π if you want to place an upper limit on the number of lines that can be stored, see the gl_limit_history() function described later. and if you don't want history at all? just specify histlen as zero and no history buffer will be allocated. clean. β¨
on error, a message is printed tostderrandNULLis 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. bye bye resources ππ«Ά
π 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 positioned 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.
and this part is so cute: 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. main character energy, honestly. π
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 that 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 terminal, gl_get_line() just calls fgets(). no cap. π
(part 1 done β more parts coming, don't go anywhere bestie)
β¨ THE RETURN STATUS OF GL_GET_LINE β¨
Okay bestie, so like we said up top, gl_get_line() can hand you back two things: a pointer to your completed input line, or NULL. But the tea is that you can find out why it returned by checking errno and by calling gl_return_status(). π
GlReturnStatus gl_return_status(GetLine *gl);
Here are the possible enumerated values that this function returns. π«Ά
GLR_NEWLINEβ the last call togl_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. Thegl_pending_io()function can be used to see what type of I/Ogl_get_line()was waiting for. (see thegl_io_mode(3)man page for details). π¦GLR_SIGNALβ a signal was caught bygl_get_line()that had an after-signal disposition ofGLS_ABORT(Seegl_trap_signal()). πGLR_TIMEOUTβ the inactivity timer expired whilegl_get_line()was waiting for input, and the timeout callback function returnedGLTO_ABORT. Seegl_inactivity_timeout()for information about timeouts. β°GLR_FDABORTβ an application I/O callback returnedGLFD_ABORT(seegl_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 thelist-or-eofordel-char-or-list-or-eofactions at the start of a new line. πGLR_ERRORβ an unexpected error causedgl_get_line()to abort (consulterrnoand/orgl_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, whose 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 literally, without any special interpretation of the characters within it, the gl_prompt_style() function can be used to enable optional formatting 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 information 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 configuration 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 configuration 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 configuration commands from an optional .teclarc file in the user's home directory. Note that the arguments are listed in ascending order of priority, with the contents of app_string being potentially overridden by commands in app_file, and commands in app_file potentially being overridden 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 configuration 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 call to this function, or a call to gl_configure_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 π
okay bestie, picture this: you want TAB to complete stuff that is not just filenames. Maybe both. Maybe instead. Either way, you get to swap in your own alternate completion callback function, and the way you do that is by calling gl_customize_completion().
int gl_customize_completion(GetLine *gl, void *data,
CplMatchFn *match_fn);
The data argument is literally a little handbag where your application can stash whatever arbitrary, application-specific info it wants to hand to the callback. It gets passed to the callback every single time the callback is called. Like, maybe it points at the symbol table you're pulling possible completions from. very choose-your-own-adventure.
The match_fn argument specifies the callback function to be called. The CplMatchFn function type lives in libtecla.h, and so does a CPL_MATCH_FN() macro you can use to declare and prototype callback functions. The full declaration and responsibilities of callback functions are described in depth in the cpl_complete_word(3) man page. go read it, it eats.
In brief though: the callback function is responsible for looking backwards in the input line β back from the exact point where the user pressed TAB β to find the start of the word being completed. Then it has to look up possible completions of that word, and record them one by one in the WordCompletion object that gets passed to it as an argument, by calling cpl_add_completion(). If the callback function also wants to offer filename completion on top of its own specific completions, it has the option of calling the builtin file-name completion callback itself. That's documented in the cpl_complete_word(3) man page too.
little side quest: if you wantgl_get_line()to hand back the current input line when a successful completion has been made, you can set that up when you callcpl_add_completion()β just make the last character of the continuation suffix a newline character. Do that and the input line gets updated to display the completion, plus any continuation suffix up to that newline character, and thengl_get_line()returns that input line. chef's kiss.
And if your callback function, for some reason, needs to write something to the terminal? It must call gl_normal_io() before doing so. That starts a new line after the input line that's currently being edited, reinstates normal terminal I/O, and tells gl_get_line() that the input line is gonna need to be redrawn when the callback returns. no cap.
β¨ ADDING COMPLETION ACTIONS β¨
So in the previous section we covered customizing the behavior of the one and only default completion action, complete-word. Now we're levelling up: installing additional action functions, so different types of word completion can be bound to different key-sequences. This is done with the gl_completion_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 exactly as described in the cpl_complete_word man page β they specify the callback function that should be invoked to identify possible completions.
The list_only argument decides the vibe of the action you're defining: should it try to complete the word as far as possible in the input line before displaying any possible ambiguous completions, or should it just display the list of possible completions without touching the input line at all? First option = pass 0. Second option = pass 1.
The name argument is the name that configuration files and future invocations of this function should use to refer to the action. It has to either be the name of an existing completion action you want to change, or a brand new unused name for a new action.
Finally, the keyseq argument is the default key-sequence to bind the action to. Pass NULL and no new keysequence gets bound to it. simple as that.
β οΈ beware bestie β οΈ in order for the user to be able to change the key-sequence bound to actions you install this way, when you callgl_completion_action()to install a given action for the very first time, you have to do it between callingnew_GetLine()and the first call togl_get_line(). If you don't, then when the user's configuration file is read on that firstgl_get_line()call, the name of your additional action won't be known yet, and any reference to it in the configuration file will generate an error. not the vibe.
And same as we 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 π¦
The built-in key-binding actions are sufficient for most applications, sure. But occasionally a specialized application needs to define one or more custom actions, bound to application-specific key-sequences. Like, imagine a sales application that would benefit from a key-sequence that displayed the part name corresponding to the part number sitting in front of the cursor. That is clearly beyond the scope of the built-in action functions. So for these 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 your 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 they subsequently bind to the specified action name, name, in their configuration file. The data argument can be a pointer to literally anything the application wishes to have passed to the action function fn whenever that function is invoked.
Your 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 the ones that were previously passed to gl_register_action() when the action function was registered.
The count argument is a numeric argument that the user has the option of entering using the digit-argument action, before invoking the action. If the user doesn't enter a number, then count is set to 1. Nominally this argument is interpreted as a repeat count, meaning the action should be repeated that many times. In practice though, for some actions a repeat count makes little sense. When that happens, actions can either simply ignore the count argument, or use its value for a different purpose. your action, your rules.
A copy of the current input line is passed in the read-only line argument. The current cursor position within that string is given by the index contained in the curpos argument.
π¨ important π¨ direct manipulation of the input line and the cursor position is not permitted. Why? Because the rules dictated by all the various modes β vi mode versus emacs mode, no-echo mode, insert mode versus overstrike mode, etc β make it too complex for an application writer to write a conforming editing action, and it would also constrain future changes to the internals of gl_get_line(). A potential solution to this dilemma would be to allow the action function to edit the line using the existing editing actions. That's currently under consideration. stay tuned.
If the action function wants to write text to the terminal without it 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 those things it must temporarily suspend line editing by calling the gl_normal_io() function.
Here's what that function does, step by step: it 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. That last bit includes stuff like restoring the normal mapping of \n to \r\n, and, when in server mode, restoring the normal blocking form of terminal I/O. Once you've called it, your action function can read from and write to the terminal without the fear of creating a whole mess.
And good news: the action function does not need to restore the original editing environment before it returns. gl_get_line() does that automatically after the action function returns.
Here's a little 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. iconic honestly.
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 proceed.
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.
One last thing β the name argument of gl_register_action() specifies the name by which a user can refer to the action in their configuration file. That's what lets them re-bind the action to an alternate key-sequence. In order for this to work, it's necessary to call gl_register_action() between calling new_GetLine() and the first call to gl_get_line(). periodt.
πΈ HISTORY FILES πΈ
Okay so bestie, you want your app to remember what people typed even after they close it and come back? Iconic. That's what these two are for π
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 is literally just the name you want to give the history file when you're saving, or the name of an existing history file when you're loading. And it can contain home-directory and environment variable expressions, so things like "~/.myapp_history" or "$HOME/.myapp_history" are totally valid. slay β¨
Here's the cute part: along with every history line, extra info about it gets recorded too β like when the user entered it, and what its nesting level is β and that info is written as a comment right before the line in the history file. Writing it as a comment means the history file can double as a command file, just in case you ever wanna replay a whole session using it. No cap, that's the whole vibe π¦
Since comment prefixes are different in different languages, the comment argument exists so you can specify the comment prefix. For example, if your application were a unix shell, like the bourne shell, you'd put "#" here. And listen β whatever you pick 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. Same prefix, both calls, periodt π
The max_lines has to be either -1, which means "save every single line in the history list", or a positive number, which is a ceiling on how many of the most recent lines should get saved.
Both functions return non-zero on error, after writing an error message to stderr. And a lil note: gl_load_history() does not consider the non-existence of a file to be an error. So don't panic, she's chill like that π
π MULTIPLE HISTORY LISTS π
So if your app uses a single GetLine object for entering many different types of input lines, you might want gl_get_line() to tell those different types apart in the history list, and only recall lines that match the current type of line. Very main character of you tbh π
To support that, gl_get_line() marks lines being recorded in the history list with an integer identifier chosen by the application. Initially this identifier is set to 0 by new_GetLine(), but you can change it later by calling gl_group_history().
int gl_group_history(GetLine *gl, unsigned id);
The integer identifier id can be any number you choose, but heads up: gl_save_history() and gl_load_history() preserve the association between identifiers and historical input lines between program invocations, so you should choose fixed identifiers for the different types of input line your application uses. Fixed, bestie. Don't be out here being random with it π
Whenever gl_get_line() appends a new input line to the history list, the current history identifier gets recorded with it. And when it's asked to recall a historical input line, it only recalls lines marked with the current identifier. Simple as that β¨
π DISPLAYING HISTORY π
Wanna show off the history list? Call 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 max_lines is greater than or equal to zero, then no more than that number of the most recent lines get displayed. If all_groups is non-zero, lines from all history groups are displayed. Otherwise, only those of the currently selected history group show up. πΈ
The format string argument, fmt, decides how each line is displayed. It can contain arbitrary characters, which get written verbatim, interleaved with any of these format directives:
%Dβ the date on which the line was originally entered, formatted like2001-11-20.%Tβ the time of day when the line was entered, formatted like23: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.
So a format string like "%D %T %H\n" would output something like:
2001-11-20 10:23:34 Hello world
And notice the inclusion of an explicit newline character in the format string. That's not decorative, that's doing work π«Ά
π LOOKING UP HISTORY π
The gl_lookup_history() function lets the calling application look up lines in the history list. So nosy, love that for us π
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 says which line to look up. The first line entered in the history list after new_GetLine() was called is denoted by 0, and lines entered after that are denoted with successively higher numbers. And just so you know, the range of lines currently preserved in the history list can be queried by calling the gl_range_of_history() function, which gets described later. π
If the requested line is in the history list, the details of the line get 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. No touching, no changing, she's frozen βοΈ
Beware, bestie π The string returned in hline->line is part of the history buffer, so it must not be modified by the caller, and it will be recycled on the next call to any function that takes gl as its argument. So if you need to keep it around, make a private copy of that string. Copy it. Don't be out here holding a dangling pointer, that's not the vibe π
π MANUAL HISTORY ARCHIVAL π
By default, whenever a line is entered by the user, it gets automatically appended to the history list, just before gl_get_line() returns the line to the caller. Convenient for most applications, sure β but there are also applications that need finer grained control over what gets added to the history list. In those cases, you can turn off the automatic addition of entered lines by calling gl_automatic_history(). β¨
int gl_automatic_history(GetLine *gl, int enable);
If you call this with enable set to 0, gl_get_line() won't automatically archive subsequently entered lines. You can re-enable automatic archiving later by calling this function again with enable set to 1. And while automatic history archiving is disabled, the calling application can use 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 '\0' 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 '\0' terminator. And if the line is longer than the maximum input line length that was specified when new_GetLine() was called, then when the line is recalled it will get truncated to the actual gl_get_line() line length. Truncated! She gets the chop βοΈ
If successful, gl_append_history() returns 0. Otherwise it returns non-zero, and sets errno to one of these values:
EINVALβ one of the arguments passed togl_append_history()wasNULL.ENOMEMβ the specified line was longer than the allocated size of the history buffer (as specified whennew_GetLine()was called), so it couldn't be archived.
A textual description of the error can optionally be obtained by calling gl_error_message(). And lowkey, note that after such an error, the history list remains in a valid state to receive new history lines, so there's little harm in simply ignoring the return status of gl_append_history(). She's resilient, she recovers π
π§ MISCELLANEOUS HISTORY CONFIGURATION π§
Wanna change the size of the history buffer that you originally specified in the call to new_GetLine()? Do it with gl_resize_history(). πΈ
int gl_resize_history(GetLine *gl, size_t histlen);
The histlen argument specifies the new size in bytes, and if you specify it as 0, the buffer will be deleted. Gone. Poof. π¦
Now, like we said back in the new_GetLine() discussion: 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. Math is math. Although the buffer is never expanded when new lines are added, a list of pointers into the buffer does get expanded when needed to accommodate the number of lines currently stored in the buffer. So 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. Sorry babes, they're evicted πͺ
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_history()), or all historical lines in the buffer. Choose wisely π
And the gl_toggle_history() function lets you toggle history on and off without losing the current contents of the history list. Which is huge, honestly.
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-bindings will act as though there is nothing in the history buffer. Nothing there. Empty. A void. πβ¨
π QUERYING HISTORY INFORMATION π
okay so you've got a history list all set up, and now you're likeβ¦ "bestie, what is it actually doing rn?" β and there is literally a function for that. 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);
when it comes back, the status info gets recorded in the variable that the state argument points at. so you point, it fills, you vibe. π«Ά
next up, the gl_range_of_history() function hands you the number and the range of lines chillin' in the history list. no cap.
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 land in the variable pointed to by the range argument. and here's the tea β β if the nlines member of that struct is greater than zero, then oldest and newest report the range of lines in the list, and newest=oldest+nlines-1. otherwise? they're both zero. simple as that. π
and then there's gl_size_of_history(), which tells you the total size of the history buffer and how much of it is currently occupied. it's giving "how full is the cup" energy. π
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 info is recorded in the variable pointed to by the size argument. boom. done. π
π CHANGING TERMINALS π
so the new_GetLine() constructor function just assumes input is being read from stdin and output is written to stdout. like, default behaviour, no thoughts head empty. but bestie, what if you wanna switch things up?? that's what this next function is for. π
int gl_change_terminal(GetLine *gl, FILE *input_fp,
FILE *output_fp, const char *term);
- the
glargument is the object thatnew_GetLine()handed back to you. input_fpsays which stream to read from.output_fpsays which stream to write to.
and here's the crucial bit β only if both of these refer to a terminal will interactive terminal input actually get enabled. otherwise gl_get_line() will just straight up call fgets() to read command input. no fancy line editing, no sparkle. just fgets(). π
if both streams do refer to a terminal, then they have to refer to the same terminal, and the type of that terminal must be specified via the term argument. the value of term gets looked up in the terminal information database (terminfo or termcap), to figure out which special control sequences are needed to control all the various aspects of the terminal. new_GetLine(), for example, passes the return value of getenv("TERM") in this argument. π
heads up bestie: if one or both ofinput_fpandoutput_fpdon't refer to a terminal, then it is legal to passNULLinstead of a terminal type. π
also, ngl, if you want to pass file descriptors to gl_change_terminal(), you can do that by creating stdio stream wrappers using the POSIX fdopen() function. iykyk. β¨
π‘ 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. it's committed. it's loyal. it's not leaving. π
but in programs that need to watch for I/O from other sources than the terminal, you've got two options:
- Use the functions described in the
gl_io_mode(3)man page to switchgl_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 bygl_get_line(). - 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 theselect()system call, which is most, if not all flavors of unix.
so option 1 is the β¨versatile but you do the choresβ¨ route, and option 2 is the "hey also keep an eye on this socket for me" route. both iconic, different vibes. π¦
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. π so likeβ¦ check your args, and check your system. fr fr.
the fd argument is the file descriptor to be watched. the event argument 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 callback 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. multitasking queen. π
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.
important bestie, do not skip this one: note that before calling the callback, gl_get_line() 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. that's a whole lot of steps but skipping any of them is giving chaos. π
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_normal_io() was called, redisplay the input line and resume editing. and note that in this case, the return values GLFD_REFRESH and GLFD_CONTINUE are equivalent. so pick whichever, they're twinning. π«Ά
okay so this next paragraph is a bit of a mess in the original page β it looks like the text got corrupted mid-sentence, so i'm giving it to you exactly as it appears, no edits, no guesses, no cap:
To support cases where the callback function calls a third-party
function which occasionally and unp0eiscre-enabledebeforehtheecallback
the 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.
so likeβ¦ the first part is garbled in the source, but the takeaway that survives is: if the callback knows that the third-party function wrote to the terminal, it should 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. clean, tidy, unregistered. periodt. π
β³ SETTING AN INACTIVITY TIMEOUT β³
Okay bestie, if your system has the select() system call, you can use gl_inactivity_timeout() to set or cancel an inactivity timeout. And "inactivity" here means both keyboard input and I/O on any file descriptors registered by prior and subsequent calls to gl_watch_fd(). On those oddball systems that don't have select(), this call literally does nothing. Zero. Nada. π
int gl_inactivity_timeout(GetLine *gl, GlTimeoutFn *callback,
void *data, unsigned long sec,
unsigned long nsec);
The timeout is given as an integral number of seconds plus an integral number of nanoseconds, via the sec and nsec arguments respectively. After that, whenever no activity is seen for that time period, the function you passed as callback gets called. The data argument of gl_inactivity_timeout() is passed verbatim to that callback whenever it fires, so you can yeet whatever application-specific info you want into it. π
And libtecla.h hands you this macro so you can declare and prototype your timeout callback functions:
#define GL_TIMEOUT_FN(fn) \
GlAfterTimeout (fn)(GetLine *gl, void *data)
When it's done, your callback is expected to return one of these enumerators, telling gl_get_line() how to proceed now that the timeout has been handled:
GLTO_ABORTβ Tellgl_get_line()to abort. When this happens,gl_get_line()will returnNULL, and a following call togl_return_status()will returnGLR_TIMEOUT. Heads up though: if your application needserrnoto always have a meaningful value whengl_get_line()returnsNULL, the callback function should seterrnoappropriately. πGLTO_REFRESHβ Redraw the input line, then continue waiting for input. Return this value if your callback wrote to the terminal without having first calledgl_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 (seegl_io_mode(3)), this causesgl_get_line()to act as though I/O blocked. That meansgl_get_line()will immediately returnNULL, and a following call togl_return_status()will returnGLR_BLOCKED.
Note that before calling the callback, gl_get_line() blocks most signals, and leaves its own signal handlers installed. So if you need to catch a particular signal you'll need to both temporarily install your own signal handler and unblock the signal. And be sure to re-block the signal (if it was originally blocked) and reinstate the original signal handler, if any, before returning. Don't skip that part fr fr. π
If the callback function needs to read from or write to the terminal, it should ideally first call gl_normal_io(gl) to temporarily suspend line editing. That restores the terminal to canonical, blocking-I/O mode and moves the cursor to the start of a new terminal line. Later, when the callback returns, gl_get_line() will notice that gl_normal_io() was called, redisplay the input line and resume editing. In this case the return values GLTO_REFRESH and GLTO_CONTINUE are equivalent. π«Ά
To support cases where the callback function calls a third-party function which occasionally and unp0eiscre-enabledebeforehtheecallback the 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 component, few computer clocks presently have resolutions 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 that 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 callback argument of 0. The data argument is ignored in this case. Periodt.
π¨ 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 terminate the process, since the terminal needs to be restored to a usable state before that happens. In this section, the signals trapped by default and how gl_get_line() responds to them are described. Changing these defaults is the topic of the following section. π
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 calling 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=EINTRSIGHUPβ This signal is generated when the controlling terminal exits.errno=ENOTTYSIGPIPEβ 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 calledgl_change_terminal()to redirect output to a pipe hidden under a pseudo terminal.errno=EPIPESIGQUITβ This signal is generated by the keyboard quit key (usually^\).errno=EINTRSIGABRTβ This signal is generated by the standard Cabort()function. By default it both terminates the process and generates a core dump.errno=EINTRSIGTERMβ This is the default signal that the UN*Xkillcommand 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 signals 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 accommodate 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,SIGIOandSIGPOLLare the same. On other systems,SIGIOis ignored by default, sogl_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 (seeman 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 ifSIGKILLorSIGPOLL, which by definition can't be caught, or any of the hardware generated exception signals, such asSIGSEGV,SIGBUSandSIGFPE, are received and unhandled whilegl_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-control shells usually reset the terminal settings when a process relinquishes the controlling terminal, so this is only a problem with older shells. π
π CUSTOMIZED SIGNAL HANDLING π
ok so last section was all about the signals gl_get_line() traps by default and how it reacts to them, right? now we're getting into the fun part: how to add signals to that trapped list, how to remove them, and how to tell gl_get_line() exactly how it should react to any given signal. full control, no cap. β¨
If you don't need gl_get_line() to do literally anything in response to a signal that it normally traps, you can just tell it 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 (like, SIGINT, for example) that you want ignored. And if the signal you named isn't currently one of the trapped ones? This function just does nothing. zero drama. π«Ά
Then we have gl_trap_signal(), which is the one that lets you either add a brand new signal to the list gl_get_line() traps, or modify how it responds to a signal it's already trapping. glow-up for your signal handling, honestly. π
int gl_trap_signal(GetLine *gl, int signo, unsigned flags,
GlAfterSignal after, int errno_value);
So, breaking it down:
signois the number of the signal you want trapped.flagsis a set of flags that determine the environment your application's signal handler gets invoked in.aftertellsgl_get_line()what to do once your application's signal handler returns.errno_valuetellsgl_get_line()what to seterrnoto if it's told to abort.
πΈ the flags argument πΈ
flags is a bitwise OR of zero or more of these enumerators, bestie:
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 (mansigprocmask),gl_get_line()does not trap that signal. This flag tellsgl_get_line()to trap the signal and unblock it for the duration of the call togl_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 flag combos that come up constantly are also enumerated for you, iconic behaviour:
GLS_RESTORE_ENV = GLS_RESTORE_SIG | GLS_RESTORE_TTY |
GLS_REDRAW_LINE
GLS_SUSPEND_INPUT = GLS_RESTORE_ENV | GLS_RESTORE_LINE
β when to use 0 β
If your signal handler β or the default system signal handler for that signal, in case you haven't overridden it β never writes to the terminal, nor suspends or terminates the calling program, then you can safely set the flags argument to 0. easy. π
π₯ when to use GL_SUSPEND_INPUT π₯
But if your signal handler always writes to the terminal, reads from it, or suspends or terminates the program, you should specify the flags argument as GL_SUSPEND_INPUT, so that:
- The cursor doesn't get left in the middle of the input line.
- So that the user can type in input and have it echoed.
- So that you don't need to end each output line with
\r\n, instead of just\n.
π¦ about GL_RESTORE_ENV π¦
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. lowkey perfect for when you have a signal handler that only occasionally writes to the terminal β because using GL_SUSPEND_LINE 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 single 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 exactly why the GL_SUSPEND_INPUT combination is better if you know that you are always going to be writing to the terminal. π
π the after argument π
The after argument determines what gl_get_line() does after the application's signal handler returns (you know, if it returns at all π). It can take any one of these values:
GLS_RETURNβ Return the completed input line, just as though the user had pressed the return key.GLS_ABORTβ Causegl_get_line()to abort. When this happens,gl_get_line()returnsNULL, and a following call togl_return_status()will returnGLR_SIGNAL. Note that if the application needserrnoalways to have a meaningful value whengl_get_line()returnsNULL, the callback function should seterrnoappropriately.GLS_CONTINUEβ Resume command line editing.
π the errno_value argument π
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. so handy, fr fr. β¨
β¨ RELIABLE SIGNAL HANDLING β¨
okay bestie, buckle up, because this is the part where everybody thinks they can write a signal handler and then the race conditions come for them π Signal handling is suprisingly hard to do reliably without race conditions. In gl_get_line() a lot of care has been taken to allow applications 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 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 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. like bestie just wants to ^C and go, and you're making them wait?? rude.
The way to avoid such problems is to do the following.
- If needed, use the
gl_trap_signal()function to configuregl_get_line()to abort when important signals are caught. - Configure
gl_get_line()such that if any of the signals that it catches are blocked whengl_get_line()is called, they will be unblocked automatically during times whengl_get_line()is waiting for I/O. This can be done either on a per signal basis, by calling thegl_trap_signal()function, and specifying theGLS_UNBLOCKattribute of the signal, or globally by calling thegl_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.
- Just before calling
gl_get_line(), block delivery of all of the signals thatgl_get_line()is configured to trap. This can be done using the POSIXsigprocmask()function in conjunction with thegl_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().
- 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. - Eventually
gl_get_line()returns, either because a signal was caught, an error occurred, or the user finished entering their input line. - Now one would check the global signal flag again, and if it is set, respond to it, and zero the flag.
- Use
sigprocmask()to unblock the signals that were blocked in step 3.
The same technique can be used around certain POSIX signal-aware functions, such as sigsetjmp() and sigsuspend(), and in particular, the former of these two functions can be used in conjunction with siglongjmp() 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. ngl this is the good part, the little engine that could π₯
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. foreshadowing, we love her
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.
- It records the number of the signal that was received in a file-scope variable.
- It then calls the POSIX
siglongjmp()function using the buffer that was passed tosigsetjmp()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. no stepping on toes in THIS group chat π«Ά
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 executing. 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 signals being caught. the way that sigsetjmp() just HANDLES it π iconic
- 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 whengl_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. β οΈ fr fr, skip this and your terminal is GONE, cursed, unusable, do not pass go - It then reinstates the application's signal handlers.
- Then it uses the C standard-library
raise()function to re-send the application the signal that was caught. - 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. - If the signal handler returns (ie. it doesn't terminate the process),
gl_get_line()blocks delivery of the above signal again. - It then undoes any actions performed in the first of the above steps, and redisplays the line, if the signal configuration calls for this.
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. asynchronous chaos β synchronous order. she's a project manager actually π
πΈ THE TERMINAL SIZE πΈ
On most systems the combination of the TIOCGWINSZ ioctl and the SIGWINCH 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. she's always checking in on her dimensions, self-care π
On the few systems where this mechanism isn't available, at startup new_GetLine() first looks for the LINES and COLUMNS 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.
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. β οΈ less than 1 is not a vibe, don't do it On systems that do support 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 accommodate 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. pipes and files stay un-measured, that's just the way it is bestie
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 dimensions, 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. your backup plan, your safety net, your girl π¦
π HIDING WHAT YOU TYPE π
ok so when you're typing in something sensitive β passwords, secrets, the whole vibe β you do NOT want it echoed back on the terminal, bestie. and lowkey you also don't want it living in the history list, because somebody walking past your unattended terminal could just... recall it. or somebody snooping through your directories could find it sitting in your history file. π
so with that in mind, the gl_echo_mode() function lets you toggle the display and the archival of any text entered in subsequent calls to gl_get_line() on and off. π
int gl_echo_mode(GetLine *gl, int enable);
the enable argument says whether the entered text should be visible or not:
- if it's
0, then lines entered after this will not be visible on the terminal, and will not be recorded in the history list. - if it's
1, then subsequent input lines get displayed as you type them, and β provided history hasn't been turned off via a call togl_toggle_history()β they'll also be archived in the history list. - if it's
-1, the echoing mode is left unchanged, which is the cute lil non-destructive way to query the current setting through the return value. π«Ά
in all cases, the return value is 0 if echoing was disabled before the function was called, and 1 if it was enabled.
when echoing is turned off, heads up: tab completion will still invisibly complete your prefix as far as it can, but ambiguous completions will not be displayed. no visual confirmation, just vibes. π
π€ SINGLE CHARACTER QUERIES π€
using gl_get_line() to ask the user for a single character reply is honestly inconvenient, because they have to hit enter or return before the character they typed actually comes back to the program. rude. so gl_query_char() exists for exactly this kind of one-character query. β¨
int gl_query_char(GetLine *gl, const char *prompt,
char defchar);
it displays the prompt you gave it at the start of a new line, and waits for the user to type a character. the moment they type one, gl_query_char() displays it to the right of the prompt, starts a newline, then returns that character to the calling program. the return value is the character that was typed. if the read had to be aborted for some reason, EOF is returned instead β and in that case the application can call the previously documented gl_return_status() to find out what went wrong. that could be, like, the reception of a signal, or the optional inactivity timer going off. π°οΈ
if the user just hits enter, the value of the defchar argument gets substituted. so when they hit either newline or return, the character in defchar is displayed after the prompt as though they'd typed it themselves, and it gets returned to the calling application. if that replacement doesn't matter to you, just pass 0 as the value of defchar.
if the entered character is unprintable, it's displayed symbolically. for example, control-A shows up as ^A, and characters beyond 127 are displayed in octal, preceded by a backslash. π
same as with gl_get_line(), echoing of the entered character can be disabled with the gl_echo_mode() function.
if the calling process gets suspended while it's waiting for the user to type their response, the cursor moves to the line following the prompt line, and then when the process resumes, the prompt is redisplayed and gl_query_char() goes right back to waiting for a character. resilient queen. π
note that in non-blocking server mode (seegl_io_mode(3)), if an incomplete input line is in the process of being read whengl_query_char()is called, that partial input line is discarded and erased from the terminal before the new prompt is displayed. so the next call togl_get_line()will start editing a fresh new line. π§Ό
πΎ READING RAW CHARACTERS πΎ
whereas gl_query_char() visibly prompts for a character and shows what they typed, gl_read_char() just reads a single character from the user without writing anything to the terminal, and without perturbing any incompletely entered input line. which means it can be called not only from between calls to gl_get_line(), but also from callback functions that the application registered to be called by gl_get_line(). sneaky and useful. π¦
int gl_read_char(GetLine *gl);
on success, the return value is the character that was read. on failure, EOF is returned, and you can call gl_return_status() 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() - terminal I/O blocking, when in non-blocking server-I/O mode
beware bestie: certain keyboard keys, like function keys and cursor keys, usually generate at least 3 characters each, so a single call to gl_read_char() is not gonna be enough to identify such keystrokes. one char β one key. π
π§½ CLEARING THE TERMINAL π§½
the calling program can clear the terminal by calling gl_erase_terminal(). in non-blocking server-I/O mode, this function also arranges for the current input line to be redrawn from scratch the next time gl_get_line() is called. fresh start, periodt. β¨
int gl_erase_terminal(GetLine *gl);
π DISPLAYING TEXT DYNAMICALLY π
between calls to gl_get_line(), gl_display_text() is the convenient way to display paragraphs of text, left-justified and split across one or more terminal lines according to whatever the current width of the terminal happens to be. you can find examples of this in the demo programs, where it's used to display introductions β and those examples also show off the advanced use of optional prefixes, suffixes and filled lines to draw a whole box around the text. so aesthetic. π
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 β like, say, if the output of a program using gl_get_line() is being piped to another program or redirected to a file β then the value of the def_width parameter is used as the terminal width.
the indentation argument says how many characters to use to indent each line of output. the fill_char argument says which character performs that indentation.
the prefix argument can either be NULL, or a string to place at the beginning of each new line (after any indentation). similarly, suffix can be NULL or a string placed at the end of each line. the suffix goes 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 given via fill_char. 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, which is how it knows the starting column index of the cursor. and since the return value is the ending column index of the cursor, you can chain them: pass the return value of one call as the start argument of the next, and a paragraph broken between more than one string can be composed by calling gl_display_text() for each successive portion. π«Ά
note that literal newline characters are necessary at the end of each paragraph to force a new line to be started. no newline, no new line, iykyk.
on error, gl_display_text() returns -1. π
π£ CALLBACK FUNCTION FACILITIES π£
unless otherwise stated, callback functions β like tab completion callbacks and event callbacks β should not call any functions in this module. but the following function is designed specifically to be used by callback functions. π«§
calling gl_replace_prompt() from a callback tells gl_get_line() to display a different prompt once the callback returns. except in non-blocking server mode, it has no effect if you use it between calls to gl_get_line(). in non-blocking server mode (see the gl_io_mode(3) man page), when it's used between two calls to gl_get_line() that are operating on the same input line, the current input line gets re-drawn with the new prompt on the following call to gl_get_line(). glow-up mid-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. that means every 8-bit character that's printable in the user's current locale now gets displayed verbatim and included in the returned input line. π
assuming the calling program correctly contains a call like this:
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 those 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 this command at the shell prompt:
locale -a
further documentation on how the user can make use of this to enter international characters lives in the tecla(7) man page. πΈ
π§΅ THREAD SAFETY π§΅
in a multi-threaded program, you should use the libtecla_r.a version of the library. that one 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 though β see the corresponding man pages for details. π)
however, thanks 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. one thread, no termcap drama. π
π FILES π
libtecla.aβ the tecla librarylibtecla.hβ the tecla header file.~/.teclarcβ the personal tecla customization file.
π SEE ALSO π
libtecla(3), gl_io_mode(3), tecla(7), ef_expand_file(3), cpl_complete_word(3), pca_lookup_file(3)
π AUTHOR π
Martin Shepherd (mcs@astro.caltech.edu)
and that's a wrap on the whole gl_get_line(3) page, bestie!! π this was the gl_get_line(3) manual, from the libtecla source β go forth and build the most gorgeous line-editing interface ever, you absolute icon. mwah. πβ¨ππ¦
π¦ where this page comes from
- debian bookworm: libtecla-dev 1.6.3-3+b1 Β· BSDlike
- debian trixie: libtecla-dev 1.6.3-3.1 Β· BSDlike
- ubuntu jammy: libtecla-dev 1.6.3-3 Β· BSDlike
- ubuntu noble: libtecla-dev 1.6.3-3.1build1 Β· BSDlike
- ubuntu questing: libtecla-dev 1.6.3-3.1build1
- ubuntu resolute: libtecla-dev 1.6.3-4 Β· BSDlike
- ubuntu stonking: libtecla-dev 1.6.3-5 Β· BSDlike
- Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 by Martin C. Shepherd
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms