diff options
-rw-r--r-- | Lib/rlcompleter.py | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py new file mode 100644 index 0000000..285faed --- /dev/null +++ b/Lib/rlcompleter.py @@ -0,0 +1,108 @@ +"""Word completion for GNU readline 2.0. + +This requires the latest extension to the readline module (the +set_completer() function). When completing a simple identifier, it +completes keywords, built-ins and globals in __main__; when completing +NAME.NAME..., it evaluates (!) the expression up to the last dot and +completes its attributes. + +It's very cool to do "import string" type "string.", hit the +completion key (twice), and see the list of names defined by the +string module! + +Tip: to use the tab key as the completion key, call + + readline.parse_and_bind("tab: complete") + +Notes: + +- Exceptions raised by the completer function are *ignored* (and +generally cause the completion to fail). This is a feature -- since +readline sets the tty device in raw (or cbreak) mode, printing a +traceback wouldn't work well without some complicated hoopla to save, +reset and restore the tty state. + +- The evaluation of the NAME.NAME... form may cause arbitrary +application defined code to be executed if an object with a +__getattr__ hook is found. Since it is the responsibility of the +application (or the user) to enable this feature, I consider this an +acceptable risk. More complicated expressions (e.g. function calls or +indexing operations) are *not* evaluated. + +- GNU readline is also used by the built-in functions input() and +raw_input(), and thus these also benefit/suffer from the completer +features. Clearly an interactive application can benefit by +specifying its own completer function and using raw_input() for all +its input. + +- When the original stdin is not a tty device, GNU readline is never +used, and this module (and the readline module) are silently inactive. + +""" + +import readline +import keyword +import __builtin__ +import __main__ +import string +import re +import traceback + +class Completer: + + def complete(self, text, state): + """Return the next possible completion for 'text'. + + This is called successively with state == 0, 1, 2, ... until it + returns None. The completion should begin with 'text'. + + """ + if state == 0: + if "." in text: + self.matches = self.attr_matches(text) + else: + self.matches = self.global_matches(text) + return self.matches[state] + + def global_matches(self, text): + """Compute matches when text is a simple name. + + Return a list of all keywords, built-in functions and names + currently defines in __main__ that match. + + """ + matches = [] + n = len(text) + for list in [keyword.kwlist, + __builtin__.__dict__.keys(), + __main__.__dict__.keys()]: + for word in list: + if word[:n] == text: + matches.append(word) + return matches + + def attr_matches(self, text): + """Compute matches when text contains a dot. + + Assuming the text is of the form NAME.NAME....[NAME], and is + evaluabable in the globals of __main__, it will be evaluated + and its attributes (as revealed by dir()) are used as possible + completions. + + WARNING: this can still invoke arbitrary C code, if an object + with a __getattr__ hook is evaluated. + + """ + m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) + if not m: + return + expr, attr = m.group(1, 3) + words = dir(eval(expr, __main__.__dict__)) + matches = [] + n = len(attr) + for word in words: + if word[:n] == attr: + matches.append("%s.%s" % (expr, word)) + return matches + +readline.set_completer(Completer().complete) |