Skip to content

Commit 06400b8

Browse files
Support for command wrapping ("aliases")
Add the --wraps option to 'complete' and 'function'. This allows a command to (recursively) inherit the completions of a wrapped command. Fixes fish-shell#393. When evaluating a completion, we inspect the entire "wrap chain" for a command, i.e. we follow the sequence of wrapping until we either hit a loop (which we silently ignore) or the end of the chain. We then evaluate completions as if the wrapping command were substituted with the wrapped command. Currently this only works for commands, i.e. 'complete --command gco --wraps git\ checkout' won't work (that would seem to encroaching on abbreviations anyways). It might be useful to show an error message for that case. The commandline builtin reflects the commandline with the wrapped command substituted in, so e.g. git completions (which inspect the command line) will just work. This sort of command line munging is also performed by 'complete -C' so it's not totally without precedent. 'alias will also now mark its generated function as wrapping the 'target.
1 parent fe68d30 commit 06400b8

10 files changed

+317
-36
lines changed

builtin.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,8 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
18091809
bool shadows = true;
18101810

18111811
woptind=0;
1812+
1813+
wcstring_list_t wrap_targets;
18121814

18131815
const struct woption long_options[] =
18141816
{
@@ -1818,6 +1820,7 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
18181820
{ L"on-process-exit", required_argument, 0, 'p' },
18191821
{ L"on-variable", required_argument, 0, 'v' },
18201822
{ L"on-event", required_argument, 0, 'e' },
1823+
{ L"wraps", required_argument, 0, 'w' },
18211824
{ L"help", no_argument, 0, 'h' },
18221825
{ L"argument-names", no_argument, 0, 'a' },
18231826
{ L"no-scope-shadowing", no_argument, 0, 'S' },
@@ -1979,6 +1982,10 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
19791982
case 'S':
19801983
shadows = 0;
19811984
break;
1985+
1986+
case 'w':
1987+
wrap_targets.push_back(woptarg);
1988+
break;
19821989

19831990
case 'h':
19841991
builtin_print_help(parser, argv[0], stdout_buffer);
@@ -2086,6 +2093,12 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
20862093
d.definition = contents.c_str();
20872094

20882095
function_add(d, parser, definition_line_offset);
2096+
2097+
// Handle wrap targets
2098+
for (size_t w=0; w < wrap_targets.size(); w++)
2099+
{
2100+
complete_add_wrapper(name, wrap_targets.at(w));
2101+
}
20892102
}
20902103

20912104
return res;

builtin.h

+19-6
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,25 @@ void builtin_pop_io(parser_t &parser);
164164
wcstring builtin_get_desc(const wcstring &b);
165165

166166

167-
/**
168-
Slightly kludgy function used with 'complete -C' in order to make
169-
the commandline builtin operate on the string to complete instead
170-
of operating on whatever is to be completed.
171-
*/
172-
const wchar_t *builtin_complete_get_temporary_buffer();
167+
168+
/** Support for setting and removing transient command lines.
169+
This is used by 'complete -C' in order to make
170+
the commandline builtin operate on the string to complete instead
171+
of operating on whatever is to be completed. It's also used by
172+
completion wrappers, to allow a command to appear as the command
173+
being wrapped for the purposes of completion.
174+
175+
Instantiating an instance of builtin_commandline_scoped_transient_t
176+
pushes the command as the new transient commandline. The destructor removes it.
177+
It will assert if construction/destruction does not happen in a stack-like (LIFO) order.
178+
*/
179+
class builtin_commandline_scoped_transient_t
180+
{
181+
size_t token;
182+
public:
183+
builtin_commandline_scoped_transient_t(const wcstring &cmd);
184+
~builtin_commandline_scoped_transient_t();
185+
};
173186

174187

