/* * tinyrl.c */ /* make sure we can get fileno() */ #undef __STRICT_ANSI__ /* LIBC HEADERS */ #include #include #include #include #include /* POSIX HEADERS */ #include #include "lub/string.h" #include "private.h" /*----------------------------------------------------------------------- */ static void tty_set_raw_mode(tinyrl_t *this) { struct termios new_termios; int fd = fileno(tinyrl_vt100__get_istream(this->term)); int status; status = tcgetattr(fd,&this->default_termios); if(-1 != status) { status = tcgetattr(fd,&new_termios); assert(-1 != status); new_termios.c_iflag = 0; new_termios.c_oflag = OPOST | ONLCR; new_termios.c_lflag = 0; new_termios.c_cc[VMIN] = 1; new_termios.c_cc[VTIME] = 0; /* Do the mode switch */ status = tcsetattr(fd,TCSAFLUSH,&new_termios); assert(-1 != status); } } /*----------------------------------------------------------------------- */ static void tty_restore_mode(const tinyrl_t *this) { int fd = fileno(tinyrl_vt100__get_istream(this->term)); /* Do the mode switch */ (void)tcsetattr(fd,TCSAFLUSH,&this->default_termios); } /*----------------------------------------------------------------------- */ /* This is called whenever a line is edited in any way. It signals that if we are currently viewing a history line we should transfer it to the current buffer */ static void changed_line(tinyrl_t *this) { /* if the current line is not our buffer then make it so */ if(this->line != this->buffer) { /* replace the current buffer with the new details */ free(this->buffer); this->line = this->buffer = lub_string_dup(this->line); this->buffer_size = strlen(this->buffer); assert(this->line); } } /*----------------------------------------------------------------------- */ static bool_t tinyrl_key_default(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(key > 31) { char tmp[2]; tmp[0] = (key & 0xFF), tmp[1] = '\0'; /* inject this text into the buffer */ result = tinyrl_insert_text(this,tmp); } else { char tmp[10]; sprintf(tmp,"~%d",key); /* inject control characters as ~N where N is the ASCII code */ result = tinyrl_insert_text(this,tmp); } return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_interrupt(tinyrl_t *this, int key) { tinyrl_delete_text(this,0,this->end); this->done = BOOL_TRUE; /* keep the compiler happy */ key = key; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_start_of_line(tinyrl_t *this, int key) { /* set the insertion point to the start of the line */ this->point = 0; /* keep the compiler happy */ key = key; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_end_of_line(tinyrl_t *this, int key) { /* set the insertion point to the end of the line */ this->point = this->end; /* keep the compiler happy */ key = key; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_kill(tinyrl_t *this, int key) { /* release any old kill string */ lub_string_free(this->kill_string); /* store the killed string */ this->kill_string = lub_string_dup(&this->buffer[this->point]); /* delete the text to the end of the line */ tinyrl_delete_text(this,this->point,this->end); /* keep the compiler happy */ key = key; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_yank(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->kill_string) { /* insert the kill string at the current insertion point */ result = tinyrl_insert_text(this,this->kill_string); } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_crlf(tinyrl_t *this, int key) { tinyrl_crlf(this); this->done = BOOL_TRUE; /* keep the compiler happy */ key = key; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_up(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; tinyrl_history_entry_t *entry = NULL; if(this->line == this->buffer) { /* go to the last history entry */ entry = tinyrl_history_getlast(this->history, &this->hist_iter); } else { /* already traversing the history list so get previous */ entry = tinyrl_history_getprevious(&this->hist_iter); } if(NULL != entry) { /* display the entry moving the insertion point * to the end of the line */ this->line = tinyrl_history_entry__get_line(entry); this->point = this->end = strlen(this->line); result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_down(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->line != this->buffer) { /* we are not already at the bottom */ /* the iterator will have been set up by the key_up() function */ tinyrl_history_entry_t *entry = tinyrl_history_getnext(&this->hist_iter); if(NULL == entry) { /* nothing more in the history list */ this->line = this->buffer; } else { this->line = tinyrl_history_entry__get_line(entry); } /* display the entry moving the insertion point * to the end of the line */ this->point = this->end = strlen(this->line); result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_left(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->point > 0) { this->point--; result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_right(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->point < this->end) { this->point++; result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_backspace(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->point) { this->point--; tinyrl_delete_text(this,this->point,this->point); result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_delete(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; if(this->point < this->end) { tinyrl_delete_text(this,this->point,this->point); result = BOOL_TRUE; } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_clear_screen(tinyrl_t *this, int key) { tinyrl_vt100_clear_screen(this->term); tinyrl_vt100_cursor_home(this->term); tinyrl_reset_line_state(this); /* keep the compiler happy */ key = key; this = this; return BOOL_TRUE; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_erase_line(tinyrl_t *this, int key) { tinyrl_delete_text(this,0,this->point); this->point = 0; /* keep the compiler happy */ key = key; this = this; return BOOL_TRUE; }/*-------------------------------------------------------- */ static bool_t tinyrl_key_escape(tinyrl_t *this, int key) { bool_t result= BOOL_FALSE; switch(tinyrl_vt100_escape_decode(this->term)) { case tinyrl_vt100_CURSOR_UP: result = tinyrl_key_up(this,key); break; case tinyrl_vt100_CURSOR_DOWN: result = tinyrl_key_down(this,key); break; case tinyrl_vt100_CURSOR_LEFT: result = tinyrl_key_left(this,key); break; case tinyrl_vt100_CURSOR_RIGHT: result = tinyrl_key_right(this,key); break; case tinyrl_vt100_UNKNOWN: break; } return result; } /*-------------------------------------------------------- */ static bool_t tinyrl_key_tab(tinyrl_t *this, int key) { bool_t result = BOOL_FALSE; tinyrl_match_e status = tinyrl_complete_with_extensions(this); switch(status) { case TINYRL_COMPLETED_MATCH: case TINYRL_MATCH: { /* everything is OK with the world... */ result = tinyrl_insert_text(this," "); break; } case TINYRL_NO_MATCH: case TINYRL_MATCH_WITH_EXTENSIONS: case TINYRL_AMBIGUOUS: case TINYRL_COMPLETED_AMBIGUOUS: { /* oops don't change the result and let the bell ring */ break; } } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static void tinyrl_fini(tinyrl_t *this) { /* delete the history session */ tinyrl_history_delete(this->history); /* delete the terminal session */ tinyrl_vt100_delete(this->term); /* free up any dynamic strings */ lub_string_free(this->buffer); this->buffer = NULL; lub_string_free(this->kill_string); this->kill_string = NULL; lub_string_free(this->last_buffer); this->last_buffer = NULL; } /*-------------------------------------------------------- */ static void tinyrl_init(tinyrl_t *this, FILE *instream, FILE *outstream, unsigned stifle, tinyrl_completion_func_t *complete_fn) { int i; for(i = 0;i < NUM_HANDLERS; i++) { this->handlers[i] = tinyrl_key_default; } /* default handlers */ this->handlers[KEY_CR] = tinyrl_key_crlf; this->handlers[KEY_LF] = tinyrl_key_crlf; this->handlers[KEY_ETX] = tinyrl_key_interrupt; this->handlers[KEY_DEL] = tinyrl_key_backspace; this->handlers[KEY_BS] = tinyrl_key_backspace; this->handlers[KEY_EOT] = tinyrl_key_delete; this->handlers[KEY_ESC] = tinyrl_key_escape; this->handlers[KEY_FF] = tinyrl_key_clear_screen; this->handlers[KEY_NAK] = tinyrl_key_erase_line; this->handlers[KEY_SOH] = tinyrl_key_start_of_line; this->handlers[KEY_ENQ] = tinyrl_key_end_of_line; this->handlers[KEY_VT] = tinyrl_key_kill; this->handlers[KEY_EM] = tinyrl_key_yank; this->handlers[KEY_HT] = tinyrl_key_tab; this->line = NULL; this->max_line_length = 0; this->prompt = NULL; this->prompt_size = 0; this->buffer = NULL; this->buffer_size = 0; this->done = BOOL_FALSE; this->completion_over = BOOL_FALSE; this->point = 0; this->end = 0; this->attempted_completion_function = complete_fn; this->state = 0; this->kill_string = NULL; this->echo_char = '\0'; this->echo_enabled = BOOL_TRUE; this->isatty = isatty(fileno(instream)) ? BOOL_TRUE : BOOL_FALSE; this->last_buffer = NULL; this->last_point = 0; /* create the vt100 terminal */ this->term = tinyrl_vt100_new(instream,outstream); /* create the history */ this->history = tinyrl_history_new(stifle); } /*-------------------------------------------------------- */ int tinyrl_printf(const tinyrl_t *this, const char *fmt, ...) { va_list args; int len; va_start(args, fmt); len = tinyrl_vt100_vprintf(this->term, fmt, args); va_end(args); return len; } /*-------------------------------------------------------- */ void tinyrl_delete(tinyrl_t *this) { assert(this); if(this) { /* let the object tidy itself up */ tinyrl_fini(this); /* release the memory associate with this instance */ free(this); } } /*-------------------------------------------------------- */ /*##################################### * EXPORTED INTERFACE *##################################### */ /*----------------------------------------------------------------------- */ int tinyrl_getchar(const tinyrl_t *this) { return tinyrl_vt100_getchar(this->term); } /*----------------------------------------------------------------------- */ static void tinyrl_internal_print(const tinyrl_t *this, const char *text) { if(BOOL_TRUE == this->echo_enabled) { /* simply echo the line */ tinyrl_vt100_printf(this->term,"%s",text); } else { /* replace the line with echo char if defined */ if(this->echo_char) { unsigned i = strlen(text); while(i--) { tinyrl_vt100_printf(this->term,"%c",this->echo_char); } } } } /*----------------------------------------------------------------------- */ void tinyrl_redisplay(tinyrl_t *this) { int delta; unsigned line_len, last_line_len,count; line_len = strlen(this->line); last_line_len = (this->last_buffer ? strlen(this->last_buffer) : 0); do { if(this->last_buffer) { delta = (int)(line_len - last_line_len); if(delta > 0) { count = (unsigned)delta; /* is the current line simply an extension of the previous one? */ if(0 == strncmp(this->line,this->last_buffer,last_line_len)) { /* output the line accounting for the echo behaviour */ tinyrl_internal_print(this,&this->line[line_len - count]); break; } } else if(delta < 0) { /* is the current line simply a deletion of some characters from the end? */ if(0 == strncmp(this->line,this->last_buffer,line_len)) { if(this->echo_enabled || this->echo_char) { int shift = (int)(this->last_point - this->point); /* just get the terminal to delete the characters */ if(shift > 0) { count = (unsigned)shift; /* we've moved the cursor backwards */ tinyrl_vt100_cursor_back(this->term,count); } else if(shift < 0) { count = (unsigned)-shift; /* we've moved the cursor forwards */ tinyrl_vt100_cursor_forward(this->term,count); } /* now delete the characters */ count = (unsigned)-delta; tinyrl_vt100_erase(this->term,count); } break; } } else { /* are the lines are the same content? */ if(0 == strcmp(this->line,this->last_buffer)) { if(this->echo_enabled || this->echo_char) { delta = (int)(this->point - this->last_point); if(delta > 0) { count = (unsigned)delta; /* move the point forwards */ tinyrl_vt100_cursor_forward(this->term,count); } else if(delta < 0) { count = (unsigned)-delta; /* move the cursor backwards */ tinyrl_vt100_cursor_back(this->term,count); } } /* done for now */ break; } } } else { /* simply display the prompt and the line */ tinyrl_vt100_printf(this->term,"%s",this->prompt); tinyrl_internal_print(this,this->line); if(this->point < line_len) { /* move the cursor to the insertion point */ tinyrl_vt100_cursor_back(this->term,line_len-this->point); } break; } /* * to have got this far we must have edited the middle of a line. */ if(this->last_point) { /* move to just after the prompt of the line */ tinyrl_vt100_cursor_back(this->term,this->last_point); } /* erase the previous line */ tinyrl_vt100_erase(this->term,strlen(this->last_buffer)); /* output the line accounting for the echo behaviour */ tinyrl_internal_print(this,this->line); delta = (int)(line_len - this->point); if(delta) { /* move the cursor back to the insertion point */ tinyrl_vt100_cursor_back(this->term, (strlen(this->line) - this->point)); } } /*lint -e717 */ while(0) /*lint +e717 */; /* update the display */ (void)tinyrl_vt100_oflush(this->term); /* set up the last line buffer */ lub_string_free(this->last_buffer); this->last_buffer = lub_string_dup(this->line); this->last_point = this->point; } /*----------------------------------------------------------------------- */ tinyrl_t * tinyrl_new(FILE *instream, FILE *outstream, unsigned stifle, tinyrl_completion_func_t *complete_fn) { tinyrl_t *this=NULL; this = malloc(sizeof(tinyrl_t)); if(NULL != this) { tinyrl_init(this,instream,outstream,stifle,complete_fn); } return this; } /*----------------------------------------------------------------------- */ char * tinyrl_readline(tinyrl_t *this, const char *prompt, void *context) { FILE *istream = tinyrl_vt100__get_istream(this->term); /* initialise for reading a line */ this->done = BOOL_FALSE; this->point = 0; this->end = 0; this->buffer = lub_string_dup(""); this->buffer_size = strlen(this->buffer); this->line = this->buffer; this->prompt = prompt; this->prompt_size = strlen(prompt); this->context = context; if(BOOL_TRUE == this->isatty) { /* set the terminal into raw input mode */ tty_set_raw_mode(this); tinyrl_reset_line_state(this); while(!this->done) { int key; /* update the display */ tinyrl_redisplay(this); /* get a key */ key = tinyrl_getchar(this); /* has the input stream terminated? */ if(EOF != key) { /* call the handler for this key */ if(BOOL_FALSE == this->handlers[key](this,key)) { /* an issue has occured */ tinyrl_ding(this); } if (BOOL_TRUE == this->done) { /* * If the last character in the line (other than * the null) is a space remove it. */ if (this->end && isspace(this->line[this->end-1])) { tinyrl_delete_text(this,this->end-1,this->end); } } } else { /* time to finish the session */ this->done = BOOL_TRUE; this->line = NULL; } } /* restores the terminal mode */ tty_restore_mode(this); } else { /* This is a non-interactive set of commands */ char *s = 0, buffer[80]; size_t len = sizeof(buffer); /* manually reset the line state without redisplaying */ lub_string_free(this->last_buffer); this->last_buffer = NULL; while((sizeof(buffer) == len) && (s = fgets(buffer,sizeof(buffer),istream))) { char *p; /* strip any spurious '\r' or '\n' */ p = strchr(buffer,'\r'); if(NULL == p) { p = strchr(buffer,'\n'); } if (NULL != p) { *p = '\0'; } /* skip any whitespace at the beginning of the line */ if(0 == this->point) { while(*s && isspace(*s)) { s++; } } if(*s) { /* append this string to the input buffer */ (void) tinyrl_insert_text(this,s); /* echo the command to the output stream */ tinyrl_redisplay(this); } len = strlen(buffer) + 1; /* account for the '\0' */ } /* * check against fgets returning null as either error or end of file. * This is a measure to stop potential task spin on encountering an * error from fgets. */ if( s == NULL || (this->line[0] == '\0' && feof(istream)) ) { /* time to finish the session */ this->line = NULL; } else { /* call the handler for the newline key */ if(BOOL_FALSE == this->handlers[KEY_LF](this,KEY_LF)) { /* an issue has occured */ tinyrl_ding(this); this->line = NULL; } } } /* * duplicate the string for return to the client * we have to duplicate as we may be referencing a * history entry or our internal buffer */ { char *result = this->line ? lub_string_dup(this->line) : NULL; /* free our internal buffer */ free(this->buffer); this->buffer = NULL; if((NULL == result) || '\0' == *result) { /* make sure we're not left on a prompt line */ tinyrl_crlf(this); } return result; } } /*----------------------------------------------------------------------- */ char * tinyrl_forceline(tinyrl_t *this, const char *prompt, void *context, const char *line) { char *s = 0, *buffer = NULL; char *p; /* initialise for reading a line */ this->done = BOOL_FALSE; this->point = 0; this->end = 0; this->buffer = lub_string_dup(""); this->buffer_size = strlen(this->buffer); this->line = this->buffer; this->prompt = prompt; this->prompt_size = strlen(prompt); this->context = context; /* manually reset the line state without redisplaying */ lub_string_free(this->last_buffer); this->last_buffer = NULL; buffer = lub_string_dup(line); s = buffer; /* strip any spurious '\r' or '\n' */ p = strchr(buffer,'\r'); if(NULL == p) { p = strchr(buffer,'\n'); } if (NULL != p) { *p = '\0'; } /* skip any whitespace at the beginning of the line */ if(0 == this->point) { while(*s && isspace(*s)) { s++; } } if(*s) { /* append this string to the input buffer */ (void) tinyrl_insert_text(this,s); /* echo the command to the output stream */ tinyrl_redisplay(this); } lub_string_free(buffer); /* call the handler for the newline key */ if(BOOL_FALSE == this->handlers[KEY_LF](this,KEY_LF)) { /* an issue has occured */ this->line = NULL; } /* * duplicate the string for return to the client * we have to duplicate as we may be referencing a * history entry or our internal buffer */ { char *result = this->line ? lub_string_dup(this->line) : NULL; /* free our internal buffer */ free(this->buffer); this->buffer = NULL; if((NULL == result) || '\0' == *result) { /* make sure we're not left on a prompt line */ tinyrl_crlf(this); } return result; } } /*----------------------------------------------------------------------- */ /* * Ensure that buffer has enough space to hold len characters, * possibly reallocating it if necessary. The function returns BOOL_TRUE * if the line is successfully extended, BOOL_FALSE if not. */ bool_t tinyrl_extend_line_buffer(tinyrl_t *this, unsigned len) { bool_t result = BOOL_TRUE; if(this->buffer_size < len) { char * new_buffer; size_t new_len = len; /* * What we do depends on whether we are limited by * memory or a user imposed limit. */ if (this->max_line_length == 0) { if(new_len < this->buffer_size + 10) { /* make sure we don't realloc too often */ new_len = this->buffer_size + 10; } /* leave space for terminator */ new_buffer = realloc(this->buffer,new_len+1); if(NULL == new_buffer) { tinyrl_ding(this); result = BOOL_FALSE; } else { this->buffer_size = new_len; this->line = this->buffer = new_buffer; } } else { if(new_len < this->max_line_length) { /* Just reallocate once to the max size */ new_buffer = realloc(this->buffer,this->max_line_length); if(NULL == new_buffer) { tinyrl_ding(this); result = BOOL_FALSE; } else { this->buffer_size = this->max_line_length - 1; this->line = this->buffer = new_buffer; } } else { tinyrl_ding(this); result = BOOL_FALSE; } } } return result; } /*----------------------------------------------------------------------- */ /* * Insert text into the line at the current cursor position. */ bool_t tinyrl_insert_text(tinyrl_t *this, const char *text) { unsigned delta = strlen(text); /* * If the client wants to change the line ensure that the line and buffer * references are in sync */ changed_line(this); if((delta + this->end) > (this->buffer_size)) { /* extend the current buffer */ if (BOOL_FALSE == tinyrl_extend_line_buffer(this,this->end + delta)) { return BOOL_FALSE; } } if(this->point < this->end) { /* move the current text to the right (including the terminator) */ memmove(&this->buffer[this->point+delta], &this->buffer[this->point], (this->end - this->point) + 1); } else { /* terminate the string */ this->buffer[this->end+delta] = '\0'; } /* insert the new text */ strncpy(&this->buffer[this->point],text,delta); /* now update the indexes */ this->point += delta; this->end += delta; return BOOL_TRUE; } /*----------------------------------------------------------------------- */ /* * A convenience function for displaying a list of strings in columnar * format on Readline's output stream. matches is the list of strings, * in argv format, such as a list of completion matches. len is the number * of strings in matches, and max is the length of the longest string in matches. * This function uses the setting of print-completions-horizontally to select * how the matches are displayed */ void tinyrl_display_matches(const tinyrl_t *this, char *const *matches, unsigned len, size_t max) { unsigned r,c; unsigned width = tinyrl_vt100__get_width(this->term); unsigned cols = width/(max+1); /* allow for a space between words */ unsigned rows = len/cols + 1; assert(matches); if(matches) { len--,matches++; /* skip the subtitution string */ /* print out a table of completions */ for(r=0; rterm,"%-*s ",max,match); } tinyrl_crlf(this); } } } /*----------------------------------------------------------------------- */ /* * Delete the text between start and end in the current line. (inclusive) * This adjusts the rl_point and rl_end indexes appropriately. */ void tinyrl_delete_text(tinyrl_t *this, unsigned start, unsigned end) { unsigned delta; /* * If the client wants to change the line ensure that the line and buffer * references are in sync */ changed_line(this); /* make sure we play it safe */ if(start > end) { unsigned tmp = end; start = end; end = tmp; } if(end > this->end) { end = this->end; } delta = (end - start) + 1; /* move any text which is left */ memmove(&this->buffer[start], &this->buffer[start+delta], this->end - end); /* now adjust the indexs */ if(this->point >= start) { if(this->point > end) { /* move the insertion point back appropriately */ this->point -= delta; } else { /* move the insertion point to the start */ this->point = start; } } if(this->end > end) { this->end -= delta; } else { this->end = start; } /* put a terminator at the end of the buffer */ this->buffer[this->end] ='\0'; } /*----------------------------------------------------------------------- */ bool_t tinyrl_bind_key(tinyrl_t *this, int key, tinyrl_key_func_t *fn) { bool_t result = BOOL_FALSE; if((key >= 0) && (key < 256)) { /* set the key handling function */ this->handlers[key] = fn; result = BOOL_TRUE; } return result; } /*-------------------------------------------------------- */ /* * Returns an array of strings which is a list of completions for text. * If there are no completions, returns NULL. The first entry in the * returned array is the substitution for text. The remaining entries * are the possible completions. The array is terminated with a NULL pointer. * * entry_func is a function of two args, and returns a char *. * The first argument is text. The second is a state argument; * it is zero on the first call, and non-zero on subsequent calls. * entry_func returns a NULL pointer to the caller when there are no * more matches. */ char ** tinyrl_completion(tinyrl_t *this, const char *line, unsigned start, unsigned end, tinyrl_compentry_func_t *entry_func) { unsigned state = 0; size_t size = 1; unsigned offset = 1; /* need at least one entry for the substitution */ char **matches = NULL; char *match; /* duplicate the string upto the insertion point */ char *text = lub_string_dupn(line,end); /* now try and find possible completions */ while( (match = entry_func(this,text,start,state++)) ) { if(size == offset) { /* resize the buffer if needed - the +1 is for the NULL terminator */ size += 10; matches = realloc(matches,(sizeof(char *) * (size + 1))); } if (NULL == matches) { /* not much we can do... */ break; } matches[offset] = match; /* * augment the substitute string with this entry */ if(1 == offset) { /* let's be optimistic */ matches[0] = lub_string_dup(match); } else { char *p = matches[0]; size_t match_len = strlen(p); /* identify the common prefix */ while((tolower(*p) == tolower(*match)) && match_len--) { p++,match++; } /* terminate the prefix string */ *p = '\0'; } offset++; } /* be a good memory citizen */ lub_string_free(text); if(matches) { matches[offset] = NULL; } return matches; } /*-------------------------------------------------------- */ void tinyrl_delete_matches(char **this) { char **matches = this; while(*matches) { /* release the memory for each contained string */ free(*matches++); } /* release the memory for the array */ free(this); } /*-------------------------------------------------------- */ void tinyrl_crlf(const tinyrl_t *this) { tinyrl_vt100_printf(this->term,"\n"); } /*-------------------------------------------------------- */ /* * Ring the terminal bell, obeying the setting of bell-style. */ void tinyrl_ding(const tinyrl_t *this) { tinyrl_vt100_ding(this->term); } /*-------------------------------------------------------- */ void tinyrl_reset_line_state(tinyrl_t *this) { /* start from scratch */ lub_string_free(this->last_buffer); this->last_buffer = NULL; tinyrl_redisplay(this); } /*-------------------------------------------------------- */ void tinyrl_replace_line(tinyrl_t *this, const char *text, int clear_undo) { size_t new_len = strlen(text); /* ignored for now */ clear_undo = clear_undo; /* ensure there is sufficient space */ if (BOOL_TRUE == tinyrl_extend_line_buffer(this,new_len)) { /* overwrite the current contents of the buffer */ strcpy(this->buffer,text); /* set the insert point and end point */ this->point = this->end = new_len; } tinyrl_redisplay(this); } /*-------------------------------------------------------- */ static tinyrl_match_e tinyrl_do_complete(tinyrl_t *this, bool_t with_extensions) { tinyrl_match_e result = TINYRL_NO_MATCH; char **matches = NULL; unsigned start,end; bool_t completion = BOOL_FALSE; bool_t prefix = BOOL_FALSE; /* find the start and end of the current word */ start = end = this->point; while(start && !isspace(this->line[start-1])) { start--; } if(this->attempted_completion_function) { this->completion_over = BOOL_FALSE; this->completion_error_over = BOOL_FALSE; /* try and complete the current line buffer */ matches = this->attempted_completion_function(this, this->line, start, end); } if((NULL == matches) && (BOOL_FALSE == this->completion_over)) { /* insert default completion call here... */ } if(matches) { /* identify and insert a common prefix if there is one */ if(0 != strncmp(matches[0],&this->line[start],strlen(matches[0]))) { /* * delete the original text not including * the current insertion point character */ if(this->end != end) { end--; } tinyrl_delete_text(this,start,end); if (BOOL_FALSE == tinyrl_insert_text(this,matches[0])) { return TINYRL_NO_MATCH; } completion = BOOL_TRUE; } if(0 == lub_string_nocasecmp(matches[0],matches[1])) { /* this is just a prefix string */ prefix = BOOL_TRUE; } /* is there more than one completion? */ if(matches[2] != NULL) { char **tmp = matches; unsigned max,len; max = len = 0; while(*tmp) { size_t size = strlen(*tmp++); len++; if(size > max) { max = size; } } if(BOOL_TRUE == completion) { result = TINYRL_COMPLETED_AMBIGUOUS; } else if(BOOL_TRUE == prefix) { result = TINYRL_MATCH_WITH_EXTENSIONS; } else { result = TINYRL_AMBIGUOUS; } if((BOOL_TRUE == with_extensions) || (BOOL_FALSE == prefix)) { /* Either we always want to show extensions or * we haven't been able to complete the current line * and there is just a prefix, so let the user see the options */ tinyrl_crlf(this); tinyrl_display_matches(this,matches,len,max); tinyrl_reset_line_state(this); } } else { result = completion ? TINYRL_COMPLETED_MATCH : TINYRL_MATCH; } /* free the memory */ tinyrl_delete_matches(matches); /* redisplay the line */ tinyrl_redisplay(this); } return result; } /*-------------------------------------------------------- */ tinyrl_match_e tinyrl_complete_with_extensions(tinyrl_t *this) { return tinyrl_do_complete(this,BOOL_TRUE); } /*-------------------------------------------------------- */ tinyrl_match_e tinyrl_complete(tinyrl_t *this) { return tinyrl_do_complete(this,BOOL_FALSE); } /*-------------------------------------------------------- */ void * tinyrl__get_context(const tinyrl_t *this) { return this->context; } /*--------------------------------------------------------- */ const char * tinyrl__get_line(const tinyrl_t *this) { return this->line; } /*--------------------------------------------------------- */ tinyrl_history_t * tinyrl__get_history(const tinyrl_t *this) { return this->history; } /*--------------------------------------------------------- */ void tinyrl_completion_over(tinyrl_t *this) { this->completion_over = BOOL_TRUE; } /*--------------------------------------------------------- */ void tinyrl_completion_error_over(tinyrl_t *this) { this->completion_error_over = BOOL_TRUE; } /*--------------------------------------------------------- */ bool_t tinyrl_is_completion_error_over(const tinyrl_t *this) { return this->completion_error_over; } /*--------------------------------------------------------- */ void tinyrl_done(tinyrl_t *this) { this->done = BOOL_TRUE; } /*--------------------------------------------------------- */ void tinyrl_enable_echo(tinyrl_t *this) { this->echo_enabled = BOOL_TRUE; } /*--------------------------------------------------------- */ void tinyrl_disable_echo(tinyrl_t *this, char echo_char) { this->echo_enabled = BOOL_FALSE; this->echo_char = echo_char; } /*--------------------------------------------------------- */ void tinyrl__set_istream(tinyrl_t *this, FILE *istream) { tinyrl_vt100__set_istream(this->term,istream); this->isatty = isatty(fileno(istream)) ? BOOL_TRUE : BOOL_FALSE; } /*-------------------------------------------------------- */ bool_t tinyrl__get_isatty(const tinyrl_t *this) { return this->isatty; } /*-------------------------------------------------------- */ FILE * tinyrl__get_istream(const tinyrl_t *this) { return tinyrl_vt100__get_istream(this->term); } /*-------------------------------------------------------- */ FILE * tinyrl__get_ostream(const tinyrl_t *this) { return tinyrl_vt100__get_ostream(this->term); } /*-------------------------------------------------------- */ const char * tinyrl__get_prompt(const tinyrl_t *this) { return this->prompt; } /*-------------------------------------------------------- */ bool_t tinyrl_is_quoting(const tinyrl_t *this) { bool_t result = BOOL_FALSE; /* count the quotes upto the current insertion point */ unsigned i = 0; while(i < this->point) { if(this->line[i++] == '"') { result = result ? BOOL_FALSE : BOOL_TRUE; } } return result; } /*--------------------------------------------------------- */ void tinyrl_limit_line_length(tinyrl_t *this, unsigned length) { this->max_line_length = length; } /*--------------------------------------------------------- */