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

1503 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 # computes s[1:] for Id.VSub_DollarName
1054 to_complete = lexer.LazyStr(t2)
1055 n = len(to_complete)
1056 for name in self.mem.VarNames():
1057 if name.startswith(to_complete):
1058 # no need to quote var names
1059 yield line_until_tab + name[n:]
1060 return
1061
1062 # echo ${P
1063 if t2.id == Id.VSub_Name and IsDummy(t1):
1064 self.comp_ui_state.display_pos = t2.col # no offset
1065 to_complete = lexer.LazyStr(t2)
1066 n = len(to_complete)
1067 for name in self.mem.VarNames():
1068 if name.startswith(to_complete):
1069 # no need to quote var names
1070 yield line_until_tab + name[n:]
1071 return
1072
1073 # echo $(( VAR
1074 if t2.id == Id.Lit_ArithVarLike and IsDummy(t1):
1075 self.comp_ui_state.display_pos = t2.col # no offset
1076 to_complete = lexer.LazyStr(t2)
1077 n = len(to_complete)
1078 for name in self.mem.VarNames():
1079 if name.startswith(to_complete):
1080 # no need to quote var names
1081 yield line_until_tab + name[n:]
1082 return
1083
1084 if len(trail.words) > 0:
1085 # echo ~<TAB>
1086 # echo ~a<TAB> $(home dirs)
1087 # This must be done at a word level, and TildeDetectAll() does NOT help
1088 # here, because they don't have trailing slashes yet! We can't do it on
1089 # tokens, because otherwise f~a will complete. Looking at word_part is
1090 # EXACTLY what we want.
1091 parts = trail.words[-1].parts
1092 if len(parts) > 0 and word_.LiteralId(parts[0]) == Id.Lit_Tilde:
1093 #log('TILDE parts %s', parts)
1094
1095 if (len(parts) == 2 and
1096 word_.LiteralId(parts[1]) == Id.Lit_CompDummy):
1097 tilde_tok = cast(Token, parts[0])
1098
1099 # end of tilde
1100 self.comp_ui_state.display_pos = tilde_tok.col + 1
1101
1102 to_complete = ''
1103 for u in pyos.GetAllUsers():
1104 name = u.pw_name
1105 s = line_until_tab + ShellQuoteB(name) + '/'
1106 yield s
1107 return
1108
1109 if (len(parts) == 3 and
1110 word_.LiteralId(parts[1]) == Id.Lit_Chars and
1111 word_.LiteralId(parts[2]) == Id.Lit_CompDummy):
1112
1113 chars_tok = cast(Token, parts[1])
1114
1115 self.comp_ui_state.display_pos = chars_tok.col
1116
1117 to_complete = lexer.TokenVal(chars_tok)
1118 n = len(to_complete)
1119 for u in pyos.GetAllUsers(): # catch errors?
1120 name = u.pw_name
1121 if name.startswith(to_complete):
1122 s = line_until_tab + ShellQuoteB(name[n:]) + '/'
1123 yield s
1124 return
1125
1126 # echo hi > f<TAB> (complete redirect arg)
1127 if len(trail.redirects) > 0:
1128 r = trail.redirects[-1]
1129 # Only complete 'echo >', but not 'echo >&' or 'cat <<'
1130 # TODO: Don't complete <<< 'h'
1131 if (r.arg.tag() == redir_param_e.Word and
1132 consts.RedirArgType(r.op.id) == redir_arg_type_e.Path):
1133 arg_word = r.arg
1134 UP_word = arg_word
1135 arg_word = cast(CompoundWord, UP_word)
1136 if WordEndsWithCompDummy(arg_word):
1137 debug_f.writeln('Completing redirect arg')
1138
1139 try:
1140 val = self.word_ev.EvalWordToString(arg_word)
1141 except error.FatalRuntime as e:
1142 debug_f.writeln('Error evaluating redirect word: %s' %
1143 e)
1144 return
1145 if val.tag() != value_e.Str:
1146 debug_f.writeln("Didn't get a string from redir arg")
1147 return
1148
1149 tok = location.LeftTokenForWord(arg_word)
1150 self.comp_ui_state.display_pos = tok.col
1151
1152 comp.Update('', val.s, '', 0, [])
1153 n = len(val.s)
1154 action = FileSystemAction(False, False, True)
1155 for name in action.Matches(comp):
1156 yield line_until_tab + ShellQuoteB(name[n:])
1157 return
1158
1159 #
1160 # We're not completing the shell language. Delegate to user-defined
1161 # completion for external tools.
1162 #
1163
1164 # Set below, and set on retries.
1165 base_opts = None # type: Dict[str, bool]
1166 user_spec = None # type: Optional[UserSpec]
1167
1168 # Used on retries.
1169 partial_argv = [] # type: List[str]
1170 num_partial = -1
1171 first = None # type: str
1172
1173 if len(trail.words) > 0:
1174 # Now check if we're completing a word!
1175 if WordEndsWithCompDummy(trail.words[-1]):
1176 debug_f.writeln('Completing words')
1177 #
1178 # It didn't look like we need to complete var names, tilde, redirects,
1179 # etc. Now try partial_argv, which may involve invoking PLUGINS.
1180
1181 # needed to complete paths with ~
1182 # mycpp: workaround list cast
1183 trail_words = [cast(word_t, w) for w in trail.words]
1184 words2 = word_.TildeDetectAll(trail_words)
1185 if 0:
1186 debug_f.writeln('After tilde detection')
1187 for w in words2:
1188 print(w, file=debug_f)
1189
1190 if 0:
1191 debug_f.writeln('words2:')
1192 for w2 in words2:
1193 debug_f.writeln(' %s' % w2)
1194
1195 for w in words2:
1196 try:
1197 # TODO:
1198 # - Should we call EvalWordSequence? But turn globbing off? It
1199 # can do splitting and such.
1200 # - We could have a variant to eval TildeSub to ~ ?
1201 val = self.word_ev.EvalWordToString(w)
1202 except error.FatalRuntime:
1203 # Why would it fail?
1204 continue
1205 if val.tag() == value_e.Str:
1206 partial_argv.append(val.s)
1207 else:
1208 pass
1209
1210 debug_f.writeln('partial_argv: [%s]' % ','.join(partial_argv))
1211 num_partial = len(partial_argv)
1212
1213 first = partial_argv[0]
1214 alias_first = None # type: str
1215 if mylib.PYTHON:
1216 debug_f.writeln('alias_words: [%s]' % trail.alias_words)
1217
1218 if len(trail.alias_words) > 0:
1219 w = trail.alias_words[0]
1220 try:
1221 val = self.word_ev.EvalWordToString(w)
1222 except error.FatalRuntime:
1223 pass
1224 alias_first = val.s
1225 debug_f.writeln('alias_first: %s' % alias_first)
1226
1227 if num_partial == 0: # should never happen because of Lit_CompDummy
1228 raise AssertionError()
1229 elif num_partial == 1:
1230 base_opts, user_spec = self.comp_lookup.GetFirstSpec()
1231
1232 # Display/replace since the beginning of the first word. Note: this
1233 # is non-zero in the case of
1234 # echo $(gr and
1235 # echo `gr
1236
1237 tok = location.LeftTokenForWord(trail.words[0])
1238 self.comp_ui_state.display_pos = tok.col
1239 self.debug_f.writeln('** DISPLAY_POS = %d' %
1240 self.comp_ui_state.display_pos)
1241
1242 else:
1243 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1244 first)
1245 if not user_spec and alias_first is not None:
1246 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1247 alias_first)
1248 if user_spec:
1249 # Pass the aliased command to the user-defined function, and use
1250 # it for retries.
1251 first = alias_first
1252 if not user_spec:
1253 base_opts, user_spec = self.comp_lookup.GetFallback()
1254
1255 # Display since the beginning
1256 tok = location.LeftTokenForWord(trail.words[-1])
1257 self.comp_ui_state.display_pos = tok.col
1258 if mylib.PYTHON:
1259 self.debug_f.writeln('words[-1]: [%s]' %
1260 trail.words[-1])
1261
1262 self.debug_f.writeln('display_pos %d' %
1263 self.comp_ui_state.display_pos)
1264
1265 # Update the API for user-defined functions.
1266 index = len(
1267 partial_argv) - 1 # COMP_CWORD is -1 when it's empty
1268 prev = '' if index == 0 else partial_argv[index - 1]
1269 comp.Update(first, partial_argv[-1], prev, index, partial_argv)
1270
1271 # This happens in the case of [[ and ((, or a syntax error like 'echo < >'.
1272 if not user_spec:
1273 debug_f.writeln("Didn't find anything to complete")
1274 return
1275
1276 # Reset it back to what was registered. User-defined functions can mutate
1277 # it.
1278 dynamic_opts = {} # type: Dict[str, bool]
1279 self.compopt_state.dynamic_opts = dynamic_opts
1280 with ctx_Completing(self.compopt_state):
1281 done = False
1282 while not done:
1283 done = True # exhausted candidates without getting a retry
1284 try:
1285 for candidate in self._PostProcess(base_opts, dynamic_opts,
1286 user_spec, comp):
1287 yield candidate
1288 except _RetryCompletion as e:
1289 debug_f.writeln('Got 124, trying again ...')
1290 done = False
1291
1292 # Get another user_spec. The ShellFuncAction may have 'sourced' code
1293 # and run 'complete' to mutate comp_lookup, and we want to get that
1294 # new entry.
1295 if num_partial == 0:
1296 raise AssertionError()
1297 elif num_partial == 1:
1298 base_opts, user_spec = self.comp_lookup.GetFirstSpec()
1299 else:
1300 # (already processed alias_first)
1301 base_opts, user_spec = self.comp_lookup.GetSpecForName(
1302 first)
1303 if not user_spec:
1304 base_opts, user_spec = self.comp_lookup.GetFallback(
1305 )
1306
1307 def _PostProcess(
1308 self,
1309 base_opts, # type: Dict[str, bool]
1310 dynamic_opts, # type: Dict[str, bool]
1311 user_spec, # type: UserSpec
1312 comp, # type: Api
1313 ):
1314 # type: (...) -> Iterator[str]
1315 """Add trailing spaces / slashes to completion candidates, and time
1316 them.
1317
1318 NOTE: This post-processing MUST go here, and not in UserSpec, because
1319 it's in READLINE in bash. compgen doesn't see it.
1320 """
1321 self.debug_f.writeln('Completing %r ... (Ctrl-C to cancel)' %
1322 comp.line)
1323 start_time = time_.time()
1324
1325 # TODO: dedupe candidates? You can get two 'echo' in bash, which is dumb.
1326
1327 i = 0
1328 for candidate, action_kind in user_spec.AllMatches(comp):
1329 # SUBTLE: dynamic_opts is part of compopt_state, which ShellFuncAction
1330 # can mutate! So we don't want to pull this out of the loop.
1331 #
1332 # TODO: The candidates from each actions shouldn't be flattened.
1333 # for action in user_spec.Actions():
1334 # if action.IsFileSystem(): # this returns is_dir too
1335 #
1336 # action.Run() # might set dynamic opts
1337 # opt_nospace = base_opts...
1338 # if 'nospace' in dynamic_opts:
1339 # opt_nosspace = dynamic_opts['nospace']
1340 # for candidate in action.Matches():
1341 # add space or /
1342 # and do escaping too
1343 #
1344 # Or maybe you can request them on demand? Most actions are EAGER.
1345 # While the ShellacAction is LAZY? And you should be able to cancel it!
1346
1347 # NOTE: User-defined plugins (and the -P flag) can REWRITE what the user
1348 # already typed. So
1349 #
1350 # $ echo 'dir with spaces'/f<TAB>
1351 #
1352 # can be rewritten to:
1353 #
1354 # $ echo dir\ with\ spaces/foo
1355 line_until_tab = self.comp_ui_state.line_until_tab
1356 line_until_word = line_until_tab[:self.comp_ui_state.display_pos]
1357
1358 opt_filenames = base_opts.get('filenames', False)
1359 if 'filenames' in dynamic_opts:
1360 opt_filenames = dynamic_opts['filenames']
1361
1362 # compopt -o filenames is for user-defined actions. Or any
1363 # FileSystemAction needs it.
1364 if action_kind == comp_action_e.FileSystem or opt_filenames:
1365 if path_stat.isdir(candidate):
1366 s = line_until_word + ShellQuoteB(candidate) + '/'
1367 yield s
1368 continue
1369
1370 opt_nospace = base_opts.get('nospace', False)
1371 if 'nospace' in dynamic_opts:
1372 opt_nospace = dynamic_opts['nospace']
1373
1374 sp = '' if opt_nospace else ' '
1375 cand = (candidate if action_kind == comp_action_e.BashFunc else
1376 ShellQuoteB(candidate))
1377
1378 yield line_until_word + cand + sp
1379
1380 # NOTE: Can't use %.2f in production build!
1381 i += 1
1382 elapsed_ms = (time_.time() - start_time) * 1000.0
1383 plural = '' if i == 1 else 'es'
1384
1385 # TODO: Show this in the UI if it takes too long!
1386 if 0:
1387 self.debug_f.writeln(
1388 '... %d match%s for %r in %d ms (Ctrl-C to cancel)' %
1389 (i, plural, comp.line, elapsed_ms))
1390
1391 elapsed_ms = (time_.time() - start_time) * 1000.0
1392 plural = '' if i == 1 else 'es'
1393 self.debug_f.writeln('Found %d match%s for %r in %d ms' %
1394 (i, plural, comp.line, elapsed_ms))
1395
1396
1397class ReadlineCallback(object):
1398 """A callable we pass to the readline module."""
1399
1400 def __init__(self, readline, root_comp, debug_f):
1401 # type: (Optional[Readline], RootCompleter, util._DebugFile) -> None
1402 self.readline = readline
1403 self.root_comp = root_comp
1404 self.debug_f = debug_f
1405
1406 # current completion being processed
1407 if mylib.PYTHON:
1408 self.comp_iter = None # type: Iterator[str]
1409 else:
1410 self.comp_matches = None # type: List[str]
1411
1412 def _GetNextCompletion(self, state):
1413 # type: (int) -> Optional[str]
1414 if state == 0:
1415 # TODO: Tokenize it according to our language. If this is $PS2, we also
1416 # need previous lines! Could make a VirtualLineReader instead of
1417 # StringLineReader?
1418 buf = self.readline.get_line_buffer()
1419
1420 # Readline parses "words" using characters provided by
1421 # set_completer_delims().
1422 # We have our own notion of words. So let's call this a 'rl_slice'.
1423 begin = self.readline.get_begidx()
1424 end = self.readline.get_endidx()
1425
1426 comp = Api(line=buf, begin=begin, end=end)
1427 self.debug_f.writeln('Api %r %d %d' % (buf, begin, end))
1428
1429 if mylib.PYTHON:
1430 self.comp_iter = self.root_comp.Matches(comp)
1431 else:
1432 it = self.root_comp.Matches(comp)
1433 self.comp_matches = list(it)
1434 self.comp_matches.reverse()
1435
1436 if mylib.PYTHON:
1437 assert self.comp_iter is not None, self.comp_iter
1438 try:
1439 next_completion = self.comp_iter.next()
1440 except StopIteration:
1441 next_completion = None # signals the end
1442 else:
1443 assert self.comp_matches is not None, self.comp_matches
1444 try:
1445 next_completion = self.comp_matches.pop()
1446 except IndexError:
1447 next_completion = None # signals the end
1448
1449 return next_completion
1450
1451 def __call__(self, unused_word, state):
1452 # type: (str, int) -> Optional[str]
1453 """Return a single match."""
1454 try:
1455 return self._GetNextCompletion(state)
1456 except util.UserExit as e:
1457 # TODO: Could use errfmt to show this
1458 print_stderr("osh: Ignoring 'exit' in completion plugin")
1459 except error.FatalRuntime as e:
1460 # From -W. TODO: -F is swallowed now.
1461 # We should have a nicer UI for displaying errors. Maybe they shouldn't
1462 # print it to stderr. That messes up the completion display. We could
1463 # print what WOULD have been COMPREPLY here.
1464 print_stderr('osh: Runtime error while completing: %s' %
1465 e.UserErrorString())
1466 self.debug_f.writeln('Runtime error while completing: %s' %
1467 e.UserErrorString())
1468 except (IOError, OSError) as e:
1469 # test this with prlimit --nproc=1 --pid=$$
1470 print_stderr('osh: I/O error (completion): %s' %
1471 posix.strerror(e.errno))
1472 except KeyboardInterrupt:
1473 # It appears GNU readline handles Ctrl-C to cancel a long completion.
1474 # So this may never happen?
1475 print_stderr('Ctrl-C in completion')
1476 except Exception as e: # ESSENTIAL because readline swallows exceptions.
1477 if mylib.PYTHON:
1478 import traceback
1479 traceback.print_exc()
1480 print_stderr('osh: Unhandled exception while completing: %s' % e)
1481 self.debug_f.writeln('Unhandled exception while completing: %s' %
1482 e)
1483 except SystemExit as e:
1484 # I think this should no longer be called, because we don't use
1485 # sys.exit()?
1486 # But put it here in case Because readline ignores SystemExit!
1487 posix._exit(e.code)
1488
1489 return None
1490
1491
1492def ExecuteReadlineCallback(cb, word, state):
1493 # type: (ReadlineCallback, str, int) -> Optional[str]
1494 return cb.__call__(word, state)
1495
1496
1497if __name__ == '__main__':
1498 # This does basic filename copmletion
1499 import readline
1500 readline.parse_and_bind('tab: complete')
1501 while True:
1502 x = raw_input('$ ')
1503 print(x)