/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Standard includes. */ #include #include #include #include /* * Local includes. */ #include "libtecla.h" #include "ioutil.h" #include "stringrp.h" #include "pathutil.h" #include "cplfile.h" #include "cplmatch.h" #include "errmsg.h" /* * Specify the number of strings to allocate when the string free-list * is exhausted. This also sets the number of elements to expand the * matches[] array by whenever it is found to be too small. */ #define STR_BLK_FACT 100 /* * Set the default number of spaces place between columns when listing * a set of completions. */ #define CPL_COL_SEP 2 /* * Completion matches are recorded in containers of the following * type. */ struct WordCompletion { ErrMsg *err; /* The error reporting buffer */ StringGroup *sg; /* Memory for a group of strings */ int matches_dim; /* The allocated size of result.matches[] */ CplMatches result; /* Completions to be returned to the caller */ #ifndef WITHOUT_FILE_SYSTEM CompleteFile *cf; /* The resources used for filename completion */ #endif }; static void cpl_sort_matches(WordCompletion *cpl); static void cpl_zap_duplicates(WordCompletion *cpl); static void cpl_clear_completions(WordCompletion *cpl); static int cpl_cmp_matches(const void *v1, const void *v2); static int cpl_cmp_suffixes(const void *v1, const void *v2); /* * The new_CplFileConf() constructor sets the integer first member of * the returned object to the following magic number. On seeing this, * cpl_file_completions() knows when it is passed a valid CplFileConf * object. */ #define CFC_ID_CODE 4568 #ifndef WITHOUT_FILE_SYSTEM /* * A pointer to a structure of the following type can be passed to * the builtin file-completion callback function to modify its behavior. */ struct CplFileConf { int id; /* new_CplFileConf() sets this to CFC_ID_CODE */ int escaped; /* If none-zero, backslashes in the input line are */ /* interpreted as escaping special characters and */ /* spaces, and any special characters and spaces in */ /* the listed completions will also be escaped with */ /* added backslashes. This is the default behaviour. */ /* If zero, backslashes are interpreted as being */ /* literal parts of the filename, and none are added */ /* to the completion suffixes. */ int file_start; /* The index in the input line of the first character */ /* of the filename. If you specify -1 here, */ /* cpl_file_completions() identifies the */ /* the start of the filename by looking backwards for */ /* an unescaped space, or the beginning of the line. */ CplCheckFn *chk_fn; /* If not zero, this argument specifies a */ /* function to call to ask whether a given */ /* file should be included in the list */ /* of completions. */ void *chk_data; /* Anonymous data to be passed to check_fn(). */ }; static void cpl_init_FileConf(CplFileConf *cfc); /* * When file-system access is being excluded, define a dummy structure * to satisfy the typedef in libtecla.h. */ #else struct CplFileConf {int dummy;}; #endif /* * Encapsulate the formatting information needed to layout a * multi-column listing of completions. */ typedef struct { int term_width; /* The width of the terminal (characters) */ int column_width; /* The number of characters within in each column. */ int ncol; /* The number of columns needed */ int nline; /* The number of lines needed */ } CplListFormat; /* * Given the current terminal width, and a list of completions, determine * how to best use the terminal width to display a multi-column listing * of completions. */ static void cpl_plan_listing(CplMatches *result, int term_width, CplListFormat *fmt); /* * Display a given line of a multi-column list of completions. */ static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data); /*....................................................................... * Create a new string-completion object. * * Output: * return WordCompletion * The new object, or NULL on error. */ WordCompletion *new_WordCompletion(void) { WordCompletion *cpl; /* The object to be returned */ /* * Allocate the container. */ cpl = (WordCompletion *) malloc(sizeof(WordCompletion)); if(!cpl) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_WordCompletion(). */ cpl->err = NULL; cpl->sg = NULL; cpl->matches_dim = 0; cpl->result.suffix = NULL; cpl->result.cont_suffix = NULL; cpl->result.matches = NULL; cpl->result.nmatch = 0; #ifndef WITHOUT_FILE_SYSTEM cpl->cf = NULL; #endif /* * Allocate a place to record error messages. */ cpl->err = _new_ErrMsg(); if(!cpl->err) return del_WordCompletion(cpl); /* * Allocate an object that allows a group of strings to be allocated * efficiently by placing many of them in contiguous string segments. */ #ifdef WITHOUT_FILE_SYSTEM cpl->sg = _new_StringGroup(MAX_PATHLEN_FALLBACK); #else cpl->sg = _new_StringGroup(_pu_pathname_dim()); #endif if(!cpl->sg) return del_WordCompletion(cpl); /* * Allocate an array for matching completions. This will be extended later * if needed. */ cpl->matches_dim = STR_BLK_FACT; cpl->result.matches = (CplMatch *) malloc(sizeof(cpl->result.matches[0]) * cpl->matches_dim); if(!cpl->result.matches) { errno = ENOMEM; return del_WordCompletion(cpl); }; /* * Allocate a filename-completion resource object. */ #ifndef WITHOUT_FILE_SYSTEM cpl->cf = _new_CompleteFile(); if(!cpl->cf) return del_WordCompletion(cpl); #endif return cpl; } /*....................................................................... * Delete a string-completion object. * * Input: * cpl WordCompletion * The object to be deleted. * Output: * return WordCompletion * The deleted object (always NULL). */ WordCompletion *del_WordCompletion(WordCompletion *cpl) { if(cpl) { cpl->err = _del_ErrMsg(cpl->err); cpl->sg = _del_StringGroup(cpl->sg); if(cpl->result.matches) { free(cpl->result.matches); cpl->result.matches = NULL; #ifndef WITHOUT_FILE_SYSTEM cpl->cf = _del_CompleteFile(cpl->cf); #endif }; free(cpl); }; return NULL; } /*....................................................................... * This function is designed to be called by CplMatchFn callback * functions. It adds one possible completion of the token that is being * completed to an array of completions. If the completion needs any * special quoting to be valid when displayed in the input line, this * quoting must be included in the string. * * Input: * cpl WordCompletion * The argument of the same name that was passed * to the calling CplMatchFn callback function. * line const char * The input line, as received by the callback * function. * word_start int The index within line[] of the start of the * word that is being completed. * word_end int The index within line[] of the character which * follows the incomplete word, as received by the * calling callback function. * suffix const char * The appropriately quoted string that could * be appended to the incomplete token to complete * it. A copy of this string will be allocated * internally. * type_suffix const char * When listing multiple completions, gl_get_line() * appends this string to the completion to indicate * its type to the user. If not pertinent pass "". * Otherwise pass a literal or static string. * cont_suffix const char * If this turns out to be the only completion, * gl_get_line() will append this string as * a continuation. For example, the builtin * file-completion callback registers a directory * separator here for directory matches, and a * space otherwise. If the match were a function * name you might want to append an open * parenthesis, etc.. If not relevant pass "". * Otherwise pass a literal or static string. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_add_completion(WordCompletion *cpl, const char *line, int word_start, int word_end, const char *suffix, const char *type_suffix, const char *cont_suffix) { CplMatch *match; /* The container of the new match */ char *string; /* A newly allocated copy of the completion string */ size_t len; /* * Check the arguments. */ if(!cpl) return 1; if(!suffix) return 0; if(!type_suffix) type_suffix = ""; if(!cont_suffix) cont_suffix = ""; /* * Do we need to extend the array of matches[]? */ if(cpl->result.nmatch+1 > cpl->matches_dim) { int needed = cpl->matches_dim + STR_BLK_FACT; CplMatch *matches = (CplMatch *) realloc(cpl->result.matches, sizeof(cpl->result.matches[0]) * needed); if(!matches) { _err_record_msg(cpl->err, "Insufficient memory to extend array of matches.", END_ERR_MSG); return 1; }; cpl->result.matches = matches; cpl->matches_dim = needed; }; /* * Allocate memory to store the combined completion prefix and the * new suffix. */ len = strlen(suffix); string = _sg_alloc_string(cpl->sg, word_end-word_start + len); if(!string) { _err_record_msg(cpl->err, "Insufficient memory to extend array of matches.", END_ERR_MSG); return 1; }; /* * Compose the string. */ strncpy(string, line + word_start, word_end - word_start); strlcpy(string + word_end - word_start, suffix, len + 1); /* * Record the new match. */ match = cpl->result.matches + cpl->result.nmatch++; match->completion = string; match->suffix = string + word_end - word_start; match->type_suffix = type_suffix; /* * Record the continuation suffix. */ cpl->result.cont_suffix = cont_suffix; return 0; } /*....................................................................... * Sort the array of matches. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_sort_matches(WordCompletion *cpl) { qsort(cpl->result.matches, cpl->result.nmatch, sizeof(cpl->result.matches[0]), cpl_cmp_matches); } /*....................................................................... * This is a qsort() comparison function used to sort matches. * * Input: * v1, v2 void * Pointers to the two matches to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int cpl_cmp_matches(const void *v1, const void *v2) { const CplMatch *m1 = (const CplMatch *) v1; const CplMatch *m2 = (const CplMatch *) v2; return strcmp(m1->completion, m2->completion); } /*....................................................................... * Sort the array of matches in order of their suffixes. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_sort_suffixes(WordCompletion *cpl) { qsort(cpl->result.matches, cpl->result.nmatch, sizeof(cpl->result.matches[0]), cpl_cmp_suffixes); } /*....................................................................... * This is a qsort() comparison function used to sort matches in order of * their suffixes. * * Input: * v1, v2 void * Pointers to the two matches to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int cpl_cmp_suffixes(const void *v1, const void *v2) { const CplMatch *m1 = (const CplMatch *) v1; const CplMatch *m2 = (const CplMatch *) v2; return strcmp(m1->suffix, m2->suffix); } /*....................................................................... * Find the common prefix of all of the matching completion matches, * and record a pointer to it in cpl->result.suffix. Note that this has * the side effect of sorting the matches into suffix order. * * Input: * cpl WordCompletion * The completion resource object. * Output: * return int 0 - OK. * 1 - Error. */ static int cpl_common_suffix(WordCompletion *cpl) { CplMatches *result; /* The result container */ const char *first, *last; /* The first and last matching suffixes */ int length; /* The length of the common suffix */ /* * Get the container of the array of matching files. */ result = &cpl->result; /* * No matching completions? */ if(result->nmatch < 1) return 0; /* * Sort th matches into suffix order. */ cpl_sort_suffixes(cpl); /* * Given that the array of matches is sorted, the first and last * suffixes are those that differ most in their prefixes, so the common * prefix of these strings is the longest common prefix of all of the * suffixes. */ first = result->matches[0].suffix; last = result->matches[result->nmatch - 1].suffix; /* * Find the point at which the first and last matching strings * first difffer. */ while(*first && *first == *last) { first++; last++; }; /* * How long is the common suffix? */ length = first - result->matches[0].suffix; /* * Allocate memory to record the common suffix. */ result->suffix = _sg_alloc_string(cpl->sg, length); if(!result->suffix) { _err_record_msg(cpl->err, "Insufficient memory to record common completion suffix.", END_ERR_MSG); return 1; }; /* * Record the common suffix. */ strncpy(result->suffix, result->matches[0].suffix, length); result->suffix[length] = '\0'; return 0; } /*....................................................................... * Discard the contents of the array of possible completion matches. * * Input: * cpl WordCompletion * The word-completion resource object. */ static void cpl_clear_completions(WordCompletion *cpl) { /* * Discard all of the strings. */ _clr_StringGroup(cpl->sg); /* * Record the fact that the array is now empty. */ cpl->result.nmatch = 0; cpl->result.suffix = NULL; cpl->result.cont_suffix = ""; /* * Also clear the error message. */ _err_clear_msg(cpl->err); return; } /*....................................................................... * Given an input line and the point at which it completion is to be * attempted, return an array of possible completions. * * Input: * cpl WordCompletion * The completion resource object. * line char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * data void * Anonymous 'data' to be passed to match_fn(). * match_fn CplMatchFn * The function that will identify the prefix * to be completed from the input line, and * record completion matches. * Output: * return CplMatches * The container of the array of possible * completions. The returned pointer refers * to a container owned by the parent WordCompletion * object, and its contents thus potentially * change on every call to cpl_matches(). * On error, NULL is returned, and a description * of the error can be acquired by calling * cpl_last_error(cpl). */ CplMatches *cpl_complete_word(WordCompletion *cpl, const char *line, int word_end, void *data, CplMatchFn *match_fn) { int line_len; /* The total length of the input line */ /* * How long is the input line? */ line_len = strlen(line); /* * Check the arguments. */ if(!cpl || !line || !match_fn || word_end < 0 || word_end > line_len) { if(cpl) { _err_record_msg(cpl->err, "cpl_complete_word: Invalid arguments.", END_ERR_MSG); }; return NULL; }; /* * Clear the return container. */ cpl_clear_completions(cpl); /* * Have the matching function record possible completion matches in * cpl->result.matches. */ if(match_fn(cpl, data, line, word_end)) { if(_err_get_msg(cpl->err)[0] == '\0') _err_record_msg(cpl->err, "Error completing word.", END_ERR_MSG); return NULL; }; /* * Record a copy of the common initial part of all of the prefixes * in cpl->result.common. */ if(cpl_common_suffix(cpl)) return NULL; /* * Sort the matches into lexicographic order. */ cpl_sort_matches(cpl); /* * Discard any duplicate matches. */ cpl_zap_duplicates(cpl); /* * If there is more than one match, discard the continuation suffix. */ if(cpl->result.nmatch > 1) cpl->result.cont_suffix = ""; /* * Return the array of matches. */ return &cpl->result; } /*....................................................................... * Recall the return value of the last call to cpl_complete_word(). * * Input: * cpl WordCompletion * The completion resource object. * Output: * return CplMatches * The container of the array of possible * completions, as returned by the last call to * cpl_complete_word(). The returned pointer refers * to a container owned by the parent WordCompletion * object, and its contents thus potentially * change on every call to cpl_complete_word(). * On error, either in the execution of this * function, or in the last call to * cpl_complete_word(), NULL is returned, and a * description of the error can be acquired by * calling cpl_last_error(cpl). */ CplMatches *cpl_recall_matches(WordCompletion *cpl) { return (!cpl || *_err_get_msg(cpl->err)!='\0') ? NULL : &cpl->result; } /*....................................................................... * Print out an array of matching completions. * * Input: * result CplMatches * The container of the sorted array of * completions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int cpl_list_completions(CplMatches *result, FILE *fp, int term_width) { return _cpl_output_completions(result, _io_write_stdio, fp, term_width); } /*....................................................................... * Print an array of matching completions via a callback function. * * Input: * result CplMatches * The container of the sorted array of * completions. * write_fn GlWriteFn * The function to call to write the completions, * or 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int _cpl_output_completions(CplMatches *result, GlWriteFn *write_fn, void *data, int term_width) { CplListFormat fmt; /* List formatting information */ int lnum; /* The sequential number of the line to print next */ /* * Not enough space to list anything? */ if(term_width < 1) return 0; /* * Do we have a callback to write via, and any completions to be listed? */ if(write_fn && result && result->nmatch>0) { /* * Work out how to arrange the listing into fixed sized columns. */ cpl_plan_listing(result, term_width, &fmt); /* * Print the listing via the specified callback. */ for(lnum=0; lnum < fmt.nline; lnum++) { if(cpl_format_line(result, &fmt, lnum, write_fn, data)) return 1; }; }; return 0; } /*....................................................................... * Return a description of the string-completion error that occurred. * * Input: * cpl WordCompletion * The string-completion resource object. * Output: * return const char * The description of the last error. */ const char *cpl_last_error(WordCompletion *cpl) { return cpl ? _err_get_msg(cpl->err) : "NULL WordCompletion argument"; } /*....................................................................... * When an error occurs while performing a completion, you registerf a * terse description of the error by calling cpl_record_error(). This * message will then be returned on the next call to cpl_last_error(). * * Input: * cpl WordCompletion * The string-completion resource object that was * originally passed to the callback. * errmsg const char * The description of the error. */ void cpl_record_error(WordCompletion *cpl, const char *errmsg) { if(cpl && errmsg) _err_record_msg(cpl->err, errmsg, END_ERR_MSG); } /*....................................................................... * This is the builtin completion callback function which performs file * completion. * * Input: * cpl WordCompletion * An opaque pointer to the object that will * contain the matches. This should be filled * via zero or more calls to cpl_add_completion(). * data void * Either NULL to request the default * file-completion behavior, or a pointer to a * CplFileConf structure, whose members specify * a different behavior. * line char * The current input line. * word_end int The index of the character in line[] which * follows the end of the token that is being * completed. * Output * return int 0 - OK. * 1 - Error. */ CPL_MATCH_FN(cpl_file_completions) { #ifdef WITHOUT_FILE_SYSTEM return 0; #else const char *start_path; /* The pointer to the start of the pathname */ /* in line[]. */ CplFileConf *conf; /* The new-style configuration object. */ /* * The following configuration object will be used if the caller didn't * provide one. */ CplFileConf default_conf; /* * This function can be called externally, so check its arguments. */ if(!cpl) return 1; if(!line || word_end < 0) { _err_record_msg(cpl->err, "cpl_file_completions: Invalid arguments.", END_ERR_MSG); return 1; }; /* * The 'data' argument is either a CplFileConf pointer, identifiable * by having an integer id code as its first member, or the deprecated * CplFileArgs pointer, or can be NULL to request the default * configuration. */ if(data && *(int *)data == CFC_ID_CODE) { conf = (CplFileConf *) data; } else { /* * Select the defaults. */ conf = &default_conf; cpl_init_FileConf(&default_conf); /* * If we have been passed an instance of the deprecated CplFileArgs * structure, copy its configuration parameters over the defaults. */ if(data) { CplFileArgs *args = (CplFileArgs *) data; conf->escaped = args->escaped; conf->file_start = args->file_start; }; }; /* * Get the start of the filename. If not specified by the caller * identify it by searching backwards in the input line for an * unescaped space or the start of the line. */ if(conf->file_start < 0) { start_path = _pu_start_of_path(line, word_end); if(!start_path) { _err_record_msg(cpl->err, "Unable to find the start of the filename.", END_ERR_MSG); return 1; }; } else { start_path = line + conf->file_start; }; /* * Perform the completion. */ if(_cf_complete_file(cpl, cpl->cf, line, start_path - line, word_end, conf->escaped, conf->chk_fn, conf->chk_data)) { cpl_record_error(cpl, _cf_last_error(cpl->cf)); return 1; }; return 0; #endif } /*....................................................................... * Initialize a CplFileArgs structure with default configuration * parameters. Note that the CplFileArgs configuration type is * deprecated. The opaque CplFileConf object should be used in future * applications. * * Input: * cfa CplFileArgs * The configuration object of the * cpl_file_completions() callback. */ void cpl_init_FileArgs(CplFileArgs *cfa) { if(cfa) { cfa->escaped = 1; cfa->file_start = -1; }; } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * Initialize a CplFileConf structure with default configuration * parameters. * * Input: * cfc CplFileConf * The configuration object of the * cpl_file_completions() callback. */ static void cpl_init_FileConf(CplFileConf *cfc) { if(cfc) { cfc->id = CFC_ID_CODE; cfc->escaped = 1; cfc->file_start = -1; cfc->chk_fn = 0; cfc->chk_data = NULL; }; } #endif /*....................................................................... * Create a new CplFileConf object and initialize it with defaults. * * Output: * return CplFileConf * The new object, or NULL on error. */ CplFileConf *new_CplFileConf(void) { #ifdef WITHOUT_FILE_SYSTEM errno = EINVAL; return NULL; #else CplFileConf *cfc; /* The object to be returned */ /* * Allocate the container. */ cfc = (CplFileConf *)malloc(sizeof(CplFileConf)); if(!cfc) return NULL; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_CplFileConf(). */ cpl_init_FileConf(cfc); return cfc; #endif } /*....................................................................... * Delete a CplFileConf object. * * Input: * cfc CplFileConf * The object to be deleted. * Output: * return CplFileConf * The deleted object (always NULL). */ CplFileConf *del_CplFileConf(CplFileConf *cfc) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) { /* * Delete the container. */ free(cfc); }; #endif return NULL; } /*....................................................................... * If backslashes in the filename should be treated as literal * characters, call the following function with literal=1. Otherwise * the default is to treat them as escape characters, used for escaping * spaces etc.. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * literal int Pass non-zero here to enable literal interpretation * of backslashes. Pass 0 to turn off literal * interpretation. */ void cfc_literal_escapes(CplFileConf *cfc, int literal) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) cfc->escaped = !literal; #endif } /*....................................................................... * Call this function if you know where the index at which the * filename prefix starts in the input line. Otherwise by default, * or if you specify start_index to be -1, the filename is taken * to start after the first unescaped space preceding the cursor, * or the start of the line, which ever comes first. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * start_index int The index of the start of the filename in * the input line, or -1 to select the default. */ void cfc_file_start(CplFileConf *cfc, int start_index) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) cfc->file_start = start_index; #endif } /*....................................................................... * If you only want certain types of files to be included in the * list of completions, you use the following function to specify a * callback function which will be called to ask whether a given file * should be included. * * Input: * cfc CplFileConf * The cpl_file_completions() configuration object * to be configured. * chk_fn CplCheckFn * Zero to disable filtering, or a pointer to a * function that returns 1 if a given file should * be included in the list of completions. * chk_data void * Anonymous data to be passed to chk_fn() * every time that it is called. */ void cfc_set_check_fn(CplFileConf *cfc, CplCheckFn *chk_fn, void *chk_data) { #ifndef WITHOUT_FILE_SYSTEM if(cfc) { cfc->chk_fn = chk_fn; cfc->chk_data = chk_data; }; #endif } /*....................................................................... * The following CplCheckFn callback returns non-zero if the specified * filename is that of an executable. */ CPL_CHECK_FN(cpl_check_exe) { #ifdef WITHOUT_FILE_SYSTEM return 0; #else return _pu_path_is_exe(pathname); #endif } /*....................................................................... * Remove duplicates from a sorted array of matches. * * Input: * cpl WordCompletion * The completion resource object. */ static void cpl_zap_duplicates(WordCompletion *cpl) { CplMatch *matches; /* The array of matches */ int nmatch; /* The number of elements in matches[] */ const char *completion; /* The completion string of the last unique match */ const char *type_suffix; /* The type of the last unique match */ int src; /* The index of the match being considered */ int dst; /* The index at which to record the next */ /* unique match. */ /* * Get the array of matches and the number of matches that it * contains. */ matches = cpl->result.matches; nmatch = cpl->result.nmatch; /* * No matches? */ if(nmatch < 1) return; /* * Initialize the comparison strings with the first match. */ completion = matches[0].completion; type_suffix = matches[0].type_suffix; /* * Go through the array of matches, copying each new unrecorded * match at the head of the array, while discarding duplicates. */ for(src=dst=1; srccompletion) != 0 || strcmp(type_suffix, match->type_suffix) != 0) { if(src != dst) matches[dst] = *match; dst++; completion = match->completion; type_suffix = match->type_suffix; }; }; /* * Record the number of unique matches that remain. */ cpl->result.nmatch = dst; return; } /*....................................................................... * Work out how to arrange a given array of completions into a listing * of one or more fixed size columns. * * Input: * result CplMatches * The set of completions to be listed. * term_width int The width of the terminal. A lower limit of * zero is quietly enforced. * Input/Output: * fmt CplListFormat * The formatting information will be assigned * to the members of *fmt. */ static void cpl_plan_listing(CplMatches *result, int term_width, CplListFormat *fmt) { int maxlen; /* The length of the longest matching string */ int i; /* * Ensure that term_width >= 0. */ if(term_width < 0) term_width = 0; /* * Start by assuming the worst case, that either nothing will fit * on the screen, or that there are no matches to be listed. */ fmt->term_width = term_width; fmt->column_width = 0; fmt->nline = fmt->ncol = 0; /* * Work out the maximum length of the matching strings. */ maxlen = 0; for(i=0; inmatch; i++) { CplMatch *match = result->matches + i; int len = strlen(match->completion) + strlen(match->type_suffix); if(len > maxlen) maxlen = len; }; /* * Nothing to list? */ if(maxlen == 0) return; /* * Split the available terminal width into columns of * maxlen + CPL_COL_SEP characters. */ fmt->column_width = maxlen; fmt->ncol = fmt->term_width / (fmt->column_width + CPL_COL_SEP); /* * If the column width is greater than the terminal width, zero columns * will have been selected. Set a lower limit of one column. Leave it * up to the caller how to deal with completions who's widths exceed * the available terminal width. */ if(fmt->ncol < 1) fmt->ncol = 1; /* * How many lines of output will be needed? */ fmt->nline = (result->nmatch + fmt->ncol - 1) / fmt->ncol; return; } /*....................................................................... * Render one line of a multi-column listing of completions, using a * callback function to pass the output to an arbitrary destination. * * Input: * result CplMatches * The container of the sorted array of * completions. * fmt CplListFormat * Formatting information. * lnum int The index of the line to print, starting * from 0, and incrementing until the return * value indicates that there is nothing more * to be printed. * write_fn GlWriteFn * The function to call to write the line, or * 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * Output: * return int 0 - Line printed ok. * 1 - Nothing to print. */ static int cpl_format_line(CplMatches *result, CplListFormat *fmt, int lnum, GlWriteFn *write_fn, void *data) { int col; /* The index of the list column being output */ /* * If the line index is out of bounds, there is nothing to be written. */ if(lnum < 0 || lnum >= fmt->nline) return 1; /* * If no output function has been provided, return as though the * line had been printed. */ if(!write_fn) return 0; /* * Print the matches in 'ncol' columns, sorted in line order within each * column. */ for(col=0; col < fmt->ncol; col++) { int m = col*fmt->nline + lnum; /* * Is there another match to be written? Note that in general * the last line of a listing will have fewer filled columns * than the initial lines. */ if(m < result->nmatch) { CplMatch *match = result->matches + m; /* * How long are the completion and type-suffix strings? */ int clen = strlen(match->completion); int tlen = strlen(match->type_suffix); /* * Write the completion string. */ if(write_fn(data, match->completion, clen) != clen) return 1; /* * Write the type suffix, if any. */ if(tlen > 0 && write_fn(data, match->type_suffix, tlen) != tlen) return 1; /* * If another column follows the current one, pad to its start with spaces. */ if(col+1 < fmt->ncol) { /* * The following constant string of spaces is used to pad the output. */ static const char spaces[] = " "; static const int nspace = sizeof(spaces) - 1; /* * Pad to the next column, using as few sub-strings of the spaces[] * array as possible. */ int npad = fmt->column_width + CPL_COL_SEP - clen - tlen; while(npad>0) { int n = npad > nspace ? nspace : npad; if(write_fn(data, spaces + nspace - n, n) != n) return 1; npad -= n; }; }; }; }; /* * Start a new line. */ { char s[] = "\r\n"; int n = strlen(s); if(write_fn(data, s, n) != n) return 1; }; return 0; }