Commits

Henning Schröder committed 2c849b3

more refactorings

  • Participants
  • Parent commits 2c0b67d

Comments (0)

Files changed (24)

 .*\.pyc
 .*~
-\.idea
+\.idea
+\.ropeproject
+PyCode - a library to create a decent Python IDE
+================================================
+
+Expected features
+-----------------
+
+ * autocomplete, of course
+   * autocomplete restructured text syntax inside documentation strings (:param foo..)
+ * find definítion
+ * find usage/show occurences
+ * show signature/doc
+ * show type/kind of given symbol
+ * refactoring
+   * rename
+   * move
+   * extract variable/constant
+ * checks
+   * valid syntax
+   * pylint
+   * pyflakes
+   * pep8
+   * regex syntax inside strings
+   * tabnanny
+ * fixing
+   * NameErrors:
+     * offer to add import-statement if name is found in module database
+     * offer to add function if name is called (method if dotted-name)
+     * offer to add variable if name is not called (attribute if dotted-name)
+   * formatting
+     * autopep8
+   * Python 2/3 syntax differences: 2to3
+   * tabs to spaces
+   * spelling corrections
+ * goto symbol
+   * inputting any class name opens the file containing the class
+ * outline/tree/structure of module
+ * indention hints/autoindent
+ * tasks (XXX/TODO in source)
+ * auto-editing
+   * close parentheses/brackets/braces
+   * space after comma
+   * after "from FOO " append "import "
+   * add documentation stub based on signature
+ * correctly load/save source files according to encoding hint at the header
+ * project settings
+   * source root folder
+   * addition python path
+   * virtualenv support
+ * online help for api/library
+ * debugging facility
+
+
 Comparison of available Python solutions
 ----------------------------------------
 
  * last update: 2012
 
 jedi (http://github.com/davidhalter/jedi/)
- * fault-tolerant autcompletion library which can ignore syntax errors
+ * fault-tolerant autocompletion library which can ignore syntax errors
  * uses tokenize for "fuzzy-parsing" instead of abstract syntax tree
- * last update: 2012 
+ * last update: 2013
 
 Ninja-IDE (http://github.com/ninja-ide/ninja-ide)
  * autocompletion built into Ninja-IDE, created because Rope was too slow
    (resides in ninja_ide/tools/completion)
- * last update: 2012
+ * last update: 2013
 
 Supplement (http://github.com/baverman/supplement)
  * claims to be faster than rope
  * last update: 2012
 
 
-Programs which contain type inference
+Other programs which contain type inference
 * pylint
 * Mypy
 * PyPy
 * ShedSkin
 
-
-Papers
+Related Papers
  * John Aycock: Aggressive Type Inference
    http://www.python.org/workshops/2000-01/proceedings/papers/aycock/aycock.html
  * Starkiller: A Static Type Inferencer and Compiler for Python 
  
 
-   
 Additional links
  * http://www.smallshire.org.uk/sufficientlysmall/2010/04/11/a-hindley-milner-type-inference-implementation-in-python/
  * http://lambda-the-ultimate.org/node/1519
 
 
 
-Features besides autocomplete
-------------------------------
 
-* goto definítion
 
-* show usage
 
-* show signature/doc
+Copyright
+---------
+This library heavily depends on the hard work of other authors, mainly of Rope and Jedi
 
-* show type/kind of given symbol
-
-* refactoring
-
-  * rename
-  
-  * move
-  
-  * extract variable/constant
-
-* fixing
-
-  * NameErrors:
-    * offer to add import-statement if name is found in module database
-    * offer to add function if name is called (method if dotted-name)
-    * offer to add variable if name is not called (attribute if dotted-name)
-
-* goto symbol
-
-  * inputting any class name opens the file containing the class
-
-* add documentation stub based on signature
+ * function module_completions is taken from Spyder
+ * copies of rope, jedi, pep8, autopep8, pylint, pyflakes and whoosh are included for convenience
+ * indenter is based  on code from rope-ide
+ * stubs for extension modules were collected from various sources. They only contain the signature of stdlib functions.

File lab/asyncipc.py

+# -*- coding: utf-8 -*-
+import atexit
+from multiprocessing import Process, Queue
+from threading import Thread
+
+
+
+class Server(object):
+    
+
+    def __init__(self):
+        pass
+
+    
+    def received(self, data):
+        print "received", len(data), "bytes"
+        return data
+
+
+
+class Connection(object):
+
+
+    def __init__(self, server_factory):
+        self.server_factory = server_factory
+        self.input_queue = Queue()
+        self.output_queue = Queue()
+
+
+    def _listen(self, server_factory, input_queue, output_queue):
+        server = server_factory()
+        while True:
+            item = input_queue.get()
+            if item is None:
+                output_queue.put(None)
+                break
+            result = server.received(item)
+            output_queue.put(result)
+
+        
+    def start(self):
+        self.proc = Process(target=self._listen, args=(self.server_factory, self.input_queue, self.output_queue))
+        self.proc.start()
+
+
+
+
+class Client(object):
+    
+    
+    def __init__(self, connection):
+        self._connection = connection
+        atexit.register(self._exit)
+        self._callback_queue = []
+        self._callback_thread = Thread(target=self._watch_results)
+        self._callback_thread.setDaemon(True)
+        self._callback_thread.start()
+        
+
+    def _exit(self):
+        self._call(None, None)
+
+
+    def _watch_results(self):
+        while True:
+            item = self._connection.output_queue.get()
+            if item is None:
+                break
+            callback = self._callback_queue.pop()
+            callback(item)
+        
+        
+    def _call(self, data, callback):    
+        self._connection.input_queue.put(data)
+        self._callback_queue.insert(0, callback)
+
+
+    def call(self, data, callback):
+        self._call(data, callback)
+
+        
+        
+class IpcCallMixIn(object):
+
+    
+    def _decode_call(self, data):
+        (msg, args, kwargs) = data
+        return (msg, args, kwargs)
+    
+    
+    def _encode_result(self, error, result):
+        return (error, result)
+    
+
+    def _encode_call(self, msg, args, kwargs):
+        info = (msg, args, kwargs)
+        data = info
+        return data
+
+        
+    def _decode_result(self, data):
+        error, value = data
+        if error:
+            raise error
+        return value
+
+
+    
+class IpcServer(Server, IpcCallMixIn):
+
+
+    def __init__(self, root=None):
+        Server.__init__(self)
+        if root is None:
+            root = self
+        self._root = root
+
+
+    def received(self, data):
+        msg, args, kwargs = self._decode_call(data)
+        method = getattr(self._root, msg)
+        try:
+            result = method(*args, **kwargs)
+            error = None
+        except Exception, error:
+            result = None
+        return self._encode_result(error, result)
+
+
+
+
+class _ResultCallback(object):
+
+    
+    def __init__(self, client, callback):
+        self.client = client
+        self.callback = callback
+        
+
+    def __call__(self, data):
+        result = self.client._decode_result(data)
+        return self.callback(result)
+
+        
+        
+class IpcClient(Client, IpcCallMixIn):
+    
+
+    def call(self, msg, *args, **kwargs):
+        callback = kwargs.pop("callback")
+        data = self._encode_call(msg, args, kwargs)
+        return self._call(data, callback=_ResultCallback(self, callback))
+
+    
+    def __getattr__(self, name):
+        method = lambda *args, **kwargs: self.call(name, *args, **kwargs)
+        return method
+
+
+
+if __name__ == "__main__":
+    def cb(result):
+        print "got", repr(result)
+
+    def create_server():
+        class Root(object):
+            def foo(self):
+                return "Foo!"
+            def bar(self, count=1):
+                return "BAR" * count
+        server = IpcServer(Root())
+        return server
+
+    con = Connection(create_server)
+    con.start()
+    client = IpcClient(con)
+    client.foo(callback=cb)
+    client.bar(2, callback=cb)
+    
+    import time; time.sleep(1)
+    print "done"

File lab/asyncrpc.py

+# -*- coding: utf-8 -*-
+import sys
+import socket
+import exceptions
+import asyncore
+import asynchat
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+
+DEFAULT_PORT = 8748
+PACKET_SIZE = 8192
+
+
+
+class NetworkError (exceptions.StandardError):
+    pass
+
+
+class State(object):
+    pass
+
+
+
+class AsyncServerChannel(asynchat.async_chat):
+
+    STATE_LENGTH = State()
+    STATE_PACKET = State()
+
+
+    def __init__ (self, conn, addr, callback):
+        self.addr = addr
+        asynchat.async_chat.__init__ (self, conn)
+        self.pstate = self.STATE_LENGTH
+        self.set_terminator(8)
+        self._receive_buffer = []
+        self.callback = callback
+
+
+    def log (self, *items):
+        print "log", self.__class__, items
+
+
+    def collect_incoming_data (self, data):
+        self._receive_buffer.append (data)
+
+
+    def found_terminator (self):
+        self._receive_buffer, data = [], ''.join(self._receive_buffer)
+
+        if self.pstate is self.STATE_LENGTH:
+            packet_length = int(data, 16)
+            self.set_terminator(packet_length)
+            self.pstate = self.STATE_PACKET
+        else:
+            self.set_terminator (8)
+            self.pstate = self.STATE_LENGTH
+
+            result = self.callback(self.addr, data) or ""
+            
+            self.push(('%08x' % len(result)) + result)
+
+
+
+class AsyncServer(asyncore.dispatcher):
+
+    
+    def __init__ (self, listen_addres=('', DEFAULT_PORT), handler_factory=AsyncServerChannel):
+        asyncore.dispatcher.__init__(self)
+        self._handler_factory = handler_factory
+        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+        self.set_reuse_addr()
+        self.bind(listen_addres)
+        self.listen(128) 
+
+
+    def handle_accept (self):
+        conn, addr = self.accept()
+        print "Got connect", conn, addr
+        self._handler_factory(conn, addr, self.on_received)
+
+
+    def on_received(self, addr, data):
+        return ''
+
+
+
+    
+        
+
+class AsyncClient(asynchat.async_chat):
+
+    STATE_LENGTH = State()
+    STATE_PACKET = State()
+
+
+    def __init__ (self, address=('', DEFAULT_PORT), keep_alive=False):
+
+        asynchat.async_chat.__init__(self)
+
+        if isinstance(address, basestring):
+            family = socket.AF_UNIX
+        else:
+            family = socket.AF_INET
+
+        self.create_socket (family, socket.SOCK_STREAM)
+        self._address = address
+        self._request_fifo = []
+        self._receive_buffer = []
+        self._pstate = self.STATE_LENGTH
+        self.set_terminator (8)
+        self._connected = False
+        self._keep_alive = keep_alive
+        self.connect (self._address)
+
+
+    def log (self, *items):
+        print "log ", self.__class__, items
+
+
+    def handle_connect (self):
+        self._connected = True
+
+
+    def close(self):
+        self._connected = False
+        self.flush_pending_requests ('lost connection to rpc server')
+        asynchat.async_chat.close(self)
+
+        
+    def flush_pending_requests (self, why):
+        f = self._request_fifo
+        while len(f):
+            callback = f.pop(0)
+            callback (why, None)
+
+
+    def collect_incoming_data (self, data):
+        print "incoming", len(data)
+        self._receive_buffer.append (data)
+
+
+    def found_terminator (self):
+        self._receive_buffer, data = [], ''.join (self._receive_buffer)
+
+        if self._pstate is self.STATE_LENGTH:
+            packet_length = int(data, 16)
+            self.set_terminator(packet_length)
+            self._pstate = self.STATE_PACKET
+        else:
+            if self._keep_alive:
+                self.set_terminator (8)
+                self._pstate = self.STATE_LENGTH
+            callback = self._request_fifo.pop(0)
+            callback(data)
+            if not self._keep_alive:
+                self.close()
+
+
+    def call(self, packet, callback):
+        if not self._connected:
+            # might be a unix socket...
+            family, type = self.family_and_type
+            self.create_socket(family, type)
+            self.connect (self._address)
+        # push the request out the socket
+        self.push ('%08x%s' % (len(packet), packet))
+        self._request_fifo.append(callback)
+
+
+
+
+class Client(object):
+
+
+    def __init__ (self, address, keep_alive=True):
+        self._address = address
+        self._socket = None
+        self._keep_alive = keep_alive
+
+
+    def connect(self):
+        s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
+        s.connect(self._address)
+        self._socket = s
+
+
+    def receive_packet(self):
+        s = self._socket.recv(8)
+        if len(s) != 8:
+            raise NetworkError("Could not receive packet")
+        packet_len = int(s, 16)
+        packet = []
+        while packet_len:
+            data = self._socket.recv(PACKET_SIZE)
+            packet.append (data)
+            packet_len = packet_len - len(data)
+        return ''.join(packet)
+
+
+    def send_packet (self, packet):
+        self._socket.send('%08x%s' % (len(packet), packet))
+
+    
+    def call(self, data):
+        if self._socket is None:
+            self.connect()
+        self.send_packet(data)
+        result = self.receive_packet()
+        if not self._keep_alive:
+            self._socket.close()
+            self._socket = None
+        return result
+
+
+    
+class RpcCallMixIn(object):
+
+    
+    def _decode_call(self, data):
+        (msg, args, kwargs) = pickle.loads(data)
+        return (msg, args, kwargs)
+    
+    
+    def _encode_result(self, error, result):
+        return pickle.dumps((error, result), 2)
+    
+
+    def _encode_call(self, msg, args, kwargs):
+        info = (msg, args, kwargs)
+        data = pickle.dumps(info, 2)
+        return data
+
+        
+    def _decode_result(self, data):
+        error, value = pickle.loads(data)
+        if error:
+            raise error
+        return value
+
+
+    
+class RpcServer(AsyncServer, RpcCallMixIn):
+
+
+    def __init__ (self, root=None, listen_address=('', DEFAULT_PORT), handler_factory=AsyncServerChannel):
+        if root is None:
+            root = self
+        self._root = root
+        AsyncServer.__init__(self, listen_address, handler_factory)
+
+    
+    def call(self, name, *args, **kwargs):
+        method = getattr(self._root, name)
+        return method(*args, **kwargs)
+
+
+    def on_received(self, addr, data):
+        (msg, args, kwargs) = self._decode_call(data)
+        try:
+            result = self.call(msg, *args, **kwargs)
+            error = None
+        except Exception, exc:
+            error = exc
+            result = None
+        return self._encode_result(error, result)
+    
+
+
+
+
+class RpcClient(Client, RpcCallMixIn):
+
+
+    def call(self, msg, *args, **kwargs):
+        send_data = self._encode_call(msg, args, kwargs)
+        receive_data = super(RpcClient, self).call(send_data)
+        return self._decode_result(receive_data)
+
+    
+    def __getattr__(self, name):
+        wrapper = lambda *args, **kwargs: self.call(name, *args, **kwargs)
+        return wrapper
+
+    
+    
+class _ResultCallback(object):
+
+    
+    def __init__(self, client, callback):
+        self.client = client
+        self.callback = callback
+        
+
+    def __call__(self, data):
+        result = self.client._decode_result(data)
+        return self.callback(result)
+
+
+
+class AsyncRpcClient(AsyncClient, RpcCallMixIn):
+    
+
+    def call(self, msg, *args, **kwargs):
+        callback = kwargs.pop("callback")
+        packet = self._encode_call(msg, args, kwargs)
+        AsyncClient.call(self, packet, callback=_ResultCallback(self, callback))
+
+
+    
+    def __getattr__(self, name):
+        wrapper = lambda *args, **kwargs: self.call(name, *args, **kwargs)
+        return wrapper
+
+
+
+
+def async_event_loop():
+    asyncore.loop()
+
+
+
+if __name__ == "__main__":
+    def client():
+        def cb(result):
+            print "got", result
+        c = AsyncRpcClient(("localhost", DEFAULT_PORT))
+        c.echo("foo", callback=cb)
+        c.twice("bar", callback=cb)
+        c.twice(2, callback=cb)
+        c.error("Error!", callback=cb)
+        async_event_loop()
+
+
+    def server():
+        class Service(object):
+            def echo(self, val):
+                return val
+            def twice(self, val):
+                return val * 2
+            def error(self, msg):
+                raise Exception(msg)
+        address = ('', DEFAULT_PORT)
+        server = RpcServer(root=Service(), listen_address=address)
+        print "Server running at", address
+        async_event_loop()
+
+
+    if "-s" in sys.argv:
+        server()
+    else:
+        client()
+

File lab/utils.py

+# -*- coding: utf-8 -*-
+from threading import Thread
+
+_async_method = None
+
+
+def set_async_method(handler):
+    global _async_method
+    _async_method = handler
+
+
+
+def sync_call(func, *args, **kwargs):
+    callback = kwargs.pop("callback")
+    result = func(*args, **kwargs)
+    callback(result)
+
+
+
+class _AsyncThread(Thread):
+
+        
+    def run(self):
+        kwargs = self.kwargs
+        callback = kwargs.pop("callback")
+        func = self.args[0]
+        args = self.args[1:]
+        result = func(*args, **kwargs)
+        callback(result)
+
+
+        
+def threaded_call(func, *args, **kwargs):
+    t = _AsyncThread(args=(func,)+args, kwargs=kwargs)
+    t.start()
+
+    
+def async(func):
+    def wrapper(*args, **kwargs):
+        _async_method(func, *args, **kwargs)
+    return wrapper
+
+
+
+class ServiceRoot(object):
+    pass
+
+
+root = ServiceRoot()
+
+
+def register(func):
+    root.__dict__[func.__name__] = func
+    return func
+
+
+def enable_ipc():
+    from asyncipc import IpcServer, Connection, IpcClient
+
+    def create_server():
+        server = IpcServer(root)
+        return server
+
+    con = Connection(create_server)
+    con.start()
+    
+    return IpcClient(con)
+
+
+    

File pycode/assist.py

-# -*- coding: utf-8 -*-
-import sys
-import os
-import inspect
-from zipimport import zipimporter
-
-import rope.base.libutils
-import rope.contrib.codeassist
-
-from pycode.utils import async, register
-from pycode.project import Project
-
-
-_project_cache = {}
-
-def _get_project(project):
-    if isinstance(project, basestring):
-        root_path = project
-        try:
-            project = _project_cache[root_path]
-        except KeyError:
-            project = _project_cache[root_path] = Project(root_path)
-    return project
-
-
-def _get_resource(project, filename):
-    try:
-        resource = rope.base.libutils.path_to_resource(project._rope_prj, filename.encode('utf-8'))
-    except Exception, _error:
-        resource = None
-    return resource
-
-
-@register
-def completions(project, source_code, offset, filename="XXXunknownXXX.py"):
-	# TODO:
-	#  * include import completitions
-	#  * offer name to override from base after "def " inside a class
-	#  * 
-    project = _get_project(project)
-    resource = _get_resource(project, filename)
-    try:
-        proposals = rope.contrib.codeassist.code_assist(project._rope_prj, source_code, offset, resource)
-        proposals = rope.contrib.codeassist.sorted_proposals(proposals)
-        result = [(proposal.type, proposal.name) for proposal in proposals]
-    except Exception, _error:
-        print _error
-        result = []
-    return result
-
-
-async_completions = async(completions)
-
-
-@register
-def calltip(project, source_code, offset, filename="XXXunknownXXX.py"):
-    project = _get_project(project)
-    resource = _get_resource(project, filename)
-    try:
-        cts = rope.contrib.codeassist.get_calltip(project._rope_prj, source_code, offset, resource, ignore_unknown=False, remove_self=True)
-        if cts is not None:
-            while '..' in cts:
-                cts = cts.replace('..', '.')
-            try:
-                doc_text = rope.contrib.codeassist.get_doc(project._rope_prj, source_code, offset, resource)
-            except Exception, _error:
-                print _error
-                doc_text = ""
-        else:
-            return ("", "")
-        return cts, doc_text
-    except Exception, _error:
-        print _error
-    return ("", "")
-
-
-async_calltip = async(calltip)
-
-
-@register
-def definition_location(project, source_code, offset, filename="XXXunknownXXX.py"):
-    project = _get_project(project)
-    resource = _get_resource(project, filename)
-    try:
-        resource, lineno = rope.contrib.codeassist.get_definition_location(project._rope_prj, source_code, offset, resource)
-        if resource is not None:
-            filename = resource.real_path
-        return filename, lineno
-    except Exception, _error:   
-        print _error
-    return (None, None)
-
-
-
-async_definition_location = async(definition_location)
-
-
-
-
-db = {}
-
-
-def root_modules():
-    """
-    Returns a list containing the names of all the modules available in the
-    folders of the pythonpath.
-    """
-    from time import time
-    TIMEOUT_GIVEUP = 20
-    modules = []
-    if db.has_key('rootmodules'):
-        return db['rootmodules']
-    t = time()
-    for path in sys.path:
-        modules += module_list(path)        
-        if time() - t > TIMEOUT_GIVEUP:
-            print "Module list generation is taking too long, we give up."
-            print
-            db['rootmodules'] = []
-            return []
-        
-    modules += sys.builtin_module_names
-        
-    modules = list(set(modules))
-    if '__init__' in modules:
-        modules.remove('__init__')
-        modules = list(set(modules))
-        db['rootmodules'] = modules
-    return modules
-
-
-def module_list(path):
-    """
-    Return the list containing the names of the modules available in the given
-    folder.
-    """
-    
-    if os.path.isdir(path):
-        folder_list = os.listdir(path)
-    elif path.endswith('.egg'):
-        try:
-            folder_list = [f for f in zipimporter(path)._files]
-        except:
-            folder_list = []
-    else:
-        folder_list = []
-        #folder_list = glob.glob(os.path.join(path,'*'))
-        folder_list = [p for p in folder_list  \
-        if os.path.exists(os.path.join(path, p,'__init__.py'))\
-           or p[-3:] in ('.py','.so')\
-           or p[-4:] in ('.pyc','.pyo','.pyd')]
-            
-    folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
-    return folder_list
-
-
-
-def module_completions(line):
-    """
-    Returns a list containing the completion possibilities for an import line.
-    The line looks like this :
-    'import xml.d'
-    'from xml.dom import'
-    """
-    def tryImport(mod, only_modules=False):
-        def isImportable(module, attr):
-            if only_modules:
-                return inspect.ismodule(getattr(module, attr))
-            else:
-                return not(attr[:2] == '__' and attr[-2:] == '__')
-        try:
-            m = __import__(mod)
-        except:
-            return []
-        completion_list = []
-        mods = mod.split('.')
-        for module in mods[1:]:
-            try:
-                m = getattr(m,module)
-            except:
-                return []
-        if (not hasattr(m, '__file__')) or (not only_modules) or\
-           (hasattr(m, '__file__') and '__init__' in m.__file__):
-            completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
-        completion_list.extend(getattr(m,'__all__',[]))
-        if hasattr(m, '__file__') and '__init__' in m.__file__:
-            completion_list.extend(module_list(os.path.dirname(m.__file__)))
-        completion_list = list(set(completion_list))
-        if '__init__' in completion_list:
-            completion_list.remove('__init__')
-        return completion_list
-        
-    def dotCompletion(mod):
-        if len(mod) < 2:
-            return filter(lambda x: x.startswith(mod[0]), root_modules())
-        
-        completion_list = tryImport('.'.join(mod[:-1]), True)
-        completion_list = filter(lambda x: x.startswith(mod[-1]),
-                                 completion_list)
-        completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
-        return completion_list
-
-    words = line.split(' ')
-    
-    if len(words) == 3 and words[0] == 'from':
-        if words[2].startswith('i') or words[2] == '':
-            return ['import ']
-        else:
-            return []
-            
-    if words[0] == 'import':
-        if ',' == words[-1][-1]:
-            return [' ']
-        
-        mod = words[-1].split('.')
-        return dotCompletion(mod)
-        
-    if len(words) < 3 and (words[0] == 'from'):
-        if len(words) == 1:
-            return root_modules()
-        
-        mod = words[1].split('.')
-        return dotCompletion(mod)
-    
-    if len(words) >= 3 and words[0] == 'from':
-        mod = words[1]
-        completion_list = tryImport(mod)
-        if words[2] == 'import' and words[3] != '':
-            if '(' in words[-1]:
-                words = words[:-2] + words[-1].split('(')
-            if ',' in words[-1]:
-                words = words[:-2] + words[-1].split(',')
-            return filter(lambda x: x.startswith(words[-1]), completion_list)
-        else:
-            return completion_list
-    
-    return []
-        
-
-
-if __name__ == "__main__":
-    # Some simple tests.
-    # Sort operations are done by the completion widget, so we have to
-    # replicate them here.
-    # We've chosen to use xml on all tests because it's on the standard
-    # library. This way we can ensure they work on all plataforms.
-    
-    xml_modules = ['xml.FtCore', 'xml.dom', 'xml.marshal', 'xml.ns', 'xml.parsers', 'xml.sax', 'xml.schema', 'xml.unicode', 'xml.utils', 'xml.xpath', 'xml.xslt']
-    
-    assert sorted(module_completions('import xml.')) == xml_modules
-
-    assert sorted(module_completions('import xml.d')) ==  ['xml.dom']
-
-    assert module_completions('from xml.etree ') == ['import ']
-
-    assert sorted(module_completions('from xml.etree import '),key=str.lower) ==\
-        ['cElementTree', 'ElementInclude', 'ElementPath', 'ElementTree']
-        
-    s = 'from xml.etree.ElementTree import '
-    assert module_completions(s + 'V') == ['VERSION']
-
-    assert sorted(module_completions(s + 'VERSION,XM')) == \
-        ['XML', 'XMLID', 'XMLParser', 'XMLTreeBuilder']
-
-    assert module_completions(s + '(dum') == ['dump']
-
-    assert module_completions(s + '(dump,Su') == ['SubElement']
-

File pycode/asyncipc.py

-# -*- coding: utf-8 -*-
-import atexit
-from multiprocessing import Process, Queue
-from threading import Thread
-
-
-
-class Server(object):
-    
-
-    def __init__(self):
-        pass
-
-    
-    def received(self, data):
-        print "received", len(data), "bytes"
-        return data
-
-
-
-class Connection(object):
-
-
-    def __init__(self, server_factory):
-        self.server_factory = server_factory
-        self.input_queue = Queue()
-        self.output_queue = Queue()
-
-
-    def _listen(self, server_factory, input_queue, output_queue):
-        server = server_factory()
-        while True:
-            item = input_queue.get()
-            if item is None:
-                output_queue.put(None)
-                break
-            result = server.received(item)
-            output_queue.put(result)
-
-        
-    def start(self):
-        self.proc = Process(target=self._listen, args=(self.server_factory, self.input_queue, self.output_queue))
-        self.proc.start()
-
-
-
-
-class Client(object):
-    
-    
-    def __init__(self, connection):
-        self._connection = connection
-        atexit.register(self._exit)
-        self._callback_queue = []
-        self._callback_thread = Thread(target=self._watch_results)
-        self._callback_thread.setDaemon(True)
-        self._callback_thread.start()
-        
-
-    def _exit(self):
-        self._call(None, None)
-
-
-    def _watch_results(self):
-        while True:
-            item = self._connection.output_queue.get()
-            if item is None:
-                break
-            callback = self._callback_queue.pop()
-            callback(item)
-        
-        
-    def _call(self, data, callback):    
-        self._connection.input_queue.put(data)
-        self._callback_queue.insert(0, callback)
-
-
-    def call(self, data, callback):
-        self._call(data, callback)
-
-        
-        
-class IpcCallMixIn(object):
-
-    
-    def _decode_call(self, data):
-        (msg, args, kwargs) = data
-        return (msg, args, kwargs)
-    
-    
-    def _encode_result(self, error, result):
-        return (error, result)
-    
-
-    def _encode_call(self, msg, args, kwargs):
-        info = (msg, args, kwargs)
-        data = info
-        return data
-
-        
-    def _decode_result(self, data):
-        error, value = data
-        if error:
-            raise error
-        return value
-
-
-    
-class IpcServer(Server, IpcCallMixIn):
-
-
-    def __init__(self, root=None):
-        Server.__init__(self)
-        if root is None:
-            root = self
-        self._root = root
-
-
-    def received(self, data):
-        msg, args, kwargs = self._decode_call(data)
-        method = getattr(self._root, msg)
-        try:
-            result = method(*args, **kwargs)
-            error = None
-        except Exception, error:
-            result = None
-        return self._encode_result(error, result)
-
-
-
-
-class _ResultCallback(object):
-
-    
-    def __init__(self, client, callback):
-        self.client = client
-        self.callback = callback
-        
-
-    def __call__(self, data):
-        result = self.client._decode_result(data)
-        return self.callback(result)
-
-        
-        
-class IpcClient(Client, IpcCallMixIn):
-    
-
-    def call(self, msg, *args, **kwargs):
-        callback = kwargs.pop("callback")
-        data = self._encode_call(msg, args, kwargs)
-        return self._call(data, callback=_ResultCallback(self, callback))
-
-    
-    def __getattr__(self, name):
-        method = lambda *args, **kwargs: self.call(name, *args, **kwargs)
-        return method
-
-
-
-if __name__ == "__main__":
-    def cb(result):
-        print "got", repr(result)
-
-    def create_server():
-        class Root(object):
-            def foo(self):
-                return "Foo!"
-            def bar(self, count=1):
-                return "BAR" * count
-        server = IpcServer(Root())
-        return server
-
-    con = Connection(create_server)
-    con.start()
-    client = IpcClient(con)
-    client.foo(callback=cb)
-    client.bar(2, callback=cb)
-    
-    import time; time.sleep(1)
-    print "done"

File pycode/asyncrpc.py

-# -*- coding: utf-8 -*-
-import sys
-import socket
-import exceptions
-import asyncore
-import asynchat
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-
-
-DEFAULT_PORT = 8748
-PACKET_SIZE = 8192
-
-
-
-class NetworkError (exceptions.StandardError):
-    pass
-
-
-class State(object):
-    pass
-
-
-
-class AsyncServerChannel(asynchat.async_chat):
-
-    STATE_LENGTH = State()
-    STATE_PACKET = State()
-
-
-    def __init__ (self, conn, addr, callback):
-        self.addr = addr
-        asynchat.async_chat.__init__ (self, conn)
-        self.pstate = self.STATE_LENGTH
-        self.set_terminator(8)
-        self._receive_buffer = []
-        self.callback = callback
-
-
-    def log (self, *items):
-        print "log", self.__class__, items
-
-
-    def collect_incoming_data (self, data):
-        self._receive_buffer.append (data)
-
-
-    def found_terminator (self):
-        self._receive_buffer, data = [], ''.join(self._receive_buffer)
-
-        if self.pstate is self.STATE_LENGTH:
-            packet_length = int(data, 16)
-            self.set_terminator(packet_length)
-            self.pstate = self.STATE_PACKET
-        else:
-            self.set_terminator (8)
-            self.pstate = self.STATE_LENGTH
-
-            result = self.callback(self.addr, data) or ""
-            
-            self.push(('%08x' % len(result)) + result)
-
-
-
-class AsyncServer(asyncore.dispatcher):
-
-    
-    def __init__ (self, listen_addres=('', DEFAULT_PORT), handler_factory=AsyncServerChannel):
-        asyncore.dispatcher.__init__(self)
-        self._handler_factory = handler_factory
-        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
-        self.set_reuse_addr()
-        self.bind(listen_addres)
-        self.listen(128) 
-
-
-    def handle_accept (self):
-        conn, addr = self.accept()
-        print "Got connect", conn, addr
-        self._handler_factory(conn, addr, self.on_received)
-
-
-    def on_received(self, addr, data):
-        return ''
-
-
-
-    
-        
-
-class AsyncClient(asynchat.async_chat):
-
-    STATE_LENGTH = State()
-    STATE_PACKET = State()
-
-
-    def __init__ (self, address=('', DEFAULT_PORT), keep_alive=False):
-
-        asynchat.async_chat.__init__(self)
-
-        if isinstance(address, basestring):
-            family = socket.AF_UNIX
-        else:
-            family = socket.AF_INET
-
-        self.create_socket (family, socket.SOCK_STREAM)
-        self._address = address
-        self._request_fifo = []
-        self._receive_buffer = []
-        self._pstate = self.STATE_LENGTH
-        self.set_terminator (8)
-        self._connected = False
-        self._keep_alive = keep_alive
-        self.connect (self._address)
-
-
-    def log (self, *items):
-        print "log ", self.__class__, items
-
-
-    def handle_connect (self):
-        self._connected = True
-
-
-    def close(self):
-        self._connected = False
-        self.flush_pending_requests ('lost connection to rpc server')
-        asynchat.async_chat.close(self)
-
-        
-    def flush_pending_requests (self, why):
-        f = self._request_fifo
-        while len(f):
-            callback = f.pop(0)
-            callback (why, None)
-
-
-    def collect_incoming_data (self, data):
-        print "incoming", len(data)
-        self._receive_buffer.append (data)
-
-
-    def found_terminator (self):
-        self._receive_buffer, data = [], ''.join (self._receive_buffer)
-
-        if self._pstate is self.STATE_LENGTH:
-            packet_length = int(data, 16)
-            self.set_terminator(packet_length)
-            self._pstate = self.STATE_PACKET
-        else:
-            if self._keep_alive:
-                self.set_terminator (8)
-                self._pstate = self.STATE_LENGTH
-            callback = self._request_fifo.pop(0)
-            callback(data)
-            if not self._keep_alive:
-                self.close()
-
-
-    def call(self, packet, callback):
-        if not self._connected:
-            # might be a unix socket...
-            family, type = self.family_and_type
-            self.create_socket(family, type)
-            self.connect (self._address)
-        # push the request out the socket
-        self.push ('%08x%s' % (len(packet), packet))
-        self._request_fifo.append(callback)
-
-
-
-
-class Client(object):
-
-
-    def __init__ (self, address, keep_alive=True):
-        self._address = address
-        self._socket = None
-        self._keep_alive = keep_alive
-
-
-    def connect(self):
-        s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
-        s.connect(self._address)
-        self._socket = s
-
-
-    def receive_packet(self):
-        s = self._socket.recv(8)
-        if len(s) != 8:
-            raise NetworkError("Could not receive packet")
-        packet_len = int(s, 16)
-        packet = []
-        while packet_len:
-            data = self._socket.recv(PACKET_SIZE)
-            packet.append (data)
-            packet_len = packet_len - len(data)
-        return ''.join(packet)
-
-
-    def send_packet (self, packet):
-        self._socket.send('%08x%s' % (len(packet), packet))
-
-    
-    def call(self, data):
-        if self._socket is None:
-            self.connect()
-        self.send_packet(data)
-        result = self.receive_packet()
-        if not self._keep_alive:
-            self._socket.close()
-            self._socket = None
-        return result
-
-
-    
-class RpcCallMixIn(object):
-
-    
-    def _decode_call(self, data):
-        (msg, args, kwargs) = pickle.loads(data)
-        return (msg, args, kwargs)
-    
-    
-    def _encode_result(self, error, result):
-        return pickle.dumps((error, result), 2)
-    
-
-    def _encode_call(self, msg, args, kwargs):
-        info = (msg, args, kwargs)
-        data = pickle.dumps(info, 2)
-        return data
-
-        
-    def _decode_result(self, data):
-        error, value = pickle.loads(data)
-        if error:
-            raise error
-        return value
-
-
-    
-class RpcServer(AsyncServer, RpcCallMixIn):
-
-
-    def __init__ (self, root=None, listen_address=('', DEFAULT_PORT), handler_factory=AsyncServerChannel):
-        if root is None:
-            root = self
-        self._root = root
-        AsyncServer.__init__(self, listen_address, handler_factory)
-
-    
-    def call(self, name, *args, **kwargs):
-        method = getattr(self._root, name)
-        return method(*args, **kwargs)
-
-
-    def on_received(self, addr, data):
-        (msg, args, kwargs) = self._decode_call(data)
-        try:
-            result = self.call(msg, *args, **kwargs)
-            error = None
-        except Exception, exc:
-            error = exc
-            result = None
-        return self._encode_result(error, result)
-    
-
-
-
-
-class RpcClient(Client, RpcCallMixIn):
-
-
-    def call(self, msg, *args, **kwargs):
-        send_data = self._encode_call(msg, args, kwargs)
-        receive_data = super(RpcClient, self).call(send_data)
-        return self._decode_result(receive_data)
-
-    
-    def __getattr__(self, name):
-        wrapper = lambda *args, **kwargs: self.call(name, *args, **kwargs)
-        return wrapper
-
-    
-    
-class _ResultCallback(object):
-
-    
-    def __init__(self, client, callback):
-        self.client = client
-        self.callback = callback
-        
-
-    def __call__(self, data):
-        result = self.client._decode_result(data)
-        return self.callback(result)
-
-
-
-class AsyncRpcClient(AsyncClient, RpcCallMixIn):
-    
-
-    def call(self, msg, *args, **kwargs):
-        callback = kwargs.pop("callback")
-        packet = self._encode_call(msg, args, kwargs)
-        AsyncClient.call(self, packet, callback=_ResultCallback(self, callback))
-
-
-    
-    def __getattr__(self, name):
-        wrapper = lambda *args, **kwargs: self.call(name, *args, **kwargs)
-        return wrapper
-
-
-
-
-def async_event_loop():
-    asyncore.loop()
-
-
-
-if __name__ == "__main__":
-    def client():
-        def cb(result):
-            print "got", result
-        c = AsyncRpcClient(("localhost", DEFAULT_PORT))
-        c.echo("foo", callback=cb)
-        c.twice("bar", callback=cb)
-        c.twice(2, callback=cb)
-        c.error("Error!", callback=cb)
-        async_event_loop()
-
-
-    def server():
-        class Service(object):
-            def echo(self, val):
-                return val
-            def twice(self, val):
-                return val * 2
-            def error(self, msg):
-                raise Exception(msg)
-        address = ('', DEFAULT_PORT)
-        server = RpcServer(root=Service(), listen_address=address)
-        print "Server running at", address
-        async_event_loop()
-
-
-    if "-s" in sys.argv:
-        server()
-    else:
-        client()
-

File pycode/checks.py

 
 
 def tasks(project, source_code):
-	"""Find tasks in source code (TODO, FIXME, XXX, ...)"""
-	results = []
-	for line, text in enumerate(source_code.splitlines()):
-		for todo in re.findall(TASKS_PATTERN, text):
-			results.append((todo, line+1))
-	return results
-	
+    """Find tasks in source code (TODO, FIXME, XXX, ...)"""
+    results = []
+    for line, text in enumerate(source_code.splitlines()):
+        for todo in re.findall(TASKS_PATTERN, text):
+            results.append((todo, line+1))
+    return results
+
 
 
 def pyflakes(project, source_code, filename=None):
-	"""
-	Check source code with pyflakes
-	Returns an empty list if pyflakes is not installed
-	"""
-	if filename is None:
-		filename = '<string>'
-		source_code += '\n'
-	import _ast
-	from pyflakes.checker import Checker
-	# First, compile into an AST and handle syntax errors.
-	try:
-		tree = compile(source_code, filename, "exec", _ast.PyCF_ONLY_AST)
-	except SyntaxError, value:
-		# If there's an encoding problem with the file, the text is None.
-		if value.text is None:
-			return []
-		else:
-			return [(value.args[0], value.lineno)]
-	else:
-		# Okay, it's syntactically valid.  Now check it.
-		w = Checker(tree, filename)
-		w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
-		results = []
+    """
+    Check source code with pyflakes
+    Returns an empty list if pyflakes is not installed
+    """
+    if filename is None:
+        filename = '<string>'
+        source_code += '\n'
+    import _ast
+    from pyflakes.checker import Checker
+    # First, compile into an AST and handle syntax errors.
+    try:
+        tree = compile(source_code, filename, "exec", _ast.PyCF_ONLY_AST)
+    except SyntaxError, value:
+        # If there's an encoding problem with the file, the text is None.
+        if value.text is None:
+            return []
+        else:
+            return [(value.args[0], value.lineno)]
+    else:
+        # Okay, it's syntactically valid.  Now check it.
+        w = Checker(tree, filename)
+        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+        results = []
         lines = source_code.splitlines()
-		for warning in w.messages:
-			if 'analysis:ignore' not in lines[warning.lineno-1]:
-				results.append((warning.message % warning.message_args, warning.lineno))
-		return results
-			
+        for warning in w.messages:
+            if 'analysis:ignore' not in lines[warning.lineno-1]:
+                results.append((warning.message % warning.message_args, warning.lineno))
+        return results
+
 
 
 def _check_external(args, source_code, filename=None, options=None):
     """
-	Check source code with checker defined with *args* (list)
+    Check source code with checker defined with *args* (list)
     Returns an empty list if checker is not installed
-	"""
+    """
     if args is None:
         return []
     if options is not None:
     return output
 
 
-def pep8(project, source_code, filename=None):
+
+def pep8_external(project, source_code, filename=None):
     """Check source code with pep8"""
     args = ['pep8']
     results = []
+    source = source_code.splitline()
     for line in _check_external(args, source_code, filename=filename, options=['-r']):
         lineno = int(re.search(r'(\:[\d]+\:)', line).group()[1:-1])
-        if 'analysis:ignore' not in lines[lineno-1]:
+        if 'analysis:ignore' not in source[lineno-1]:
             message = line[line.find(': ')+2:]
             results.append((message, lineno))
     return results
 
 
 
-
-def lint(project, source_code, filename=None):    
+def lint_external(project, source_code, filename=None):
     args = ["pylint"]
     options = ["-f parseable", "-r", "-n", "--disable-msg-cat=C,R"]
     results = []
                 msg = "Error"    
             if func:
                 line = re.sub("\\[([WE])(, (.+?))?\\]",
-                              "%s (%s):" % (msg, func), message)
+                              "%s (%s):" % (msg, func), msg)
             else:
                 line = re.sub("\\[([WE])?\\]", "%s:" % msg, line)
             results.append((msg, line))

File pycode/fileio.py

+# -*- coding: utf-8 -*-
+import re
+
+
+ENCODING = "utf-8"
+STRIP_BLANK_LINES = True
+UNTABIFY = True
+TAB_WIDTH = 4
+
+
+def guess_encoding(source, peek_lines=100, encoding=ENCODING):
+    """
+    :param source: source code as byte string
+    :param peek_lines: number of lines to check for encoding hint
+    :param encoding: default encoding ist utf-8
+    :return: name of encoding
+    """
+    lines = source.splitlines()
+    enc_match = re.compile("#\s*-\*-\s*coding:\s*(.*?)\s*-\*-").match
+    for i, l in enumerate(lines):
+        if i > peek_lines:
+            break
+        if not l.startswith("#"):
+            break
+        found = enc_match(l)
+        if found:
+            encoding = found.group(1)
+            print "found encoding", repr(encoding), "for", filename
+            break
+    return encoding
+
+
+
+def load(filename):
+    """
+    :param filename: existing filenname
+    :return: source code as unicode string
+    """
+    source = open(filename).read()
+    encoding = guess_encoding(source)
+    return source.decode(encoding)
+
+
+
+def save(filename, source, tab_width=TAB_WIDTH, strip_blank_lines=STRIP_BLANK_LINES, untabify=UNTABIFY):
+    """
+    :param filename:
+    :param source:
+    :param tab_width:
+    :param strip_blank_lines:
+    :param untabify:
+    :return:
+    """
+    assert isinstance(source, unicode), "expected source code as unicode string"
+    encoding = guess_encoding(source)
+    tab = u" " * tab_width
+    lines = source.splitlines()
+    out = []
+    for l in lines:
+        if strip_blank_lines and len(l.strip()) == 0:
+            l = u""
+        elif untabify:
+            l = l.replace(u"\t", tab)
+        out.append(out)
+    source = u"\n".join(out).encode(encoding)
+
+    f = open(filename, "w")
+    f.write(source)
+    f.close()

File pycode/indenter.py

     
     def _indents_caused_by_current_stmt(self, current_line):
         new_indent = 0
-        if self._strip(current_line) == 'else:':
+        stripped_current_line = self._strip(current_line)
+        if stripped_current_line == 'else:':
             new_indent -= self.indents
-        if self._strip(current_line) == 'finally:':
+        elif stripped_current_line == 'finally:':
             new_indent -= self.indents
         if self._startswith(current_line, ('elif',)):
             new_indent -= self.indents
-        if self._startswith(current_line, ('except',)) and \
-           self._strip(current_line).endswith(':'):
+        elif self._startswith(current_line, ('except',)) and \
+           stripped_current_line.endswith(':'):
             new_indent -= self.indents
         return new_indent
 

File pycode/onlinehelp.py

     }
     context_wrapper = ContextWrapper
 
+    # TODO: web-scraping content with jquery-like api
 
     def match(self, fully_qualified_name):
         found = re.match(self.pattern, fully_qualified_name)
 
 
 
+class WxClassHelp(CodeHelp):
+    # api documentation used epydoc
+    pattern = "wx\.(?P<class>\w+)\.(?P<method>\w+)"
+    url = "http://wxpython.org/docs/api/wx.%(class)s-class.html#%(method)s"
+
+    
+class WxModuleHelp(CodeHelp):
+    pattern = "wx\.(?P<module>\w+)\.(?P<class>\w+)\.(?P<method>\w+)"
+    url = "http://wxpython.org/docs/api/wx.%(module)s.%(class)s-class.html#%(method)s"
+
 
 help_providers = [
   PyQtHelp(),
+  WxModuleHelp(),
+  WxClassHelp(),
   CodeHelp(),
 ]
+
 def help_for(name):
     for h in help_providers:
         url = h.match(name)
         if url:
             return url
+
         
-print help_for("re.sub")
-print help_for("PyQt4.QtGui.QPushButton.setText")
-
+if __name__ == "__main__":
+    print help_for("re.sub")
+    print help_for("PyQt4.QtGui.QPushButton.setText")
+    print help_for("wx.Window.Create")
+    print help_for("wx.richtext.RichTextObject.__init__")

File pycode/project.py

 # -*- coding: utf-8 -*-
-import rope.base.project
-
-
-ROPE_PREFS = {
-    'automatic_soa': True,
-	'automatic_soi': True,
-	'ignore_syntax_errors': True,
-	'ignore_bad_imports': True,
-	'soa_followed_calls': 3,
-	'perform_doa': True,
-	'import_dynload_stdmods': True,
-	'extension_modules': [
-		"PyQt4", "PyQt4.QtGui", "QtGui", "PyQt4.QtCore", "QtCore",
-		"PyQt4.QtScript", "QtScript", "os.path", "numpy", "scipy", "PIL",
-		"OpenGL", "array", "audioop", "binascii", "cPickle", "cStringIO",
-		"cmath", "collections", "datetime", "errno", "exceptions", "gc",
-		"imageop", "imp", "itertools", "marshal", "math", "mmap", "msvcrt", "multiprocessing",
-		"nt", "operator", "os", "parser", "rgbimg", "signal", "strop", "sys",
-		"thread", "time", "wx", "wxPython", "xxsubtype", "zipimport", "zlib"
-	],
-}
-
-
-
-class Project(object):
-	
-
-	def __init__(self, root_path):
-		self.root_path = root_path
-		try:
-			self._rope_prj = rope.base.project.Project(self.root_path, **ROPE_PREFS)
-		except Exception, _error:
-			print "Could not create rope project", _error
-			import traceback
-			traceback.print_exc()
-			self._rope_prj = rope.base.project.get_no_project()
-			self._rope_prj.root = None
-
-		
-	def validate(self):
-		self._rope_prj.validate(self._rope_prj.root)
-		
-		
-	def close(self):
-		self._rope_prj.close()

File pycode/pyqtfrontend.py

 from PyQt4.QtGui import (
     QToolTip, QTreeWidget, QTreeWidgetItem, QTextCursor, QFrame, QIcon, QCompleter, QTableView, QAbstractItemView, QKeyEvent)
 
-from pycode.assist import async_completions, async_calltip, async_definition_location
+from pycode.ropeassist import async_completions, async_calltip, async_definition_location
 from pycode.indenter import PythonCodeIndenter
 
 from pycode.utils import set_async_method, enable_ipc
 class PyCode(object):
     
 
-    def __init__(self, project, textedit, filename=None):
-        self._prj = project
+    def __init__(self, project_folder, textedit, filename=None):
+        self._prj = project_folder
         self._textedit = textedit
         self._filename = filename
         self._completer = Completer(self._textedit)

File pycode/ropeassist.py

+# -*- coding: utf-8 -*-
+import sys
+import os
+import inspect
+from zipimport import zipimporter
+
+import rope.base.libutils
+import rope.base.project
+import rope.contrib.codeassist
+
+from pycode.utils import async, register
+
+
+
+
+ROPE_DEFAULT_PREFS = {
+    'automatic_soa': True,
+    'automatic_soi': True,
+    'ignore_syntax_errors': True,
+    'ignore_bad_imports': True,
+    'soa_followed_calls': 3,
+    'perform_doa': True,
+    'import_dynload_stdmods': True,
+    'extension_modules': [
+        "PyQt4", "PyQt4.QtGui", "QtGui", "PyQt4.QtCore", "QtCore",
+        "PyQt4.QtScript", "QtScript", "os.path", "numpy", "scipy", "PIL",
+        "OpenGL", "array", "audioop", "binascii", "cPickle", "cStringIO",
+        "cmath", "collections", "datetime", "errno", "exceptions", "gc",
+        "imageop", "imp", "itertools", "marshal", "math", "mmap", "msvcrt", "multiprocessing",
+        "nt", "operator", "os", "parser", "rgbimg", "signal", "strop", "sys",
+        "thread", "time", "wx", "wxPython", "xxsubtype", "zipimport", "zlib"
+    ],
+    }
+
+
+
+class Project(object):
+
+
+    def __init__(self, root_path):
+        self.root_path = root_path
+        try:
+            self._rope_prj = rope.base.project.Project(self.root_path, **ROPE_DEFAULT_PREFS)
+        except Exception, _error:
+            print "Could not create rope project", _error
+            import traceback
+            traceback.print_exc()
+            self._rope_prj = rope.base.project.get_no_project()
+            self._rope_prj.root = None
+
+
+    def validate(self):
+        self._rope_prj.validate(self._rope_prj.root)
+
+
+    def close(self):
+        self._rope_prj.close()
+
+
+_project_cache = {}
+
+def _get_project(project):
+    if isinstance(project, basestring):
+        root_path = project
+        try:
+            project = _project_cache[root_path]
+        except KeyError:
+            project = _project_cache[root_path] = Project(root_path)
+    return project
+
+
+def _get_resource(project, filename):
+    try:
+        resource = rope.base.libutils.path_to_resource(project._rope_prj, filename.encode('utf-8'))
+    except Exception, _error:
+        resource = None
+    return resource
+
+
+@register
+def completions(project, source_code, offset, filename="XXXunknownXXX.py"):
+    # TODO:
+    #  * include import completitions
+    #  * offer name to override from base after "def " inside a class
+    #  *
+    project = _get_project(project)
+    resource = _get_resource(project, filename)
+    try:
+        proposals = rope.contrib.codeassist.code_assist(project._rope_prj, source_code, offset, resource)
+        proposals = rope.contrib.codeassist.sorted_proposals(proposals)
+        result = [(proposal.type, proposal.name) for proposal in proposals]
+    except Exception, _error:
+        print _error
+        result = []
+    return result
+
+
+def calltip(project, source_code, offset, filename="XXXunknownXXX.py"):
+    project = _get_project(project)
+    resource = _get_resource(project, filename)
+    try:
+        cts = rope.contrib.codeassist.get_calltip(project._rope_prj, source_code, offset, resource, ignore_unknown=False, remove_self=True)
+        if cts is not None:
+            while '..' in cts:
+                cts = cts.replace('..', '.')
+            try:
+                doc_text = rope.contrib.codeassist.get_doc(project._rope_prj, source_code, offset, resource)
+            except Exception, _error:
+                print _error
+                doc_text = ""
+        else:
+            return ("", "")
+        return cts, doc_text
+    except Exception, _error:
+        print _error
+    return ("", "")
+
+
+
+def definition_location(project, source_code, offset, filename="XXXunknownXXX.py"):
+    project = _get_project(project)
+    resource = _get_resource(project, filename)
+    try:
+        resource, lineno = rope.contrib.codeassist.get_definition_location(project._rope_prj, source_code, offset, resource)
+        if resource is not None:
+            filename = resource.real_path
+        return filename, lineno
+    except Exception, _error:   
+        print _error
+    return (None, None)
+
+
+
+async_definition_location = async(definition_location)
+
+
+
+
+db = {}
+
+
+def root_modules():
+    """
+    Returns a list containing the names of all the modules available in the
+    folders of the pythonpath.
+    """
+    from time import time
+    TIMEOUT_GIVEUP = 20
+    modules = []
+    if db.has_key('rootmodules'):
+        return db['rootmodules']
+    t = time()
+    for path in sys.path:
+        modules += module_list(path)        
+        if time() - t > TIMEOUT_GIVEUP:
+            print "Module list generation is taking too long, we give up."
+            print
+            db['rootmodules'] = []
+            return []
+        
+    modules += sys.builtin_module_names
+        
+    modules = list(set(modules))
+    if '__init__' in modules:
+        modules.remove('__init__')
+        modules = list(set(modules))
+        db['rootmodules'] = modules
+    return modules
+
+
+def module_list(path):
+    """
+    Return the list containing the names of the modules available in the given
+    folder.
+    """
+    
+    if os.path.isdir(path):
+        folder_list = os.listdir(path)
+    elif path.endswith('.egg'):
+        try:
+            folder_list = [f for f in zipimporter(path)._files]
+        except:
+            folder_list = []
+    else:
+        folder_list = []
+        #folder_list = glob.glob(os.path.join(path,'*'))
+        folder_list = [p for p in folder_list  \
+        if os.path.exists(os.path.join(path, p,'__init__.py'))\
+           or p[-3:] in ('.py','.so')\
+           or p[-4:] in ('.pyc','.pyo','.pyd')]
+            
+    folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
+    return folder_list
+
+
+
+def module_completions(line):
+    """
+    Returns a list containing the completion possibilities for an import line.
+    The line looks like this :
+    'import xml.d'
+    'from xml.dom import'
+    """
+    def tryImport(mod, only_modules=False):
+        def isImportable(module, attr):
+            if only_modules:
+                return inspect.ismodule(getattr(module, attr))
+            else:
+                return not(attr[:2] == '__' and attr[-2:] == '__')
+        try:
+            m = __import__(mod)
+        except:
+            return []
+        completion_list = []
+        mods = mod.split('.')
+        for module in mods[1:]:
+            try:
+                m = getattr(m,module)
+            except:
+                return []
+        if (not hasattr(m, '__file__')) or (not only_modules) or\
+           (hasattr(m, '__file__') and '__init__' in m.__file__):
+            completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
+        completion_list.extend(getattr(m,'__all__',[]))
+        if hasattr(m, '__file__') and '__init__' in m.__file__:
+            completion_list.extend(module_list(os.path.dirname(m.__file__)))
+        completion_list = list(set(completion_list))
+        if '__init__' in completion_list:
+            completion_list.remove('__init__')
+        return completion_list
+        
+    def dotCompletion(mod):
+        if len(mod) < 2:
+            return filter(lambda x: x.startswith(mod[0]), root_modules())
+        
+        completion_list = tryImport('.'.join(mod[:-1]), True)
+        completion_list = filter(lambda x: x.startswith(mod[-1]),
+                                 completion_list)
+        completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
+        return completion_list
+
+    words = line.split(' ')
+    
+    if len(words) == 3 and words[0] == 'from':
+        if words[2].startswith('i') or words[2] == '':
+            return ['import ']
+        else:
+            return []
+            
+    if words[0] == 'import':
+        if ',' == words[-1][-1]:
+            return [' ']
+        
+        mod = words[-1].split('.')
+        return dotCompletion(mod)
+        
+    if len(words) < 3 and (words[0] == 'from'):
+        if len(words) == 1:
+            return root_modules()
+        
+        mod = words[1].split('.')
+        return dotCompletion(mod)
+    
+    if len(words) >= 3 and words[0] == 'from':
+        mod = words[1]
+        completion_list = tryImport(mod)
+        if words[2] == 'import' and words[3] != '':
+            if '(' in words[-1]:
+                words = words[:-2] + words[-1].split('(')
+            if ',' in words[-1]:
+                words = words[:-2] + words[-1].split(',')
+            return filter(lambda x: x.startswith(words[-1]), completion_list)
+        else:
+            return completion_list
+