From 4a04b6a7b2feb29627dfb983eee78c381375a4cf 2005-12-26 17:51:33 From: fperez Date: 2005-12-26 17:51:33 Subject: [PATCH] Namespace fixes for embedded instances, as well as tab-complete enhancements in that context. Full details in changelog --- diff --git a/IPython/FlexCompleter.py b/IPython/FlexCompleter.py index 0a0f777..e8216ca 100644 --- a/IPython/FlexCompleter.py +++ b/IPython/FlexCompleter.py @@ -78,31 +78,40 @@ used, and this module (and the readline module) are silently inactive. # #***************************************************************************** -import readline import __builtin__ import __main__ +import readline +import keyword +import types __all__ = ["Completer"] class Completer: - def __init__(self, namespace = None): + def __init__(self,namespace=None,global_namespace=None): """Create a new completer for the command line. - Completer([namespace]) -> completer instance. + Completer([namespace,global_namespace]) -> completer instance. If unspecified, the default namespace where completions are performed is __main__ (technically, __main__.__dict__). Namespaces should be given as dictionaries. + An optional second namespace can be given. This allows the completer + to handle cases where both the local and global scopes need to be + distinguished. + Completer instances should be used as the completion mechanism of readline via the set_completer() call: readline.set_completer(Completer(my_namespace).complete) """ - if namespace and type(namespace) != type({}): + if namespace and type(namespace) != types.DictType: raise TypeError,'namespace must be a dictionary' + if global_namespace and type(global_namespace) != types.DictType: + raise TypeError,'global_namespace must be a dictionary' + # Don't bind to namespace quite yet, but flag whether the user wants a # specific namespace or to use __main__.__dict__. This will allow us # to bind to __main__.__dict__ at completion time, not now. @@ -112,6 +121,12 @@ class Completer: self.use_main_ns = 0 self.namespace = namespace + # The global namespace, if given, can be bound directly + if global_namespace is None: + self.global_namespace = {} + else: + self.global_namespace = global_namespace + def complete(self, text, state): """Return the next possible completion for 'text'. @@ -136,27 +151,29 @@ class Completer: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently - defined in self.namespace that match. + defined in self.namespace or self.global_namespace that match. """ - import keyword matches = [] + match_append = matches.append n = len(text) - for list in [keyword.kwlist, - __builtin__.__dict__.keys(), - self.namespace.keys()]: - for word in list: + for lst in [keyword.kwlist, + __builtin__.__dict__.keys(), + self.namespace.keys(), + self.global_namespace.keys()]: + for word in lst: if word[:n] == text and word != "__builtins__": - matches.append(word) + match_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 - evaluatable in self.namespace, it will be evaluated and its attributes - (as revealed by dir()) are used as possible completions. (For class - instances, class members are are also considered.) + evaluatable in self.namespace or self.global_namespace, it will be + evaluated and its attributes (as revealed by dir()) are used as + possible completions. (For class instances, class members are are + also considered.) WARNING: this can still invoke arbitrary C code, if an object with a __getattr__ hook is evaluated. @@ -170,7 +187,12 @@ class Completer: if not m: return [] expr, attr = m.group(1, 3) - object = eval(expr, self.namespace) + print 'expr:',expr # dbg + try: + object = eval(expr, self.namespace) + except: + object = eval(expr, self.global_namespace) + print 'obj:',object # dbg words = [w for w in dir(object) if isinstance(w, basestring)] if hasattr(object,'__class__'): words.append('__class__') diff --git a/IPython/Shell.py b/IPython/Shell.py index ba6cd49..ca7229f 100644 --- a/IPython/Shell.py +++ b/IPython/Shell.py @@ -4,7 +4,7 @@ All the matplotlib support code was co-developed with John Hunter, matplotlib's author. -$Id: Shell.py 927 2005-12-08 23:19:59Z fperez $""" +$Id: Shell.py 952 2005-12-26 17:51:33Z fperez $""" #***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez @@ -212,8 +212,8 @@ class IPShellEmbed: If you need to manually""" - if dummy not in [0,1]: - raise ValueError,'dummy parameter must be 0 or 1' + if dummy not in [0,1,False,True]: + raise ValueError,'dummy parameter must be boolean' self.__dummy_mode = dummy def get_dummy_mode(self): @@ -874,12 +874,13 @@ def start(): global USE_TK # Crude sys.argv hack to extract the threading options. - if len(sys.argv) > 1: - if len(sys.argv) > 2: - arg2 = sys.argv[2] + argv = sys.argv + if len(argv) > 1: + if len(argv) > 2: + arg2 = argv[2] if arg2.endswith('-tk'): USE_TK = True - arg1 = sys.argv[1] + arg1 = argv[1] if arg1.endswith('-gthread'): shell = IPShellGTK elif arg1.endswith( '-qthread' ): diff --git a/IPython/iplib.py b/IPython/iplib.py index af65870..cd00f2d 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -6,7 +6,7 @@ Requires Python 2.1 or newer. This file contains all the classes and helper functions specific to IPython. -$Id: iplib.py 951 2005-12-25 00:57:24Z fperez $ +$Id: iplib.py 952 2005-12-26 17:51:33Z fperez $ """ #***************************************************************************** @@ -171,7 +171,8 @@ try: class MagicCompleter(FlexCompleter.Completer): """Extension of the completer class to work on %-prefixed lines.""" - def __init__(self,shell,namespace=None,omit__names=0,alias_table=None): + def __init__(self,shell,namespace=None,global_namespace=None, + omit__names=0,alias_table=None): """MagicCompleter() -> completer Return a completer object suitable for use by the readline library @@ -184,6 +185,10 @@ try: only be accessed via the ipython instance. - namespace: an optional dict where completions are performed. + + - global_namespace: secondary optional dict for completions, to + handle cases (such as IPython embedded inside functions) where + both Python scopes are visible. - The optional omit__names parameter sets the completer to omit the 'magic' names (__magicname__) for python objects unless the text @@ -225,13 +230,14 @@ try: """Return all possible completions for the benefit of emacs.""" completions = [] + comp_append = completions.append try: for i in xrange(sys.maxint): res = self.complete(text, i) if not res: break - completions.append(res) + comp_append(res) #XXX workaround for ``notDefined.`` except NameError: pass @@ -622,13 +628,22 @@ class InteractiveShell(code.InteractiveConsole, Logger, Magic): # instance has its own private namespace, so we can't go shoving # everything into __main__. - try: - main_name = self.user_ns['__name__'] - except KeyError: - raise KeyError,'user_ns dictionary MUST have a "__name__" key' - else: - #print "pickle hack in place" # dbg - sys.modules[main_name] = FakeModule(self.user_ns) + # note, however, that we should only do this for non-embedded + # ipythons, which really mimic the __main__.__dict__ with their own + # namespace. Embedded instances, on the other hand, should not do + # this because they need to manage the user local/global namespaces + # only, but they live within a 'normal' __main__ (meaning, they + # shouldn't overtake the execution environment of the script they're + # embedded in). + + if not embedded: + try: + main_name = self.user_ns['__name__'] + except KeyError: + raise KeyError,'user_ns dictionary MUST have a "__name__" key' + else: + #print "pickle hack in place" # dbg + sys.modules[main_name] = FakeModule(self.user_ns) # List of input with multi-line handling. # Fill its zero entry, user counter starts at 1 @@ -972,11 +987,11 @@ class InteractiveShell(code.InteractiveConsole, Logger, Magic): def set_completer_frame(self, frame): if frame: - ns = frame.f_globals.copy() - ns.update(frame.f_locals) - self.Completer.namespace = ns + self.Completer.namespace = frame.f_locals + self.Completer.global_namespace = frame.f_globals else: self.Completer.namespace = self.user_ns + self.Completer.global_namespace = self.user_global_ns def post_config_initialization(self): """Post configuration init method @@ -1234,6 +1249,7 @@ want to merge them back into the new files.""" % locals() import readline self.Completer = MagicCompleter(self, self.user_ns, + self.user_global_ns, self.rc.readline_omit__names, self.alias_table) except ImportError,NameError: @@ -1427,6 +1443,10 @@ want to merge them back into the new files.""" % locals() if local_ns is None and global_ns is None: self.user_global_ns.update(__main__.__dict__) + # make sure the tab-completer has the correct frame information, so it + # actually completes using the frame's locals/globals + self.set_completer_frame(call_frame) + self.interact(header) def interact(self, banner=None): @@ -1914,7 +1934,7 @@ want to merge them back into the new files.""" % locals() cmd = '%sipmagic("%s")' % (pre,esc_quotes('%s %s' % (iFun,theRest))) self.log(cmd,continue_prompt) self.update_cache(line) - #print 'in handle_magic, cmd=<%s>' % cmd # dbg + print 'in handle_magic, cmd=<%s>' % cmd # dbg return cmd def handle_auto(self, line, continue_prompt=None, diff --git a/doc/ChangeLog b/doc/ChangeLog index 7b2c7c4..5b7411d 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,26 @@ +2005-12-26 Fernando Perez + + * IPython/FlexCompleter.py (Completer.__init__): Added support for + distinct local and global namespaces in the completer API. This + change allows us top properly handle completion with distinct + scopes, including in embedded instances (this had never really + worked correctly). + + Note: this introduces a change in the constructor for + MagicCompleter, as a new global_namespace parameter is now the + second argument (the others were bumped one position). + +2005-12-25 Fernando Perez + + * IPython/iplib.py (embed_mainloop): fix tab-completion in + embedded instances (which can be done now thanks to Vivian's + frame-handling fixes for pdb). + (InteractiveShell.__init__): Fix namespace handling problem in + embedded instances. We were overwriting __main__ unconditionally, + and this should only be done for 'full' (non-embedded) IPython; + embedded instances must respect the caller's __main__. Thanks to + a bug report by Yaroslav Bulatov + 2005-12-24 Fernando Perez * setup.py: added download_url to setup(). This registers the