Skip to content

Commit b8be384

Browse files
authored
More thoughtful completion (#126)
* More thoughtful completion A variety of improvements to bash_kernel's completion, all of which try to more semantically match what the user (read: @kdm9) expects when completing from a given position. In particular: - not completing command names while in something that looks like a path (e.g. `./path/ap<tab>` shouldn't complete the command named `apt`, only the path named `./path/apple.txt`). - Splitting completion prompts at `=` and `"`, so that one can e.g. complete the path in `variable="./path<tab>"`. As with all code that tries to be smart, this will likely have a few undesirable corner cases. Please report any issues you encounter! * tokenise around stream redirection
1 parent cdaed43 commit b8be384

File tree

1 file changed

+32
-11
lines changed

1 file changed

+32
-11
lines changed

bash_kernel/kernel.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -249,29 +249,50 @@ def do_complete(self, code, cursor_pos):
249249
'cursor_end': cursor_pos, 'metadata': dict(),
250250
'status': 'ok'}
251251

252-
if not code or code[-1] == ' ':
253-
return default
254-
255-
tokens = code.replace(';', ' ').split()
256-
if not tokens:
257-
return default
258252

259253
matches = []
254+
# The regex below might cause issues, but is designed to allow
255+
# completion on the rhs of a variable assignment and within strings,
256+
# like var="/etc/<tab>", which should complete from /etc/.
257+
# Let's just hope no one makes a habit of puting =/"/' into file names
258+
# </naievity> (blame @kdm9 if it breaks)
259+
tokens = re.split("[\t \n;=\"'><]+", code)
260260
token = tokens[-1]
261261
start = cursor_pos - len(token)
262-
263-
if token[0] == '$':
262+
if token and token[0] == '$':
264263
# complete variables
265264
cmd = 'compgen -A arrayvar -A export -A variable %s' % token[1:] # strip leading $
266265
output = self.bashwrapper.run_command(cmd).rstrip()
267266
completions = set(output.split())
268267
# append matches including leading $
269268
matches.extend(['$'+c for c in completions])
270269
else:
271-
# complete functions and builtins
272-
cmd = 'compgen -cdfa %s' % token
270+
# complete path
271+
cmd = 'compgen -d -S / %s' % token
272+
output = self.bashwrapper.run_command(cmd).rstrip()
273+
dirs = list(set(output.split()))
274+
cmd = 'compgen -f %s' % token
275+
output = self.bashwrapper.run_command(cmd).rstrip()
276+
filesanddirs = list(set(output.split()))
277+
files = [x for x in filesanddirs if x + "/" not in dirs]
278+
if '/' not in token:
279+
# Add an explict ./ for relative paths
280+
matches.extend(["./" + x for x in files + dirs])
281+
else:
282+
matches.extend(files)
283+
matches.extend(dirs)
284+
if '/' not in token and code[-1] != '"':
285+
# complete anything command-like (avoid annoying errors where command names get completed after a directory)
286+
cmd = 'compgen -abc -A function %s' % token
273287
output = self.bashwrapper.run_command(cmd).rstrip()
274-
matches.extend(output.split())
288+
matches.extend(list(set(output.split())))
289+
if code[-1] == '"':
290+
# complete variables
291+
cmd = 'compgen -A arrayvar -A export -A variable %s' % token[1:] # strip leading $
292+
output = self.bashwrapper.run_command(cmd).rstrip()
293+
completions = set(output.split())
294+
# append matches including leading $
295+
matches.extend(['$'+c for c in completions])
275296

276297
if not matches:
277298
return default

0 commit comments

Comments
 (0)