443 lines
10 KiB
C
443 lines
10 KiB
C
/* History and file completion functions for editline library.
|
|
*
|
|
* Copyright (c) 1992, 1993 Simmule Turner and Rich Salz
|
|
* All rights reserved.
|
|
*
|
|
* This software is not subject to any license of the American Telephone
|
|
* and Telegraph Company or of the Regents of the University of California.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose on
|
|
* any computer system, and to alter it and redistribute it freely, subject
|
|
* to the following restrictions:
|
|
* 1. The authors are not responsible for the consequences of use of this
|
|
* software, no matter how awful, even if they arise from flaws in it.
|
|
* 2. The origin of this software must not be misrepresented, either by
|
|
* explicit claim or by omission. Since few users ever read sources,
|
|
* credits must appear in the documentation.
|
|
* 3. Altered versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software. Since few users
|
|
* ever read sources, credits must appear in the documentation.
|
|
* 4. This notice may not be removed or altered.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include "editline.h"
|
|
|
|
#define MAX_TOTAL_MATCHES (256 << sizeof(char *))
|
|
|
|
int rl_attempted_completion_over = 0;
|
|
rl_completion_func_t *rl_attempted_completion_function = NULL;
|
|
rl_compentry_func_t *rl_completion_entry_function = NULL;
|
|
|
|
/* Wrap strcmp() for qsort() -- weird construct to pass -Wcast-qual */
|
|
static int compare(const void *p1, const void *p2)
|
|
{
|
|
char *const *v1 = (char *const *)p1;
|
|
char *const *v2 = (char *const *)p2;
|
|
|
|
return strcmp(*v1, *v2);
|
|
}
|
|
|
|
/* Fill in *avp with an array of names that match file, up to its length.
|
|
* Ignore . and .. . */
|
|
static int FindMatches(char *dir, char *file, char ***avp)
|
|
{
|
|
char **av;
|
|
char **word;
|
|
char *p;
|
|
DIR *dp;
|
|
DIRENTRY *ep;
|
|
size_t ac;
|
|
size_t len;
|
|
size_t choices;
|
|
size_t total;
|
|
|
|
if ((dp = opendir(dir)) == NULL)
|
|
return 0;
|
|
|
|
av = NULL;
|
|
ac = 0;
|
|
len = strlen(file);
|
|
choices = 0;
|
|
total = 0;
|
|
while ((ep = readdir(dp)) != NULL) {
|
|
p = ep->d_name;
|
|
if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))
|
|
continue;
|
|
if (len && strncmp(p, file, len) != 0)
|
|
continue;
|
|
|
|
choices++;
|
|
if ((total += strlen(p)) > MAX_TOTAL_MATCHES) {
|
|
/* This is a bit too much. */
|
|
while (ac > 0) free(av[--ac]);
|
|
continue;
|
|
}
|
|
|
|
if ((ac % MEM_INC) == 0) {
|
|
word = malloc(sizeof(char *) * (ac + MEM_INC));
|
|
if (!word) {
|
|
total = 0;
|
|
break;
|
|
}
|
|
|
|
if (ac) {
|
|
memcpy(word, av, ac * sizeof(char *));
|
|
free(av);
|
|
}
|
|
*avp = av = word;
|
|
}
|
|
|
|
if ((av[ac] = strdup(p)) == NULL) {
|
|
if (ac == 0)
|
|
free(av);
|
|
total = 0;
|
|
break;
|
|
}
|
|
ac++;
|
|
}
|
|
|
|
/* Clean up and return. */
|
|
closedir(dp);
|
|
if (total > MAX_TOTAL_MATCHES) {
|
|
char many[sizeof(total) * 3];
|
|
|
|
p = many + sizeof(many);
|
|
*--p = '\0';
|
|
while (choices > 0) {
|
|
*--p = '0' + choices % 10;
|
|
choices /= 10;
|
|
}
|
|
|
|
while (p > many + sizeof(many) - 8)
|
|
*--p = ' ';
|
|
|
|
if ((p = strdup(p)) != NULL)
|
|
av[ac++] = p;
|
|
|
|
if ((p = strdup("choices")) != NULL)
|
|
av[ac++] = p;
|
|
} else {
|
|
if (ac)
|
|
qsort(av, ac, sizeof(char *), compare);
|
|
}
|
|
|
|
return ac;
|
|
}
|
|
|
|
/* Split a pathname into allocated directory and trailing filename parts. */
|
|
static int SplitPath(const char *path, char **dirpart, char **filepart)
|
|
{
|
|
static char DOT[] = ".";
|
|
char *dpart;
|
|
char *fpart;
|
|
|
|
if ((fpart = strrchr(path, '/')) == NULL) {
|
|
if ((dpart = strdup(DOT)) == NULL)
|
|
return -1;
|
|
|
|
if ((fpart = strdup(path)) == NULL) {
|
|
free(dpart);
|
|
return -1;
|
|
}
|
|
} else {
|
|
if ((dpart = strdup(path)) == NULL)
|
|
return -1;
|
|
|
|
dpart[fpart - path + 1] = '\0';
|
|
if ((fpart = strdup(fpart + 1)) == NULL) {
|
|
free(dpart);
|
|
return -1;
|
|
}
|
|
}
|
|
*dirpart = dpart;
|
|
*filepart = fpart;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static rl_complete_func_t *el_complete_func = NULL;
|
|
|
|
/* For compatibility with the Heimdal project. */
|
|
rl_complete_func_t *rl_set_complete_func(rl_complete_func_t *func)
|
|
{
|
|
rl_complete_func_t *old = el_complete_func;
|
|
el_complete_func = func;
|
|
|
|
return old;
|
|
}
|
|
|
|
/* Attempt to complete the pathname, returning an allocated copy.
|
|
* Fill in *match if we completed it, or set it to 0 if ambiguous. */
|
|
char *el_filename_complete(char *pathname, int *match)
|
|
{
|
|
char **av;
|
|
char *dir;
|
|
char *file;
|
|
char *path;
|
|
char *p;
|
|
size_t ac;
|
|
size_t end;
|
|
size_t i;
|
|
size_t j;
|
|
size_t len;
|
|
|
|
if (SplitPath((const char *)pathname, &dir, &file) < 0)
|
|
return NULL;
|
|
|
|
if ((ac = FindMatches(dir, file, &av)) == 0) {
|
|
free(dir);
|
|
free(file);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
p = NULL;
|
|
len = strlen(file);
|
|
if (ac == 1) {
|
|
/* Exactly one match -- finish it off. */
|
|
*match = 1;
|
|
j = strlen(av[0]) - len + 2;
|
|
p = malloc(sizeof(char) * (j + 1));
|
|
if (p) {
|
|
memcpy(p, av[0] + len, j);
|
|
len = strlen(dir) + strlen(av[0]) + 2;
|
|
path = malloc(sizeof(char) * len);
|
|
if (path) {
|
|
snprintf(path, len, "%s/%s", dir, av[0]);
|
|
rl_add_slash(path, p);
|
|
free(path);
|
|
}
|
|
}
|
|
} else {
|
|
*match = 0;
|
|
if (len) {
|
|
/* Find largest matching substring. */
|
|
for (i = len, end = strlen(av[0]); i < end; i++) {
|
|
for (j = 1; j < ac; j++) {
|
|
if (av[0][i] != av[j][i])
|
|
goto breakout;
|
|
}
|
|
}
|
|
breakout:
|
|
if (i > len) {
|
|
j = i - len + 1;
|
|
p = malloc(sizeof(char) * j);
|
|
if (p) {
|
|
memcpy(p, av[0] + len, j);
|
|
p[j - 1] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clean up and return. */
|
|
free(dir);
|
|
free(file);
|
|
for (i = 0; i < ac; i++)
|
|
free(av[i]);
|
|
free(av);
|
|
|
|
return p;
|
|
}
|
|
|
|
char *rl_filename_completion_function(const char *text, int state)
|
|
{
|
|
char *dir;
|
|
char *file;
|
|
static char **av;
|
|
static size_t i, ac;
|
|
|
|
if (!state) {
|
|
if (SplitPath(text, &dir, &file) < 0)
|
|
return NULL;
|
|
|
|
ac = FindMatches(dir, file, &av);
|
|
free(dir);
|
|
free(file);
|
|
if (!ac)
|
|
return NULL;
|
|
|
|
i = 0;
|
|
}
|
|
|
|
if (i < ac)
|
|
return av[i++];
|
|
|
|
do {
|
|
free(av[--i]);
|
|
} while (i > 0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Similar to el_find_word(), but used by GNU Readline API */
|
|
static char *rl_find_token(size_t *len)
|
|
{
|
|
char *ptr;
|
|
int pos;
|
|
|
|
for (pos = rl_point; pos < rl_end; pos++) {
|
|
if (isspace(rl_line_buffer[pos])) {
|
|
if (pos > 0)
|
|
pos--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ptr = &rl_line_buffer[pos];
|
|
while (pos >= 0 && !isspace(rl_line_buffer[pos])) {
|
|
if (pos == 0)
|
|
break;
|
|
|
|
pos--;
|
|
}
|
|
|
|
if (ptr != &rl_line_buffer[pos]) {
|
|
*len = (size_t)(ptr - &rl_line_buffer[pos]);
|
|
return &rl_line_buffer[pos];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* "uses an application-supplied generator function to generate the list
|
|
* of possible matches, and then returns the array of these matches. The
|
|
* caller should place the address of its generator function in
|
|
* rl_completion_entry_function"
|
|
*/
|
|
char **rl_completion_matches(const char *token, rl_compentry_func_t *generator)
|
|
{
|
|
int state = 0, num = 0;
|
|
char **array, *entry;
|
|
|
|
if (!generator) {
|
|
generator = rl_completion_entry_function;
|
|
if (!generator)
|
|
generator = rl_filename_completion_function;
|
|
}
|
|
|
|
if (!generator)
|
|
return NULL;
|
|
|
|
array = malloc(512 * sizeof(char *));
|
|
if (!array)
|
|
return NULL;
|
|
|
|
while (num < 511 && (entry = generator(token, state))) {
|
|
state = 1;
|
|
array[num++] = entry;
|
|
}
|
|
array[num] = NULL;
|
|
|
|
if (!num) {
|
|
free(array);
|
|
return NULL;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
static char *complete(char *token, int *match)
|
|
{
|
|
size_t len = 0;
|
|
char *word, **words = NULL;
|
|
int start, end;
|
|
|
|
word = rl_find_token(&len);
|
|
if (!word)
|
|
goto fallback;
|
|
|
|
start = word - rl_line_buffer;
|
|
end = start + len;
|
|
|
|
word = strndup(word, len);
|
|
if (!word)
|
|
goto fallback;
|
|
|
|
rl_attempted_completion_over = 0;
|
|
words = rl_attempted_completion_function(word, start, end);
|
|
|
|
if (!rl_attempted_completion_over && !words)
|
|
words = rl_completion_matches(word, NULL);
|
|
|
|
if (words) {
|
|
int i = 0;
|
|
|
|
free(word);
|
|
word = NULL;
|
|
if (words[0])
|
|
word = strdup(words[0] + len);
|
|
|
|
while (words[i])
|
|
free(words[i++]);
|
|
free(words);
|
|
|
|
if (word)
|
|
return word;
|
|
}
|
|
|
|
fallback:
|
|
return el_filename_complete(token, match);
|
|
}
|
|
|
|
/*
|
|
* First check for editline specific custom completion function, then
|
|
* for any GNU Readline compat, then fallback to filename completion.
|
|
*/
|
|
char *rl_complete(char *token, int *match)
|
|
{
|
|
if (el_complete_func)
|
|
return el_complete_func(token, match);
|
|
|
|
if (rl_attempted_completion_function)
|
|
return complete(token, match);
|
|
|
|
return el_filename_complete(token, match);
|
|
}
|
|
|
|
static rl_list_possib_func_t *el_list_possib_func = NULL;
|
|
|
|
/* For compatibility with the Heimdal project. */
|
|
rl_list_possib_func_t *rl_set_list_possib_func(rl_list_possib_func_t *func)
|
|
{
|
|
rl_list_possib_func_t *old = el_list_possib_func;
|
|
el_list_possib_func = func;
|
|
return old;
|
|
}
|
|
|
|
/* Default possible completions. */
|
|
int el_filename_list_possib(char *pathname, char ***av)
|
|
{
|
|
char *dir;
|
|
char *file;
|
|
int ac;
|
|
|
|
if (SplitPath(pathname, &dir, &file) < 0)
|
|
return 0;
|
|
|
|
ac = FindMatches(dir, file, av);
|
|
free(dir);
|
|
free(file);
|
|
|
|
return ac;
|
|
}
|
|
|
|
/* Return all possible completions. */
|
|
int rl_list_possib(char *token, char ***av)
|
|
{
|
|
if (el_list_possib_func)
|
|
return el_list_possib_func(token, av);
|
|
|
|
return el_filename_list_possib(token, av);
|
|
}
|
|
|
|
|
|
/**
|
|
* Local Variables:
|
|
* c-file-style: "k&r"
|
|
* c-basic-offset: 4
|
|
* End:
|
|
*/
|