mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-22 20:36:50 +03:00
completion: fixed completion of escaped commands in commandline
Changes: * added new flag: INPUT_COMPLETE_SHELL_ESC - forces filename completion return escaped names * cleaned up src/complete.c: removed global variables in favour of completion flags. Code changed respectively. Signed-off-by: Sergei Trofimovich <slyfox@inbox.ru>
This commit is contained in:
parent
a5e61d285e
commit
60d8c1d615
@ -294,7 +294,8 @@ command_new (int y, int x, int cols)
|
||||
{
|
||||
WInput *cmd;
|
||||
|
||||
cmd = input_new (y, x, DEFAULT_COLOR, cols, "", "cmdline", INPUT_COMPLETE_DEFAULT);
|
||||
cmd = input_new (y, x, DEFAULT_COLOR, cols, "", "cmdline",
|
||||
INPUT_COMPLETE_DEFAULT | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_SHELL_ESC);
|
||||
|
||||
/* Add our hooks */
|
||||
cmd->widget.callback = command_callback;
|
||||
|
160
src/complete.c
160
src/complete.c
@ -45,17 +45,34 @@
|
||||
#include "util.h"
|
||||
#include "key.h" /* XCTRL and ALT macros */
|
||||
|
||||
typedef char *CompletionFunction (char *, int);
|
||||
typedef char *CompletionFunction (char * text, int state, INPUT_COMPLETE_FLAGS flags);
|
||||
|
||||
/* This flag is used in filename_completion_function */
|
||||
static int ignore_filenames = 0;
|
||||
//#define DO_COMPLETION_DEBUG
|
||||
#ifdef DO_COMPLETION_DEBUG
|
||||
/*
|
||||
* Useful to print/debug completion flags
|
||||
*/
|
||||
static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
static char s_cf[] = "FHCVUDS";
|
||||
|
||||
/* This flag is used by command_completion_function */
|
||||
/* to hint the filename_completion_function */
|
||||
static int look_for_executables = 0;
|
||||
s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) ? 'F' : ' ';
|
||||
s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) ? 'H' : ' ';
|
||||
s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) ? 'C' : ' ';
|
||||
s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) ? 'V' : ' ';
|
||||
s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) ? 'U' : ' ';
|
||||
s_cf[5] = (flags & INPUT_COMPLETE_CD) ? 'D' : ' ';
|
||||
s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) ? 'S' : ' ';
|
||||
|
||||
return s_cf;
|
||||
}
|
||||
#define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
|
||||
#else
|
||||
#define SHOW_C_CTX(func)
|
||||
#endif /* DO_CMPLETION_DEBUG */
|
||||
|
||||
static char *
|
||||
filename_completion_function (char *text, int state)
|
||||
filename_completion_function (char *text, int state, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
static DIR *directory;
|
||||
static char *filename = NULL;
|
||||
@ -66,6 +83,8 @@ filename_completion_function (char *text, int state)
|
||||
|
||||
struct dirent *entry = NULL;
|
||||
|
||||
SHOW_C_CTX("filename_completion_function");
|
||||
|
||||
/* If we're starting the match process, initialize us a bit. */
|
||||
if (!state){
|
||||
const char *temp;
|
||||
@ -139,18 +158,15 @@ filename_completion_function (char *text, int state)
|
||||
}
|
||||
g_free (tmp);
|
||||
}
|
||||
switch (look_for_executables)
|
||||
{
|
||||
case 2: if (!isexec)
|
||||
continue;
|
||||
break;
|
||||
case 1: if (!isexec && !isdir)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (ignore_filenames && !isdir)
|
||||
continue;
|
||||
break;
|
||||
if ((flags & INPUT_COMPLETE_COMMANDS)
|
||||
&& (isexec || isdir))
|
||||
break;
|
||||
if ((flags & INPUT_COMPLETE_CD)
|
||||
&& isdir)
|
||||
break;
|
||||
if (flags & (INPUT_COMPLETE_FILENAMES))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry){
|
||||
@ -184,18 +200,27 @@ filename_completion_function (char *text, int state)
|
||||
}
|
||||
if (isdir)
|
||||
strcat (temp, PATH_SEP_STR);
|
||||
return temp;
|
||||
|
||||
if (temp && (flags & INPUT_COMPLETE_SHELL_ESC))
|
||||
{
|
||||
SHELL_ESCAPED_STR e_temp = mhl_shell_escape_dup(temp);
|
||||
mhl_mem_free (temp);
|
||||
temp = e_temp.s;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
/* We assume here that text[0] == '~' , if you want to call it in another way,
|
||||
you have to change the code */
|
||||
static char *
|
||||
username_completion_function (char *text, int state)
|
||||
username_completion_function (char *text, int state, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
static struct passwd *entry;
|
||||
static int userlen;
|
||||
|
||||
SHOW_C_CTX("username_completion_function");
|
||||
|
||||
if (text[0] == '\\' && text[1] == '~') text++;
|
||||
if (!state){ /* Initialization stuff */
|
||||
setpwent ();
|
||||
@ -231,12 +256,14 @@ extern char **environ;
|
||||
/* We assume text [0] == '$' and want to have a look at text [1], if it is
|
||||
equal to '{', so that we should append '}' at the end */
|
||||
static char *
|
||||
variable_completion_function (char *text, int state)
|
||||
variable_completion_function (char *text, int state, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
static char **env_p;
|
||||
static int varlen, isbrace;
|
||||
const char *p = NULL;
|
||||
|
||||
SHOW_C_CTX("variable_completion_function");
|
||||
|
||||
if (!state){ /* Initialization stuff */
|
||||
isbrace = (text [1] == '{');
|
||||
varlen = strlen (text + 1 + isbrace);
|
||||
@ -346,11 +373,13 @@ static void fetch_hosts (const char *filename)
|
||||
}
|
||||
|
||||
static char *
|
||||
hostname_completion_function (char *text, int state)
|
||||
hostname_completion_function (char *text, int state, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
static char **host_p;
|
||||
static int textstart, textlen;
|
||||
|
||||
SHOW_C_CTX("hostname_completion_function");
|
||||
|
||||
if (!state){ /* Initialization stuff */
|
||||
const char *p;
|
||||
|
||||
@ -400,9 +429,8 @@ hostname_completion_function (char *text, int state)
|
||||
* table of shell built-ins.
|
||||
*/
|
||||
static char *
|
||||
command_completion_function (char *text, int state)
|
||||
command_completion_function (char *text, int state, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
text = mhl_shell_unescape_buf(text);
|
||||
static const char *path_end;
|
||||
static int isabsolute;
|
||||
static int phase;
|
||||
@ -427,9 +455,15 @@ command_completion_function (char *text, int state)
|
||||
};
|
||||
char *p, *found;
|
||||
|
||||
SHOW_C_CTX("command_completion_function");
|
||||
|
||||
if (!(flags & INPUT_COMPLETE_COMMANDS))
|
||||
return 0;
|
||||
|
||||
text = mhl_shell_unescape_buf(text);
|
||||
|
||||
if (!state) { /* Initialize us a little bit */
|
||||
isabsolute = strchr (text, PATH_SEP) != 0;
|
||||
look_for_executables = isabsolute ? 1 : 2;
|
||||
if (!isabsolute) {
|
||||
words = bash_reserved;
|
||||
phase = 0;
|
||||
@ -445,16 +479,8 @@ command_completion_function (char *text, int state)
|
||||
}
|
||||
|
||||
if (isabsolute) {
|
||||
p = filename_completion_function (text, state);
|
||||
if (!p)
|
||||
{
|
||||
look_for_executables = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_ESCAPED_STR e_p = mhl_shell_escape_dup(p);
|
||||
mhl_mem_free(p);
|
||||
return e_p.s;
|
||||
p = filename_completion_function (text, state, flags);
|
||||
return p;
|
||||
}
|
||||
|
||||
found = NULL;
|
||||
@ -494,7 +520,7 @@ command_completion_function (char *text, int state)
|
||||
}
|
||||
found =
|
||||
filename_completion_function (cur_word,
|
||||
state - init_state);
|
||||
state - init_state, flags);
|
||||
if (!found) {
|
||||
g_free (cur_word);
|
||||
cur_word = NULL;
|
||||
@ -503,17 +529,16 @@ command_completion_function (char *text, int state)
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
look_for_executables = 0;
|
||||
g_free (path);
|
||||
path = NULL;
|
||||
return NULL;
|
||||
}
|
||||
if ((p = strrchr (found, PATH_SEP)) != NULL) {
|
||||
p++;
|
||||
p = g_strdup (p);
|
||||
g_free (found);
|
||||
/* TODO: shell escape? */
|
||||
return p;
|
||||
|
||||
SHELL_ESCAPED_STR e_p = mhl_shell_escape_dup(p);
|
||||
mhl_mem_free(found);
|
||||
return e_p.s;
|
||||
}
|
||||
return found;
|
||||
|
||||
@ -533,7 +558,7 @@ match_compare (const void *a, const void *b)
|
||||
as the second.
|
||||
In case no matches were found we return NULL. */
|
||||
static char **
|
||||
completion_matches (char *text, CompletionFunction entry_function)
|
||||
completion_matches (char *text, CompletionFunction entry_function, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
/* Number of slots in match_list. */
|
||||
int match_list_size;
|
||||
@ -549,7 +574,7 @@ completion_matches (char *text, CompletionFunction entry_function)
|
||||
|
||||
match_list[1] = NULL;
|
||||
|
||||
while ((string = (*entry_function) (text, matches)) != NULL){
|
||||
while ((string = (*entry_function) (text, matches, flags)) != NULL){
|
||||
if (matches + 1 == match_list_size)
|
||||
match_list = (char **) g_realloc (match_list, ((match_list_size += 30) + 1) * sizeof (char *));
|
||||
match_list[++matches] = string;
|
||||
@ -613,10 +638,8 @@ check_is_cd (const char *text, int start, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
const char *p, *q;
|
||||
|
||||
if (flags & INPUT_COMPLETE_CD)
|
||||
return 1;
|
||||
|
||||
if (!(flags & INPUT_COMPLETE_COMMANDS))
|
||||
SHOW_C_CTX("check_is_cd");
|
||||
if (!(flags & INPUT_COMPLETE_CD))
|
||||
return 0;
|
||||
|
||||
/* Skip initial spaces */
|
||||
@ -635,7 +658,7 @@ check_is_cd (const char *text, int start, INPUT_COMPLETE_FLAGS flags)
|
||||
|
||||
/* Returns an array of matches, or NULL if none. */
|
||||
static char **
|
||||
try_complete (char *text, int *start, int *end, int flags)
|
||||
try_complete (char *text, int *start, int *end, INPUT_COMPLETE_FLAGS flags)
|
||||
{
|
||||
int in_command_position = 0;
|
||||
char *word, c;
|
||||
@ -644,8 +667,8 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
char *p = NULL, *q = NULL, *r = NULL;
|
||||
int is_cd = check_is_cd (text, *start, flags);
|
||||
|
||||
SHOW_C_CTX("try_complete");
|
||||
|
||||
ignore_filenames = 0;
|
||||
c = text [*end];
|
||||
text [*end] = 0;
|
||||
word = g_strdup (text + *start);
|
||||
@ -660,7 +683,7 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
for (i = *start - 1; i > -1; i--) {
|
||||
if (text[i] == ' ' || text[i] == '\t'){
|
||||
if (i == 0 ) continue;
|
||||
if (text[i-1] == '\\') {
|
||||
if (text[i-1] != '\\') {
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
@ -702,14 +725,16 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
|
||||
/* Command substitution? */
|
||||
if (p > q && p > r){
|
||||
matches = completion_matches (p + 1, command_completion_function);
|
||||
SHOW_C_CTX("try_complete:cmd_backq_subst");
|
||||
matches = completion_matches (p + 1, command_completion_function, flags & (~INPUT_COMPLETE_FILENAMES));
|
||||
if (matches)
|
||||
*start += p + 1 - word;
|
||||
}
|
||||
|
||||
/* Variable name? */
|
||||
else if (q > p && q > r){
|
||||
matches = completion_matches (q, variable_completion_function);
|
||||
SHOW_C_CTX("try_complete:var_subst");
|
||||
matches = completion_matches (q, variable_completion_function, flags);
|
||||
if (matches)
|
||||
*start += q - word;
|
||||
}
|
||||
@ -717,7 +742,8 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
/* Starts with '@', then look through the known hostnames for
|
||||
completion first. */
|
||||
else if (r > p && r > q){
|
||||
matches = completion_matches (r, hostname_completion_function);
|
||||
SHOW_C_CTX("try_complete:host_subst");
|
||||
matches = completion_matches (r, hostname_completion_function, flags);
|
||||
if (matches)
|
||||
*start += r - word;
|
||||
}
|
||||
@ -725,20 +751,26 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
/* Starts with `~' and there is no slash in the word, then
|
||||
try completing this word as a username. */
|
||||
if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
|
||||
matches = completion_matches (word, username_completion_function);
|
||||
{
|
||||
SHOW_C_CTX("try_complete:user_subst");
|
||||
matches = completion_matches (word, username_completion_function, flags);
|
||||
}
|
||||
|
||||
|
||||
/* And finally if this word is in a command position, then
|
||||
complete over possible command names, including aliases, functions,
|
||||
and command names. */
|
||||
if (!matches && in_command_position)
|
||||
matches = completion_matches (word, command_completion_function);
|
||||
{
|
||||
SHOW_C_CTX("try_complete:cmd_subst");
|
||||
matches = completion_matches (word, command_completion_function, flags & (~INPUT_COMPLETE_FILENAMES));
|
||||
}
|
||||
|
||||
else if (!matches && (flags & INPUT_COMPLETE_FILENAMES)){
|
||||
if (is_cd)
|
||||
ignore_filenames = 1;
|
||||
matches = completion_matches (word, filename_completion_function);
|
||||
ignore_filenames = 0;
|
||||
if (is_cd)
|
||||
flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
|
||||
SHOW_C_CTX("try_complete:filename_subst_1");
|
||||
matches = completion_matches (word, filename_completion_function, flags);
|
||||
if (!matches && is_cd && *word != PATH_SEP && *word != '~'){
|
||||
char *p, *q = text + *start;
|
||||
|
||||
@ -776,10 +808,9 @@ try_complete (char *text, int *start, int *end, int flags)
|
||||
*s = 0;
|
||||
if (*cdpath){
|
||||
r = concat_dir_and_file (cdpath, word);
|
||||
ignore_filenames = 1;
|
||||
matches = completion_matches (r, filename_completion_function);
|
||||
ignore_filenames = 0;
|
||||
g_free (r);
|
||||
SHOW_C_CTX("try_complete:filename_subst_2");
|
||||
matches = completion_matches (r, filename_completion_function, flags);
|
||||
g_free (r);
|
||||
}
|
||||
*s = c;
|
||||
cdpath = s + 1;
|
||||
@ -1037,7 +1068,6 @@ complete_engine (WInput *in, int what_to_do)
|
||||
return 0;
|
||||
}
|
||||
|
||||
//void complete (WInput *in, COMPLETION_STYLE style)
|
||||
void complete (WInput *in)
|
||||
{
|
||||
int engine_flags;
|
||||
|
@ -12,6 +12,7 @@ typedef enum {
|
||||
INPUT_COMPLETE_VARIABLES = 1<<3,
|
||||
INPUT_COMPLETE_USERNAMES = 1<<4,
|
||||
INPUT_COMPLETE_CD = 1<<5,
|
||||
INPUT_COMPLETE_SHELL_ESC = 1<<6,
|
||||
|
||||
INPUT_COMPLETE_DEFAULT = INPUT_COMPLETE_FILENAMES
|
||||
| INPUT_COMPLETE_HOSTNAMES
|
||||
|
Loading…
Reference in New Issue
Block a user