OILS / core / completion.py View on Github | oilshell.org

1502 lines, 801 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9completion.py - Tab completion.
10
11Architecture:
12
13Completion should run in threads? For two reasons:
14
15- Completion can be slow -- e.g. completion for distributed resources
16- Because readline has a weird interface, and then you can implement
17 "iterators" in C++ or oil. They just push onto a PIPE. Use a netstring
18 protocol and self-pipe?
19- completion can be in another process anyway?
20
21Does that mean the user code gets run in an entirely separate interpreter? The
22whole lexer/parser/cmd_eval combo has to be thread-safe. Does it get a copy of
23the same startup state?
24
25Features TODO:
26 - complete flags after alias expansion
27 - complete history expansions like zsh
28 - complete flags for all builtins, using frontend/args.py?
29 - might need a special error token
30
31bash note: most of this stuff is in pcomplete.c and bashline.c (4K lines!).
32Uses ITEMLIST with a bunch of flags.
33"""
34from __future__ import print_function
35
36import time as time_
37
38from _devbuild.gen.id_kind_asdl import Id
39from _devbuild.gen.syntax_asdl import (CompoundWord, word_part_e, word_t,
40 redir_param_e, Token)
41from _devbuild.gen.runtime_asdl import (scope_e, comp_action_e, comp_action_t)
42from _devbuild.gen.types_asdl import redir_arg_type_e
43from _devbuild.gen.value_asdl import (value, value_e)
44from core import error
45from core import pyos
46from core import state
47from core import ui
48from core import util
49from frontend import consts
50from frontend import lexer
51from frontend import location
52from frontend import reader
53from mycpp import mylib
54from mycpp.mylib import print_stderr, iteritems, log
55from osh.string_ops import ShellQuoteB
56from osh import word_
57from pylib import os_path
58from pylib import path_stat
59
60import libc
61import posix_ as posix
62from posix_ import X_OK # translated directly to C macro
63
64from typing import (Dict, Tuple, List, Iterator, Optional, Any, cast,
65 TYPE_CHECKING)
66if TYPE_CHECKING:
67 from core.comp_ui import State
68 from core.state import Mem
69 from frontend.py_readline import Readline
70 from core.util import _DebugFile
71 from frontend.parse_lib import ParseContext
72 from osh.cmd_eval import CommandEvaluator
73 from osh.split import SplitContext
74 from osh.word_eval import AbstractWordEvaluator
75
76# To quote completion candidates.
77# ! is for history expansion, which only happens interactively, but
78# completion only does too.
79# *?[] are for globs
80# {} are for brace expansion
81# ~ in filenames should be quoted
82#
83# TODO: Also escape tabs as \t and newlines at \n?
84# SHELL_META_CHARS = r' ~`!$&|;()\"*?[]{}<>' + "'"
85
86
87class _RetryCompletion(Exception):
88 """For the 'exit 124' protocol."""
89
90 def __init__(self):
91 # type: () -> None
92 pass
93
94
95# mycpp: rewrite of multiple-assignment
96# Character types
97CH_Break = 0
98CH_Other = 1
99
100# mycpp: rewrite of multiple-assignment
101# States
102ST_Begin = 0
103ST_Break = 1
104ST_Other = 2
105
106
107# State machine definition.
108# (state, char) -> (new state, emit span)
109# NOT: This would be less verbose as a dict, but a C++ compiler will turn this
110# into a lookup table anyway.
111def _TRANSITIONS(state, ch):
112 # type: (int, int) -> Tuple[int, bool]
113 if state == ST_Begin and ch == CH_Break:
114 return (ST_Break, False)
115
116 if state == ST_Begin and ch == CH_Other:
117 return (ST_Other, False)
118
119 if state == ST_Break and ch == CH_Break:
120 return (ST_Break, False)
121
122 if state == ST_Break and ch == CH_Other:
123 return (ST_Other, True)
124
125 if state == ST_Other and ch == CH_Break:
126 return (ST_Break, True)
127
128 if state == ST_Other and ch == CH_Other:
129 return (ST_Other, False)
130
131 raise ValueError("invalid (state, ch) pair")
132
133
134def AdjustArg(arg, break_chars, argv_out):
135 # type: (str, List[str], List[str]) -> None
136 # stores the end of each span
137 end_indices = [] # type: List[int]
138 state = ST_Begin
139 for i, c in enumerate(arg):
140 ch = CH_Break if c in break_chars else CH_Other
141 state, emit_span = _TRANSITIONS(state, ch)
142 if emit_span:
143 end_indices.append(i)
144
145 # Always emit a span at the end (even for empty string)
146 end_indices.append(len(arg))
147
148 begin = 0
149 for end in end_indices:
150 argv_out.append(arg[begin:end])
151 begin = end
152
153
154# NOTE: How to create temporary options? With copy.deepcopy()?
155# We might want that as a test for OVM. Copying is similar to garbage
156# collection in that you walk a graph.
157
158# These values should never be mutated.
159_DEFAULT_OPTS = {} # type: Dict[str, bool]
160
161
162class OptionState(object):
163 """Stores the compopt state of the CURRENT completion."""
164
165 def __init__(self):
166 # type: () -> None
167 # For the IN-PROGRESS completion.
168 self.currently_completing = False
169 # should be SET to a COPY of the registration options by the completer.
170 self.dynamic_opts = None # type: Dict[str, bool]
171
172
173class ctx_Completing(object):
174
175 def __init__(self, compopt_state):
176 # type: (OptionState) -> None
177 compopt_state.currently_completing = True
178 self.compopt_state = compopt_state
179
180 def __enter__(self):
181 # type: () -> None
182 pass
183
184 def __exit__(self, type, value, traceback):
185 # type: (Any, Any, Any) -> None
186 self.compopt_state.currently_completing = False
187
188
189def _PrintOpts(opts, f):
190 # type: (Dict[str, bool], mylib.BufWriter) -> None
191 f.write(' (')
192 for k, v in iteritems(opts):
193 f.write(' %s=%s' % (k, '1' if v else '0'))
194 f.write(' )\n')
195
196
197class Lookup(object):
198 """Stores completion hooks registered by the user."""
199
200 def __init__(self):
201 # type: () -> None
202
203 # Pseudo-commands __first and __fallback are for -E and -D.
204 empty_spec = UserSpec([], [], [], DefaultPredicate(), '', '')
205 do_nothing = (_DEFAULT_OPTS, empty_spec)
206 self.lookup = {
207 '__fallback': do_nothing,
208 '__first': do_nothing,
209 } # type: Dict[str, Tuple[Dict[str, bool], UserSpec]]
210
211 # for the 124 protocol
212 self.commands_with_spec_changes = [] # type: List[str]
213
214 # So you can register *.sh, unlike bash. List of (glob, [actions]),
215 # searched linearly.
216 self.patterns = [] # type: List[Tuple[str, Dict[str, bool], UserSpec]]
217
218 def __str__(self):
219 # type: () -> str
220 return '<completion.Lookup %s>' % self.lookup
221
222 def PrintSpecs(self):
223 # type: () -> None
224 """ For complete -p """
225
226 # TODO: This format could be nicer / round-trippable?
227
228 f = mylib.BufWriter()
229
230 f.write('[Commands]\n')
231 for name in sorted(self.lookup):
232 base_opts, user_spec = self.lookup[name]
233
234 f.write('%s:\n' % name)
235 _PrintOpts(base_opts, f)
236
237 user_spec.PrintSpec(f)
238
239 f.write('[Patterns]\n')
240 for pat, base_opts, spec in self.patterns:
241 #print('%s %s %s' % (pat, base_opts, spec))
242 f.write('%s:\n' % pat)
243 _PrintOpts(base_opts, f)
244
245 user_spec.PrintSpec(f)
246
247 # Print to stderr since it's not parse-able
248 print_stderr(f.getvalue())
249
250 def ClearCommandsChanged(self):
251 # type: () -> None
252 del self.commands_with_spec_changes[:]
253
254 def GetCommandsChanged(self):
255 # type: () -> List[str]
256 return self.commands_with_spec_changes
257
258 def RegisterName(self, name, base_opts, user_spec):
259 # type: (str, Dict[str, bool], UserSpec) -> None
260 """Register a completion action with a name.
261
262 Used by the 'complete' builtin.
263 """
264 self.lookup[name] = (base_opts, user_spec)
265
266 if name not in ('__fallback', '__first'):
267 self.commands_with_spec_changes.append(name)
268
269 def RegisterGlob(self, glob_pat, base_opts, user_spec):
270 # type: (str, Dict[str, bool], UserSpec) -> None
271 self.patterns.append((glob_pat, base_opts, user_spec))
272
273 def GetSpecForName(self, argv0):
274 # type: (str) -> Tuple[Dict[str, bool], UserSpec]
275 """
276 Args:
277 argv0: A finished argv0 to lookup
278 """
279 pair = self.lookup.get(argv0) # NOTE: Could be ''
280 if pair:
281 # mycpp: rewrite of tuple return
282 a, b = pair
283 return (a, b)
284
285 key = os_path.basename(argv0)
286 pair = self.lookup.get(key)
287 if pair:
288 # mycpp: rewrite of tuple return
289 a, b = pair
290 return (a, b)
291
292 for glob_pat, base_opts, user_spec in self.patterns:
293 #log('Matching %r %r', key, glob_pat)
294 if libc.fnmatch(glob_pat, key):
295 return base_opts, user_spec
296
297 return None, None
298
299 def GetFirstSpec(self):
300 # type: () -> Tuple[Dict[str, bool], UserSpec]
301 # mycpp: rewrite of tuple return
302 a, b = self.lookup['__first']
303 return (a, b)
304
305 def GetFallback(self):
306 # type: () -> Tuple[Dict[str, bool], UserSpec]
307 # mycpp: rewrite of tuple return
308 a, b = self.lookup['__fallback']
309 return (a, b)
310
311
312class Api(object):
313
314 def __init__(self, line, begin, end):
315 # type: (str, int, int) -> None
316 """
317 Args:
318 index: if -1, then we're running through compgen
319 """
320 self.line = line
321 self.begin = begin
322 self.end = end
323 self.first = None # type: str
324 self.to_complete = None # type: str
325 self.prev = None # type: str
326 self.index = -1 # type: int
327 self.partial_argv = [] # type: List[str]
328 # NOTE: COMP_WORDBREAKS is initialized in Mem().
329
330 # NOTE: to_complete could be 'cur'
331 def Update(self, first, to_complete, prev, index, partial_argv):
332 # type: (str, str, str, int, List[str]) -> None
333 """Added after we've done parsing."""
334 self.first = first
335 self.to_complete = to_complete
336 self.prev = prev
337 self.index = index # COMP_CWORD
338 # COMP_ARGV and COMP_WORDS can be derived from this
339 self.partial_argv = partial_argv
340 if self.partial_argv is None:
341 self.partial_argv = []
342
343 def __repr__(self):
344 # type: () -> str
345 """For testing."""
346 return '<Api %r %d-%d>' % (self.line, self.begin, self.end)
347
348
349#
350# Actions
351#
352
353
354class CompletionAction(object):
355
356 def __init__(self):
357 # type: () -> None
358 pass
359
360 def Matches(self, comp):
361 # type: (Api) -> Iterator[str]
362 pass
363
364 def ActionKind(self):
365 # type: () -> comp_action_t
366 return comp_action_e.Other
367
368 def Print(self, f):
369 # type: (mylib.BufWriter) -> None
370 f.write('???CompletionAction ')
371
372 def __repr__(self):
373 # type: () -> str
374 return self.__class__.__name__
375
376
377class UsersAction(CompletionAction):
378 """complete -A user."""
379
380 def __init__(self):
381 # type: () -> None
382 pass
383
384 def Matches(self, comp):
385 # type: (Api) -> Iterator[str]
386 for u in pyos.GetAllUsers():
387 name = u.pw_name
388 if name.startswith(comp.to_complete):
389 yield name
390
391 def Print(self, f):
392 # type: (mylib.BufWriter) -> None
393 f.write('UserAction ')
394
395
396class TestAction(CompletionAction):
397
398 def __init__(self, words, delay=0.0):
399 # type: (List[str], Optional[float]) -> None
400 self.words = words
401 self.delay = delay
402
403 def Matches(self, comp):
404 # type: (Api) -> Iterator[str]
405 for w in self.words:
406 if w.startswith(comp.to_complete):
407 if self.delay != 0.0:
408 time_.sleep(self.delay)
409 yield w
410
411 def Print(self, f):
412 # type: (mylib.BufWriter) -> None
413 f.write('TestAction ')
414
415
416class DynamicWordsAction(CompletionAction):
417 """compgen -W '$(echo one two three)'."""
418
419 def __init__(
420 self,
421 word_ev, # type: AbstractWordEvaluator
422 splitter, # type: SplitContext
423 arg_word, # type: CompoundWord
424 errfmt, # type: ui.ErrorFormatter
425 ):
426 # type: (...) -> None
427 self.word_ev = word_ev
428 self.splitter = splitter
429 self.arg_word = arg_word
430 self.errfmt = errfmt
431
432 def Matches(self, comp):
433 # type: (Api) -> Iterator[str]
434 try:
435 val = self.word_ev.EvalWordToString(self.arg_word)
436 except error.FatalRuntime as e:
437 self.errfmt.PrettyPrintError(e)
438 raise
439
440 # SplitForWordEval() Allows \ escapes
441 candidates = self.splitter.SplitForWordEval(val.s)
442 for c in candidates:
443 if c.startswith(comp.to_complete):
444 yield c
445
446 def Print(self, f):
447 # type: (mylib.BufWriter) -> None
448 f.write('DynamicWordsAction ')
449
450
451class FileSystemAction(CompletionAction):
452 """Complete paths from the file system.
453
454 Directories will have a / suffix.
455 """
456
457 def __init__(self, dirs_only, exec_only, add_slash):
458 # type: (bool, bool, bool) -> None
459 self.dirs_only = dirs_only
460 self.exec_only = exec_only
461
462 # This is for redirects, not for UserSpec, which should respect compopt -o
463 # filenames.
464 self.add_slash = add_slash # for directories
465
466 def ActionKind(self):
467 # type: () -> comp_action_t
468 return comp_action_e.FileSystem
469
470 def Print(self, f):
471 # type: (mylib.BufWriter) -> None
472 f.write('FileSystemAction ')
473
474 def Matches(self, comp):
475 # type: (Api) -> Iterator[str]
476 to_complete = comp.to_complete
477
478 # Problem: .. and ../.. don't complete /.
479 # TODO: Set display_pos before fixing this.
480
481 #import os
482 #to_complete = os.path.normpath(to_complete)
483
484 dirname, basename = os_path.split(to_complete)
485 if dirname == '': # We're completing in this directory
486 to_list = '.'
487 else: # We're completing in some other directory
488 to_list = dirname
489
490 if 0:
491 log('basename %r' % basename)
492 log('to_list %r' % to_list)
493 log('dirname %r' % dirname)
494
495 try:
496 names = posix.listdir(to_list)
497 except (IOError, OSError) as e:
498 return # nothing
499
500 for name in names:
501 path = os_path.join(dirname, name)
502
503 if path.startswith(to_complete):
504 if self.dirs_only: # add_slash not used here
505 # NOTE: There is a duplicate isdir() check later to add a trailing
506 # slash. Consolidate the checks for fewer stat() ops. This is hard
507 # because all the completion actions must obey the same interface.
508 # We could have another type like candidate = File | Dir |
509 # OtherString ?
510 if path_stat.isdir(path):
511 yield path
512 continue
513
514 if self.exec_only:
515 # TODO: Handle exception if file gets deleted in between listing and
516 # check?
517 if not posix.access(path, X_OK):
518 continue
519
520 if self.add_slash and path_stat.isdir(path):
521 path = path + '/'
522 yield path
523 else:
524 yield path
525
526
527class CommandAction(CompletionAction):
528 """ TODO: Implement complete -C """
529
530 def __init__(self, cmd_ev, command_name):
531 # type: (CommandEvaluator, str) -> None
532 self.cmd_ev = cmd_ev
533 self.command_name = command_name
534
535 def Matches(self, comp):
536 # type: (Api) -> Iterator[str]
537 for candidate in ['TODO-complete-C']:
538 yield candidate
539
540
541class ShellFuncAction(CompletionAction):
542 """Call a user-defined function using bash's completion protocol."""
543
544 def __init__(self, cmd_ev, func, comp_lookup):
545 # type: (CommandEvaluator, value.Proc, Lookup) -> None
546 """
547 Args:
548 comp_lookup: For the 124 protocol: test if the user-defined function
549 registered a new UserSpec.
550 """
551 self.cmd_ev = cmd_ev
552 self.func = func
553 self.comp_lookup = comp_lookup
554
555 def Print(self, f):
556 # type: (mylib.BufWriter) -> None
557
558 f.write('[ShellFuncAction %s] ' % self.func.name)
559
560 def ActionKind(self):
561 # type: () -> comp_action_t
562 return comp_action_e.BashFunc
563
564 def debug(self, msg):
565 # type: (str) -> None
566 self.cmd_ev.debug_f.writeln(msg)
567
568 def Matches(self, comp):
569 # type: (Api) -> Iterator[str]
570
571 # Have to clear the response every time. TODO: Reuse the object?
572 state.SetGlobalArray(self.cmd_ev.mem, 'COMPREPLY', [])
573
574 # New completions should use COMP_ARGV, a construct specific to OSH>
575 state.SetGlobalArray(self.cmd_ev.mem, 'COMP_ARGV', comp.partial_argv)
576
577 # Old completions may use COMP_WORDS. It is split by : and = to emulate
578 # bash's behavior.
579 # More commonly, they will call _init_completion and use the 'words' output
580 # of that, ignoring COMP_WORDS.
581 comp_words = [] # type: List[str]
582 for a in comp.partial_argv:
583 AdjustArg(a, [':', '='], comp_words)
584 if comp.index == -1: # compgen
585 comp_cword = comp.index
586 else:
587 comp_cword = len(comp_words) - 1 # weird invariant
588
589 state.SetGlobalArray(self.cmd_ev.mem, 'COMP_WORDS', comp_words)
590 state.SetGlobalString(self.cmd_ev.mem, 'COMP_CWORD', str(comp_cword))
591 state.SetGlobalString(self.cmd_ev.mem, 'COMP_LINE', comp.line)
592 state.SetGlobalString(self.cmd_ev.mem, 'COMP_POINT', str(comp.end))
593
594 argv = [comp.first, comp.to_complete, comp.prev]
595 # TODO: log the arguments
596 self.debug('Running completion function %r with %d arguments' %
597 (self.func.name, len(argv)))
598
599 self.comp_lookup.ClearCommandsChanged()
600 status = self.cmd_ev.RunFuncForCompletion(self.func, argv)
601 commands_changed = self.comp_lookup.GetCommandsChanged()
602
603 self.debug('comp.first %r, commands_changed: %s' %
604 (comp.first, ', '.join(commands_changed)))
605
606 if status == 124:
607 cmd = os_path.basename(comp.first)
608 if cmd in commands_changed:
609 #self.debug('Got status 124 from %r and %s commands changed' % (self.func.name, commands_changed))
610 raise _RetryCompletion()
611 else:
612 # This happens with my own completion scripts. bash doesn't show an
613 # error.
614 self.debug(
615 "Function %r returned 124, but the completion spec for %r wasn't "
616 "changed" % (self.func.name, cmd))
617 return
618
619 # Read the response. (The name 'COMP_REPLY' would be more consistent with others.)
620 val = self.cmd_ev.mem.GetValue('COMPREPLY', scope_e.GlobalOnly)
621
622 if val.tag() == value_e.Undef:
623 # We set it above, so this error would only happen if the user unset it.
624 # Not changing it means there were no completions.
625 # TODO: This writes over the command line; it would be better to use an
626 # error object.
627 print_stderr('osh error: Ran function %r but COMPREPLY was unset' %
628 self.func.name)
629 return
630
631 if val.tag() != value_e.BashArray:
632 print_stderr('osh error: COMPREPLY should be an array, got %s' %
633 ui.ValType(val))
634 return
635
636 if 0:
637 self.debug('> %r' % val) # CRASHES in C++
638
639 array_val = cast(value.BashArray, val)
640 for s in array_val.strs:
641 #self.debug('> %r' % s)
642 yield s
643
644
645class VariablesAction(CompletionAction):
646 """compgen -A variable."""
647
648 def __init__(self, mem):
649 # type: (Mem) -> None
650 self.mem = mem
651
652 def Matches(self, comp):
653 # type: (Api) -> Iterator[str]
654 for var_name in self.mem.VarNames():
655 yield var_name
656
657 def Print(self, f):
658 # type: (mylib.BufWriter) -> None
659
660 f.write('VariablesAction ')
661
662
663class ExternalCommandAction(CompletionAction):
664 """Complete commands in $PATH.
665
666 This is PART of compgen -A command.
667 """
668
669 def __init__(self, mem):
670 # type: (Mem) -> None
671 """
672 Args:
673 mem: for looking up Path
674 """
675 self.mem = mem
676 # Should we list everything executable in $PATH here? And then whenever
677 # $PATH is changed, regenerated it?
678 # Or we can cache directory listings? What if the contents of the dir
679 # changed?
680 # Can we look at the dir timestamp?
681 #
682 # (dir, timestamp) -> list of entries perhaps? And then every time you hit
683 # tab, do you have to check the timestamp? It should be cached by the
684 # kernel, so yes.
685 # XXX(unused?) self.ext = []
686
687 # (dir, timestamp) -> list
688 # NOTE: This cache assumes that listing a directory is slower than statting
689 # it to get the mtime. That may not be true on all systems? Either way
690 # you are reading blocks of metadata. But I guess /bin on many systems is
691 # huge, and will require lots of sys calls.
692 self.cache = {} # type: Dict[Tuple[str, int], List[str]]
693
694 def Print(self, f):
695 # type: (mylib.BufWriter) -> None
696
697 f.write('ExternalCommandAction ')
698
699 def Matches(self, comp):
700 # type: (Api) -> Iterator[str]
701 """TODO: Cache is never cleared.
702
703 - When we get a newer timestamp, we should clear the old one.
704 - When PATH is changed, we can remove old entries.
705 """
706 val = self.mem.GetValue('PATH')
707 if val.tag() != value_e.Str:
708 # No matches if not a string
709 return
710
711 val_s = cast(value.Str, val)
712 path_dirs = val_s.s.split(':')
713 #log('path: %s', path_dirs)
714
715 executables = [] # type: List[str]
716 for d in path_dirs:
717 try:
718 key = pyos.MakeDirCacheKey(d)
719 except (IOError, OSError) as e:
720 # There could be a directory that doesn't exist in the $PATH.
721 continue
722
723 dir_exes = self.cache.get(key)
724 if dir_exes is None:
725 entries = posix.listdir(d)
726 dir_exes = []
727 for name in entries:
728 path = os_path.join(d, name)
729 # TODO: Handle exception if file gets deleted in between listing and
730 # check?
731 if not posix.access(path, X_OK):
732 continue
733 dir_exes.append(name) # append the name, not the path
734
735 self.cache[key] = dir_exes
736
737 executables.extend(dir_exes)
738
739 # TODO: Shouldn't do the prefix / space thing ourselves. readline does
740 # that at the END of the line.
741 for word in executables:
742 if word.startswith(comp.to_complete):
743 yield word
744
745
746class _Predicate(object):
747
748 def __init__(self):
749 # type: () -> None
750 pass
751
752 def Evaluate(self, candidate):
753 # type: (str) -> bool
754 raise NotImplementedError()
755
756 def Print(self, f):
757 # type: (mylib.BufWriter) -> None
758
759 f.write('???Predicate ')
760
761
762class DefaultPredicate(_Predicate):
763
764 def __init__(self):
765 # type: () -> None
766 pass
767
768 def Evaluate(self, candidate):
769 # type: (str) -> bool
770 return True
771
772 def Print(self, f):
773 # type: (mylib.BufWriter) -> None
774
775 f.write('DefaultPredicate ')
776
777
778class GlobPredicate(_Predicate):
779 """Expand into files that match a pattern. !*.py filters them.
780
781 Weird syntax:
782 -X *.py or -X !*.py
783
784 Also & is a placeholder for the string being completed?. Yeah I probably
785 want to get rid of this feature.
786 """
787
788 def __init__(self, include, glob_pat):
789 # type: (bool, str) -> None
790 self.include = include # True for inclusion, False for exclusion
791 self.glob_pat = glob_pat # extended glob syntax supported
792
793 def Evaluate(self, candidate):
794 # type: (str) -> bool
795 """Should we INCLUDE the candidate or not?"""
796 matched = libc.fnmatch(self.glob_pat, candidate)
797 # This is confusing because of bash's double-negative syntax
798 if self.include:
799 return not matched
800 else:
801 return matched
802
803 def __repr__(self):
804 # type: () -> str
805 return '<GlobPredicate %s %r>' % (self.include, self.glob_pat)
806
807 def Print(self, f):
808 # type: (mylib.BufWriter) -> None
809 f.write('GlobPredicate ')
810
811
812class UserSpec(object):
813 """Completion config for a set of commands (or complete -D -E)
814
815 - The compgen builtin exposes this DIRECTLY.
816 - Readline must call ReadlineCallback, which uses RootCompleter.
817 """
818
819 def __init__(
820 self,
821 actions, # type: List[CompletionAction]
822 extra_actions, # type: List[CompletionAction]
823 else_actions, # type: List[CompletionAction]
824 predicate, # type: _Predicate
825 prefix, # type: str
826 suffix, # type: str
827 ):
828 # type: (...) -> None
829 self.actions = actions
830 self.extra_actions = extra_actions
831 self.else_actions = else_actions
832 self.predicate = predicate # for -X
833 self.prefix = prefix
834 self.suffix = suffix
835
836 def PrintSpec(self, f):
837 # type: (mylib.BufWriter) -> None
838 """ Print with indentation of 2 """
839 f.write(' actions: ')
840 for a in self.actions:
841 a.Print(f)
842 f.write('\n')
843
844 f.write(' extra: ')
845 for a in self.extra_actions:
846 a.Print(f)
847 f.write('\n')
848
849 f.write(' else: ')
850 for a in self.else_actions:
851 a.Print(f)
852 f.write('\n')
853
854 f.write(' predicate: ')
855 self.predicate.Print(f)
856 f.write('\n')
857
858 f.write(' prefix: %s\n' % self.prefix)
859 f.write(' suffix: %s\n' % self.prefix)
860
861 def AllMatches(self, comp):
862 # type: (Api) -> Iterator[Tuple[str, comp_action_t]]
863 """yield completion candidates."""
864 num_matches = 0
865
866 for a in self.actions:
867 action_kind = a.ActionKind()
868 for match in a.Matches(comp):
869 # Special case hack to match bash for compgen -F. It doesn't filter by
870 # to_complete!
871 show = (
872 self.predicate.Evaluate(match) and
873 # ShellFuncAction results are NOT filtered by prefix!
874 (match.startswith(comp.to_complete) or
875 action_kind == comp_action_e.BashFunc))
876
877 # There are two kinds of filters: changing the string, and filtering
878 # the set of strings. So maybe have modifiers AND filters? A triple.
879 if show:
880 yield self.prefix + match + self.suffix, action_kind
881 num_matches += 1
882
883 # NOTE: extra_actions and else_actions don't respect -X, -P or -S, and we
884 # don't have to filter by startswith(comp.to_complete). They are all all
885 # FileSystemActions, which do it already.
886
887 # for -o plusdirs
888 for a in self.extra_actions:
889 for match in a.Matches(comp):
890 # We know plusdirs is a file system action
891 yield match, comp_action_e.FileSystem
892
893 # for -o default and -o dirnames
894 if num_matches == 0:
895 for a in self.else_actions:
896 for match in a.Matches(comp):
897 # both are FileSystemAction
898 yield match, comp_action_e.FileSystem
899
900 # What if the cursor is not at the end of line? See readline interface.
901 # That's OK -- we just truncate the line at the cursor?
902 # Hm actually zsh does something smarter, and which is probably preferable.
903 # It completes the word that
904
905
906# Helpers for Matches()
907def IsDollar(t):
908 # type: (Token) -> bool
909
910 # We have rules for Lit_Dollar in
911 # lex_mode_e.{ShCommand,DQ,VSub_ArgUnquoted,VSub_ArgDQ}
912 return t.id == Id.Lit_Dollar
913
914
915def IsDummy(t):
916 # type: (Token) -> bool
917 return t.id == Id.Lit_CompDummy
918
919
920def WordEndsWithCompDummy(w):
921 # type: (CompoundWord) -> bool
922 last_part = w.parts[-1]
923 UP_part = last_part
924 if last_part.tag() == word_part_e.Literal:
925 last_part = cast(Token, UP_part)
926 return last_part.id == Id.Lit_CompDummy
927 else:
928 return False
929
930
931class RootCompleter(object):
932 """Dispatch to various completers.
933
934 - Complete the OSH language (variables, etc.), or
935 - Statically evaluate argv and dispatch to a command completer.
936 """
937
938 def __init__(
939 self,
940 word_ev, # type: AbstractWordEvaluator
941 mem, # type: Mem
942 comp_lookup, # type: Lookup
943 compopt_state, # type: OptionState
944 comp_ui_state, # type: State
945 parse_ctx, # type: ParseContext
946 debug_f, # type: _DebugFile
947 ):
948 # type: (...) -> None
949 self.word_ev = word_ev # for static evaluation of words
950 self.mem = mem # to complete variable names
951 self.comp_lookup = comp_lookup
952 self.compopt_state = compopt_state # for compopt builtin
953 self.comp_ui_state = comp_ui_state
954
955 self.parse_ctx = parse_ctx
956 self.debug_f = debug_f
957
958 def Matches(self, comp):
959 # type: (Api) -> Iterator[str]
960 """
961 Args:
962 comp: Callback args from readline. Readline uses
963 set_completer_delims to tokenize the string.
964
965 Returns a list of matches relative to readline's completion_delims.
966 We have to post-process the output of various completers.
967 """
968 # Pass the original line "out of band" to the completion callback.
969 line_until_tab = comp.line[:comp.end]
970 self.comp_ui_state.line_until_tab = line_until_tab
971
972 self.parse_ctx.trail.Clear()
973 line_reader = reader.StringLineReader(line_until_tab,
974 self.parse_ctx.arena)
975 c_parser = self.parse_ctx.MakeOshParser(line_reader,
976 emit_comp_dummy=True)
977
978 # We want the output from parse_ctx, so we don't use the return value.
979 try:
980 c_parser.ParseLogicalLine()
981 except error.Parse as e:
982 # e.g. 'ls | ' will not parse. Now inspect the parser state!
983 pass
984
985 debug_f = self.debug_f
986 trail = self.parse_ctx.trail
987 if mylib.PYTHON:
988 trail.PrintDebugString(debug_f)
989
990 #
991 # First try completing the shell language itself.
992 #
993
994 # NOTE: We get Eof_Real in the command state, but not in the middle of a
995 # BracedVarSub. This is due to the difference between the CommandParser
996 # and WordParser.
997 tokens = trail.tokens
998 last = -1
999 if tokens[-1].id == Id.Eof_Real:
1000 last -= 1 # ignore it
1001
1002 try:
1003 t1 = tokens[last]
1004 except IndexError:
1005 t1 = None
1006 try:
1007 t2 = tokens[last - 1]
1008 except IndexError:
1009 t2 = None
1010
1011 debug_f.writeln('line: %r' % comp.line)
1012 debug_f.writeln('rl_slice from byte %d to %d: %r' %
1013 (comp.begin, comp.end, comp.line[comp.begin:comp.end]))
1014
1015 # Note: this logging crashes C++ because of type mismatch
1016 if t1:
1017 #debug_f.writeln('t1 %s' % t1)
1018 pass
1019
1020 if t2:
1021 #debug_f.writeln('t2 %s' % t2)
1022 pass
1023
1024 #debug_f.writeln('tokens %s', tokens)
1025
1026 # Each of the 'yield' statements below returns a fully-completed line, to
1027 # appease the readline library. The root cause of this dance: If there's
1028 # one candidate, readline is responsible for redrawing the input line. OSH
1029 # only displays candidates and never redraws the input line.
1030
1031 if t2: # We always have t1?
1032 # echo $
1033 if IsDollar(t2) and IsDummy(t1):
1034 self.comp_ui_state.display_pos = t2.col + 1 # 1 for $
1035 for name in self.mem.VarNames():
1036 yield line_until_tab + name # no need to quote var names
1037 return
1038
1039 # echo ${
1040 if t2.id == Id.Left_DollarBrace and IsDummy(t1):
1041 self.comp_ui_state.display_pos = t2.col + 2 # 2 for ${
1042 for name in self.mem.VarNames():
1043 # no need to quote var names
1044 yield line_until_tab + name
1045 return
1046
1047 # echo $P
1048 if t2.id == Id.VSub_DollarName and IsDummy(t1):
1049 # Example: ${undef:-$P
1050 # readline splits at ':' so we have to prepend '-$' to every completed
1051 # variable name.
1052 self.comp_ui_state.display_pos = t2.col + 1 # 1 for $
1053 to_complete = t2.tval[1:]
1054 n = len(to_complete)
1055 for name in self.mem.VarNames():
1056 if name.startswith(to_complete):
1057 # no need to quote var names
1058 yield line_until_tab + name[n:]
1059 return
1060
1061 # echo ${P
1062 if t2.id == Id.VSub_Name and IsDummy(t1):
1063 self.comp_ui_state.display_pos = t2.col # no offset
1064 to_complete = t2.tval
1065 n = len(to_complete)
1066 for name in self.mem.VarNames():
1067 if name.startswith(to_complete):
1068 # no need to quote var names
1069 yield line_until_tab + name[n:]
1070 return
1071
1072 # echo $(( VAR
1073 if t2.id == Id.Lit_ArithVarLike and IsDummy(t1):
1074 self.comp_ui_state.display_pos = t2.col # no offset
1075 to_complete = t2.tval
1076 n = len(to_complete)
1077 for name in self.mem.VarNames():
1078 if name.startswith(to_complete):
1079 # no need to quote var names
1080 yield line_until_tab + name[n:]
1081 return
1082
1083 if len(trail.words) > 0:
1084 # echo ~<TAB>
1085 # echo ~a<TAB> $(home dirs)
1086 # This must be done at a word level, and TildeDetectAll() does NOT help
1087 # here, because they don't have trailing slashes yet! We can't do it on
1088 # tokens, because otherwise f~a will complete. Looking at word_part is
1089 # EXACTLY what we want.
1090 parts = trail.words[-1].parts
1091 if len(parts) > 0 and word_.LiteralId(parts[0]) == Id.Lit_Tilde:
1092 #log('TILDE parts %s', parts)
1093
1094 if (len(parts) == 2 and
1095 word_.LiteralId(parts[1]) == Id.Lit_CompDummy):
1096 tilde_tok = cast(Token, parts[0])
1097
1098 # end of tilde
1099 self.comp_ui_state.display_pos = tilde_tok.col + 1
1100
1101 to_complete = ''
1102 for u in pyos.GetAllUsers():
1103 name = u.pw_name
1104 s = line_until_tab + ShellQuoteB(name) + '/'
1105 yield s
1106 return
1107
1108 if (len(parts) == 3 and
1109 word_.LiteralId(parts[1]) == Id.Lit_Chars and
1110 word_.LiteralId(parts[2]) == Id.Lit_CompDummy):
1111
1112 chars_tok = cast(Token, parts[1])
1113
1114 self.comp_ui_state.display_pos = chars_tok.col
1115
1116 to_complete = lexer.TokenVal(chars_tok)
1117 n = len(to_complete)
1118 for u in pyos.GetAllUsers(): # catch errors?
1119 name = u.pw_name
1120 if name.startswith(to_complete):
1121 s = line_until_tab + ShellQuoteB(name[n:]) + '/'
1122 yield s
1123 return
1124
1125 # echo hi > f<TAB> (complete redirect arg)
1126 if len(trail.redirects) > 0:
1127 r = trail.redirects[-1]
1128 # Only complete 'echo >', but not 'echo >&' or 'cat <<'
1129 # TODO: Don't complete <<< 'h'
1130 if (r.arg.tag() == redir_param_e.Word and
1131 consts.RedirArgType(r.op.id) == redir_arg_type_e.Path):
1132 arg_word = r.arg
1133 UP_word = arg_word
1134 arg_word = cast(CompoundWord, UP_word)
1135 if WordEndsWithCompDummy(arg_word):
1136 debug_f.writeln('Completing redirect arg')
1137
1138 try:
1139 val = self.word_ev.EvalWordToString(arg_word)
1140 except error.FatalRuntime as e:
1141 debug_f.writeln('Error evaluating redirect word: %s' %
1142 e)
1143 return
1144 if val.tag() != value_e.Str:
1145 debug_f.writeln("Didn't get a string from redir arg")
1146 return
1147
1148 tok = location.LeftTokenForWord(arg_word)
1149 self.comp_ui_state.display_pos = tok.col
1150
1151 comp.Update('', val.s, '', 0, [])
1152 n = len(val.s)
1153 action = FileSystemAction(False, False, True)
1154 for name in action.Matches(comp):
1155 yield line_until_tab + ShellQuoteB(name[n:])
1156 return
1157
1158 #
1159 # We're not completing the shell language. Delegate to user-defined
1160 # completion for external tools.
1161 #
1162
1163 # Set below, and set on retries.
1164 base_opts = None # type: Dict[str, bool]
1165 user_spec = None # type: Optional[UserSpec]
1166
1167 # Used on retries.
1168 partial_argv = [] # type: List[str]
1169 num_partial = -1
1170 first = None # type: str
1171
1172 if len(trail.words) > 0:
1173 # Now check if we're completing a word!
1174 if WordEndsWithCompDummy(trail.words[-1]):
1175 debug_f.writeln('Completing words')
1176 #
1177 # It didn't look like we need to complete var names, tilde, redirects,
1178 # etc. Now try partial_argv, which may involve invoking PLUGINS.
1179
1180 # needed to complete paths with ~
1181 # mycpp: workaround list cast
1182 trail_words = [cast(word_t, w) for w in trail.words]
1183 words2 = word_.TildeDetectAll(trail_words)
1184 if 0:
1185 debug_f.writeln('After tilde detection')
1186 for w in words2:
1187 print(w, file=debug_f)
1188
1189 if 0:
1190 debug_f.writeln('words2:')
1191 for w2 in words2:
1192 debug_f.writeln(' %s' % w2)
1193
1194 for w in words2:
1195 try:
1196 # TODO:
1197 # - Should we call EvalWordSequence? But turn globbing off? It
1198 # can do splitting and such.
1199 # - We could have a variant to eval TildeSub to ~ ?
1200 val = self.word_ev.EvalWordToString(w)
1201 except error.FatalRuntime:
1202 # Why would it fail?
1203 continue
1204 if val.tag() == value_e.Str:
1205 partial_argv.append(val.s)
1206 else:
1207 pass
1208
1209 debug_f.writeln('partial_argv: [%s]' % ','.join(partial_argv))
1210 num_partial = len(partial_argv)
1211
1212 first = partial_argv[0]
1213 alias_first = None # type: str
1214 if mylib.PYTHON:
1215 debug_f.writeln('alias_words: [%s]' % trail.alias_words)
1216
1217 if len(trail.alias_words) > 0:
1218 w = trail.alias_words[0]
1219 try:
1220 val = self.word_ev.EvalWordToString(w)
1221 except error.FatalRuntime:
1222 pass
1223 alias_first = val.s
1224 debug_f.writeln('alias_first: %s' % alias_first)
1225
1226 if num_partial == 0: # should never happen because of Lit_CompDummy
1227 raise AssertionError()
1228 elif num_partial == 1:
1229 base_opts, user_spec = self.comp_lookup.GetFirstSpec()
1230
1231 # Display/replace since the beginning of the first word. Note: this
1232 # is non-zero in the case of
1233 # echo $(gr and
1234 # echo `gr
1235
1236 tok = location.LeftTokenForWord(trail.words[0])
1237 self.comp_ui_state.display_pos = tok.col
1238 self.debug_f.writeln('** DISPLAY_POS = %d' %
1239 self.comp_ui_state.display_pos)
1240
1241 else:
1242 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1243 first)
1244 if not user_spec and alias_first is not None:
1245 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1246 alias_first)
1247 if user_spec:
1248 # Pass the aliased command to the user-defined function, and use
1249 # it for retries.
1250 first = alias_first
1251 if not user_spec:
1252 base_opts, user_spec = self.comp_lookup.GetFallback()
1253
1254 # Display since the beginning
1255 tok = location.LeftTokenForWord(trail.words[-1])
1256 self.comp_ui_state.display_pos = tok.col
1257 if mylib.PYTHON:
1258 self.debug_f.writeln('words[-1]: [%s]' %
1259 trail.words[-1])
1260
1261 self.debug_f.writeln('display_pos %d' %
1262 self.comp_ui_state.display_pos)
1263
1264 # Update the API for user-defined functions.
1265 index = len(
1266 partial_argv) - 1 # COMP_CWORD is -1 when it's empty
1267 prev = '' if index == 0 else partial_argv[index - 1]
1268 comp.Update(first, partial_argv[-1], prev, index, partial_argv)
1269
1270 # This happens in the case of [[ and ((, or a syntax error like 'echo < >'.
1271 if not user_spec:
1272 debug_f.writeln("Didn't find anything to complete")
1273 return
1274
1275 # Reset it back to what was registered. User-defined functions can mutate
1276 # it.
1277 dynamic_opts = {} # type: Dict[str, bool]
1278 self.compopt_state.dynamic_opts = dynamic_opts
1279 with ctx_Completing(self.compopt_state):
1280 done = False
1281 while not done:
1282 done = True # exhausted candidates without getting a retry
1283 try:
1284 for candidate in self._PostProcess(base_opts, dynamic_opts,
1285 user_spec, comp):
1286 yield candidate
1287 except _RetryCompletion as e:
1288 debug_f.writeln('Got 124, trying again ...')
1289 done = False
1290
1291 # Get another user_spec. The ShellFuncAction may have 'sourced' code
1292 # and run 'complete' to mutate comp_lookup, and we want to get that
1293 # new entry.
1294 if num_partial == 0:
1295 raise AssertionError()
1296 elif num_partial == 1:
1297 base_opts, user_spec = self.comp_lookup.GetFirstSpec()
1298 else:
1299 # (already processed alias_first)
1300 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1301 first)
1302 if not user_spec:
1303 base_opts, user_spec = self.comp_lookup.GetFallback(
1304 )
1305
1306 def _PostProcess(
1307 self,
1308 base_opts, # type: Dict[str, bool]
1309 dynamic_opts, # type: Dict[str, bool]
1310 user_spec, # type: UserSpec
1311 comp, # type: Api
1312 ):
1313 # type: (...) -> Iterator[str]
1314 """Add trailing spaces / slashes to completion candidates, and time
1315 them.
1316
1317 NOTE: This post-processing MUST go here, and not in UserSpec, because
1318 it's in READLINE in bash. compgen doesn't see it.
1319 """
1320 self.debug_f.writeln('Completing %r ... (Ctrl-C to cancel)' %
1321 comp.line)
1322 start_time = time_.time()
1323
1324 # TODO: dedupe candidates? You can get two 'echo' in bash, which is dumb.
1325
1326 i = 0
1327 for candidate, action_kind in user_spec.AllMatches(comp):
1328 # SUBTLE: dynamic_opts is part of compopt_state, which ShellFuncAction
1329 # can mutate! So we don't want to pull this out of the loop.
1330 #
1331 # TODO: The candidates from each actions shouldn't be flattened.
1332 # for action in user_spec.Actions():
1333 # if action.IsFileSystem(): # this returns is_dir too
1334 #
1335 # action.Run() # might set dynamic opts
1336 # opt_nospace = base_opts...
1337 # if 'nospace' in dynamic_opts:
1338 # opt_nosspace = dynamic_opts['nospace']
1339 # for candidate in action.Matches():
1340 # add space or /
1341 # and do escaping too
1342 #
1343 # Or maybe you can request them on demand? Most actions are EAGER.
1344 # While the ShellacAction is LAZY? And you should be able to cancel it!
1345
1346 # NOTE: User-defined plugins (and the -P flag) can REWRITE what the user
1347 # already typed. So
1348 #
1349 # $ echo 'dir with spaces'/f<TAB>
1350 #
1351 # can be rewritten to:
1352 #
1353 # $ echo dir\ with\ spaces/foo
1354 line_until_tab = self.comp_ui_state.line_until_tab
1355 line_until_word = line_until_tab[:self.comp_ui_state.display_pos]
1356
1357 opt_filenames = base_opts.get('filenames', False)
1358 if 'filenames' in dynamic_opts:
1359 opt_filenames = dynamic_opts['filenames']
1360
1361 # compopt -o filenames is for user-defined actions. Or any
1362 # FileSystemAction needs it.
1363 if action_kind == comp_action_e.FileSystem or opt_filenames:
1364 if path_stat.isdir(candidate):
1365 s = line_until_word + ShellQuoteB(candidate) + '/'
1366 yield s
1367 continue
1368
1369 opt_nospace = base_opts.get('nospace', False)
1370 if 'nospace' in dynamic_opts:
1371 opt_nospace = dynamic_opts['nospace']
1372
1373 sp = '' if opt_nospace else ' '
1374 cand = (candidate if action_kind == comp_action_e.BashFunc else
1375 ShellQuoteB(candidate))
1376
1377 yield line_until_word + cand + sp
1378
1379 # NOTE: Can't use %.2f in production build!
1380 i += 1
1381 elapsed_ms = (time_.time() - start_time) * 1000.0
1382 plural = '' if i == 1 else 'es'
1383
1384 # TODO: Show this in the UI if it takes too long!
1385 if 0:
1386 self.debug_f.writeln(
1387 '... %d match%s for %r in %d ms (Ctrl-C to cancel)' %
1388 (i, plural, comp.line, elapsed_ms))
1389
1390 elapsed_ms = (time_.time() - start_time) * 1000.0
1391 plural = '' if i == 1 else 'es'
1392 self.debug_f.writeln('Found %d match%s for %r in %d ms' %
1393 (i, plural, comp.line, elapsed_ms))
1394
1395
1396class ReadlineCallback(object):
1397 """A callable we pass to the readline module."""
1398
1399 def __init__(self, readline, root_comp, debug_f):
1400 # type: (Optional[Readline], RootCompleter, util._DebugFile) -> None
1401 self.readline = readline
1402 self.root_comp = root_comp
1403 self.debug_f = debug_f
1404
1405 # current completion being processed
1406 if mylib.PYTHON:
1407 self.comp_iter = None # type: Iterator[str]
1408 else:
1409 self.comp_matches = None # type: List[str]
1410
1411 def _GetNextCompletion(self, state):
1412 # type: (int) -> Optional[str]
1413 if state == 0:
1414 # TODO: Tokenize it according to our language. If this is $PS2, we also
1415 # need previous lines! Could make a VirtualLineReader instead of
1416 # StringLineReader?
1417 buf = self.readline.get_line_buffer()
1418
1419 # Readline parses "words" using characters provided by
1420 # set_completer_delims().
1421 # We have our own notion of words. So let's call this a 'rl_slice'.
1422 begin = self.readline.get_begidx()
1423 end = self.readline.get_endidx()
1424
1425 comp = Api(line=buf, begin=begin, end=end)
1426 self.debug_f.writeln('Api %r %d %d' % (buf, begin, end))
1427
1428 if mylib.PYTHON:
1429 self.comp_iter = self.root_comp.Matches(comp)
1430 else:
1431 it = self.root_comp.Matches(comp)
1432 self.comp_matches = list(it)
1433 self.comp_matches.reverse()
1434
1435 if mylib.PYTHON:
1436 assert self.comp_iter is not None, self.comp_iter
1437 try:
1438 next_completion = self.comp_iter.next()
1439 except StopIteration:
1440 next_completion = None # signals the end
1441 else:
1442 assert self.comp_matches is not None, self.comp_matches
1443 try:
1444 next_completion = self.comp_matches.pop()
1445 except IndexError:
1446 next_completion = None # signals the end
1447
1448 return next_completion
1449
1450 def __call__(self, unused_word, state):
1451 # type: (str, int) -> Optional[str]
1452 """Return a single match."""
1453 try:
1454 return self._GetNextCompletion(state)
1455 except util.UserExit as e:
1456 # TODO: Could use errfmt to show this
1457 print_stderr("osh: Ignoring 'exit' in completion plugin")
1458 except error.FatalRuntime as e:
1459 # From -W. TODO: -F is swallowed now.
1460 # We should have a nicer UI for displaying errors. Maybe they shouldn't
1461 # print it to stderr. That messes up the completion display. We could
1462 # print what WOULD have been COMPREPLY here.
1463 print_stderr('osh: Runtime error while completing: %s' %
1464 e.UserErrorString())
1465 self.debug_f.writeln('Runtime error while completing: %s' %
1466 e.UserErrorString())
1467 except (IOError, OSError) as e:
1468 # test this with prlimit --nproc=1 --pid=$$
1469 print_stderr('osh: I/O error (completion): %s' %
1470 posix.strerror(e.errno))
1471 except KeyboardInterrupt:
1472 # It appears GNU readline handles Ctrl-C to cancel a long completion.
1473 # So this may never happen?
1474 print_stderr('Ctrl-C in completion')
1475 except Exception as e: # ESSENTIAL because readline swallows exceptions.
1476 if mylib.PYTHON:
1477 import traceback
1478 traceback.print_exc()
1479 print_stderr('osh: Unhandled exception while completing: %s' % e)
1480 self.debug_f.writeln('Unhandled exception while completing: %s' %
1481 e)
1482 except SystemExit as e:
1483 # I think this should no longer be called, because we don't use
1484 # sys.exit()?
1485 # But put it here in case Because readline ignores SystemExit!
1486 posix._exit(e.code)
1487
1488 return None
1489
1490
1491def ExecuteReadlineCallback(cb, word, state):
1492 # type: (ReadlineCallback, str, int) -> Optional[str]
1493 return cb.__call__(word, state)
1494
1495
1496if __name__ == '__main__':
1497 # This does basic filename copmletion
1498 import readline
1499 readline.parse_and_bind('tab: complete')
1500 while True:
1501 x = raw_input('$ ')
1502 print(x)