175188
/**

builtin_commandline.cpp

+53-4
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,51 @@ static size_t get_cursor_pos()
7979
return current_cursor_pos;
8080
}
8181

82+
static pthread_mutex_t transient_commandline_lock = PTHREAD_MUTEX_INITIALIZER;
83+
static wcstring_list_t *get_transient_stack()
84+
{
85+
ASSERT_IS_MAIN_THREAD();
86+
ASSERT_IS_LOCKED(transient_commandline_lock);
87+
// A pointer is a little more efficient than an object as a static because we can elide the thread-safe initialization
88+
static wcstring_list_t *result = NULL;
89+
if (! result)
90+
{
91+
result = new wcstring_list_t();
92+
}
93+
return result;
94+
}
95+
96+
static bool get_top_transient(wcstring *out_result)
97+
{
98+
ASSERT_IS_MAIN_THREAD();
99+
bool result = false;
100+
scoped_lock locker(transient_commandline_lock);
101+
const wcstring_list_t *stack = get_transient_stack();
102+
if (! stack->empty())
103+
{
104+
out_result->assign(stack->back());
105+
result = true;
106+
}
107+
return result;
108+
}
109+
110+
builtin_commandline_scoped_transient_t::builtin_commandline_scoped_transient_t(const wcstring &cmd)
111+
{
112+
ASSERT_IS_MAIN_THREAD();
113+
scoped_lock locker(transient_commandline_lock);
114+
wcstring_list_t *stack = get_transient_stack();
115+
stack->push_back(cmd);
116+
this->token = stack->size();
117+
}
118+
119+
builtin_commandline_scoped_transient_t::~builtin_commandline_scoped_transient_t()
120+
{
121+
ASSERT_IS_MAIN_THREAD();
122+
scoped_lock locker(transient_commandline_lock);
123+
wcstring_list_t *stack = get_transient_stack();
124+
assert(this->token == stack->size());
125+
stack->pop_back();
126+
}
82127

83128
/**
84129
Replace/append/insert the selection with/at/after the specified string.
@@ -216,11 +261,15 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
216261
int search_mode = 0;
217262
int paging_mode = 0;
218263
const wchar_t *begin = NULL, *end = NULL;
219-
220-
current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
221-
if (current_buffer)
264+
265+
scoped_push<const wchar_t *> saved_current_buffer(&current_buffer);
266+
scoped_push<size_t> saved_current_cursor_pos(&current_cursor_pos);
267+
268+
wcstring transient_commandline;
269+
if (get_top_transient(&transient_commandline))
222270
{
223-
current_cursor_pos = wcslen(current_buffer);
271+
current_buffer = transient_commandline.c_str();
272+
current_cursor_pos = transient_commandline.size();
224273
}
225274
else
226275
{

builtin_complete.cpp

+23-21
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ Functions used for implementing the complete builtin.
2424
#include "parser.h"
2525
#include "reader.h"
2626

27-
28-
/**
29-
Internal storage for the builtin_complete_get_temporary_buffer() function.
30-
*/
31-
static const wchar_t *temporary_buffer;
32-
3327
/*
3428
builtin_complete_* are a set of rather silly looping functions that
3529
make sure that all the proper combinations of complete_add or
@@ -270,13 +264,6 @@ static void builtin_complete_remove(const wcstring_list_t &cmd,
270264

271265
}
272266

273-
274-
const wchar_t *builtin_complete_get_temporary_buffer()
275-
{
276-
ASSERT_IS_MAIN_THREAD();
277-
return temporary_buffer;
278-
}
279-
280267
/**
281268
The complete builtin. Used for specifying programmable
282269
tab-completions. Calls the functions in complete.c for any heavy
@@ -300,6 +287,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
300287

301288
wcstring_list_t cmd;
302289
wcstring_list_t path;
290+
wcstring_list_t wrap_targets;
303291

304292
static int recursion_level=0;
305293

@@ -326,6 +314,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
326314
{ L"unauthoritative", no_argument, 0, 'u' },
327315
{ L"authoritative", no_argument, 0, 'A' },
328316
{ L"condition", required_argument, 0, 'n' },
317+
{ L"wraps", required_argument, 0, 'w' },
329318
{ L"do-complete", optional_argument, 0, 'C' },
330319
{ L"help", no_argument, 0, 'h' },
331320
{ 0, 0, 0, 0 }
@@ -422,6 +411,10 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
422411
case 'n':
423412
condition = woptarg;
424413
break;
414+
415+
case 'w':
416+
wrap_targets.push_back(woptarg);
417+
break;
425418

426419
case 'C':
427420
do_complete = true;
@@ -495,9 +488,9 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
495488
const wchar_t *token;
496489

497490
parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0);
498-
499-
const wchar_t *prev_temporary_buffer = temporary_buffer;
500-
temporary_buffer = do_complete_param.c_str();
491+
492+
/* Create a scoped transient command line, so that bulitin_commandline will see our argument, not the reader buffer */
493+
builtin_commandline_scoped_transient_t temp_buffer(do_complete_param);
501494

502495
if (recursion_level < 1)
503496
{
@@ -536,9 +529,6 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
536529

537530
recursion_level--;
538531
}
539-
540-
temporary_buffer = prev_temporary_buffer;
541-
542532
}
543533
else if (woptind != argc)
544534
{
@@ -558,14 +548,15 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
558548
else
559549
{
560550
int flags = COMPLETE_AUTO_SPACE;
561-
551+
562552
if (remove)
563553
{
564554
builtin_complete_remove(cmd,
565555
path,
566556
short_opt.c_str(),
567557
gnu_opt,
568558
old_opt);
559+
569560
}
570561
else
571562
{
@@ -581,7 +572,18 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
581572
desc,
582573
flags);
583574
}
584-
575+
576+
// Handle wrap targets (probably empty)
577+
// We only wrap commands, not paths
578+
for (size_t w=0; w < wrap_targets.size(); w++)
579+
{
580+
const wcstring &wrap_target = wrap_targets.at(w);
581+
for (size_t i=0; i < cmd.size(); i++)
582+
{
583+
584+
(remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), wrap_target);
585+
}
586+
}
585587
}
586588
}
587589

0 commit comments

Comments
 (0)