"""AutoComplete.py - An IDLE extension for automatically completing names.



This extension can complete either attribute names of file names. It can pop

a window with all available names, for the user to select from.

"""

import os

import sys

import string



from configHandler import idleConf



import AutoCompleteWindow

from HyperParser import HyperParser



import __main__



# This string includes all chars that may be in a file name (without a path

# separator)

FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-"

# This string includes all chars that may be in an identifier

ID_CHARS = string.ascii_letters + string.digits + "_"



# These constants represent the two different types of completions

COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)



SEPS = os.sep

if os.altsep:  # e.g. '/' on Windows...

    SEPS += os.altsep



class AutoComplete:



    menudefs = [

        ('edit', [

            ("Show Completions", "<<force-open-completions>>"),

        ])

    ]



    popupwait = idleConf.GetOption("extensions", "AutoComplete",

                                   "popupwait", type="int", default=0)



    def __init__(self, editwin=None):

        self.editwin = editwin

        if editwin is None:  # subprocess and test

            return

        self.text = editwin.text

        self.autocompletewindow = None



        # id of delayed call, and the index of the text insert when the delayed

        # call was issued. If _delayed_completion_id is None, there is no

        # delayed call.

        self._delayed_completion_id = None

        self._delayed_completion_index = None



    def _make_autocomplete_window(self):

        return AutoCompleteWindow.AutoCompleteWindow(self.text)



    def _remove_autocomplete_window(self, event=None):

        if self.autocompletewindow:

            self.autocompletewindow.hide_window()

            self.autocompletewindow = None



    def force_open_completions_event(self, event):

        """Happens when the user really wants to open a completion list, even

        if a function call is needed.

        """

        self.open_completions(True, False, True)



    def try_open_completions_event(self, event):

        """Happens when it would be nice to open a completion list, but not

        really necessary, for example after an dot, so function

        calls won't be made.

        """

        lastchar = self.text.get("insert-1c")

        if lastchar == ".":

            self._open_completions_later(False, False, False,

                                         COMPLETE_ATTRIBUTES)

        elif lastchar in SEPS:

            self._open_completions_later(False, False, False,

                                         COMPLETE_FILES)



    def autocomplete_event(self, event):

        """Happens when the user wants to complete his word, and if necessary,

        open a completion list after that (if there is more than one

        completion)

        """

        if hasattr(event, "mc_state") and event.mc_state:

            # A modifier was pressed along with the tab, continue as usual.

            return

        if self.autocompletewindow and self.autocompletewindow.is_active():

            self.autocompletewindow.complete()

            return "break"

        else:

            opened = self.open_completions(False, True, True)

            if opened:

                return "break"



    def _open_completions_later(self, *args):

        self._delayed_completion_index = self.text.index("insert")

        if self._delayed_completion_id is not None:

            self.text.after_cancel(self._delayed_completion_id)

        self._delayed_completion_id = \

            self.text.after(self.popupwait, self._delayed_open_completions,

                            *args)



    def _delayed_open_completions(self, *args):

        self._delayed_completion_id = None

        if self.text.index("insert") != self._delayed_completion_index:

            return

        self.open_completions(*args)



    def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):

        """Find the completions and create the AutoCompleteWindow.

        Return True if successful (no syntax error or so found).

        if complete is True, then if there's nothing to complete and no

        start of completion, won't open completions and return False.

        If mode is given, will open a completion list only in this mode.

        """

        # Cancel another delayed call, if it exists.

        if self._delayed_completion_id is not None:

            self.text.after_cancel(self._delayed_completion_id)

            self._delayed_completion_id = None



        hp = HyperParser(self.editwin, "insert")

        curline = self.text.get("insert linestart", "insert")

        i = j = len(curline)

        if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):

            self._remove_autocomplete_window()

            mode = COMPLETE_FILES

            while i and curline[i-1] in FILENAME_CHARS:

                i -= 1

            comp_start = curline[i:j]

            j = i

            while i and curline[i-1] in FILENAME_CHARS + SEPS:

                i -= 1

            comp_what = curline[i:j]

        elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):

            self._remove_autocomplete_window()

            mode = COMPLETE_ATTRIBUTES

            while i and curline[i-1] in ID_CHARS:

                i -= 1

            comp_start = curline[i:j]

            if i and curline[i-1] == '.':

                hp.set_index("insert-%dc" % (len(curline)-(i-1)))

                comp_what = hp.get_expression()

                if not comp_what or \

                   (not evalfuncs and comp_what.find('(') != -1):

                    return

            else:

                comp_what = ""

        else:

            return



        if complete and not comp_what and not comp_start:

            return

        comp_lists = self.fetch_completions(comp_what, mode)

        if not comp_lists[0]:

            return

        self.autocompletewindow = self._make_autocomplete_window()

        self.autocompletewindow.show_window(comp_lists,

                                            "insert-%dc" % len(comp_start),

                                            complete,

                                            mode,

                                            userWantsWin)

        return True



    def fetch_completions(self, what, mode):

        """Return a pair of lists of completions for something. The first list

        is a sublist of the second. Both are sorted.



        If there is a Python subprocess, get the comp. list there.  Otherwise,

        either fetch_completions() is running in the subprocess itself or it

        was called in an IDLE EditorWindow before any script had been run.



        The subprocess environment is that of the most recently run script.  If

        two unrelated modules are being edited some calltips in the current

        module may be inoperative if the module was not the last to run.

        """

        try:

            rpcclt = self.editwin.flist.pyshell.interp.rpcclt

        except:

            rpcclt = None

        if rpcclt:

            return rpcclt.remotecall("exec", "get_the_completion_list",

                                     (what, mode), {})

        else:

            if mode == COMPLETE_ATTRIBUTES:

                if what == "":

                    namespace = __main__.__dict__.copy()

                    namespace.update(__main__.__builtins__.__dict__)

                    bigl = eval("dir()", namespace)

                    bigl.sort()

                    if "__all__" in bigl:

                        smalll = eval("__all__", namespace)

                        smalll.sort()

                    else:

                        smalll = filter(lambda s: s[:1] != '_', bigl)

                else:

                    try:

                        entity = self.get_entity(what)

                        bigl = dir(entity)

                        bigl.sort()

                        if "__all__" in bigl:

                            smalll = entity.__all__

                            smalll.sort()

                        else:

                            smalll = filter(lambda s: s[:1] != '_', bigl)

                    except:

                        return [], []



            elif mode == COMPLETE_FILES:

                if what == "":

                    what = "."

                try:

                    expandedpath = os.path.expanduser(what)

                    bigl = os.listdir(expandedpath)

                    bigl.sort()

                    smalll = filter(lambda s: s[:1] != '.', bigl)

                except OSError:

                    return [], []



            if not smalll:

                smalll = bigl

            return smalll, bigl



    def get_entity(self, name):

        """Lookup name in a namespace spanning sys.modules and __main.dict__"""

        namespace = sys.modules.copy()

        namespace.update(__main__.__dict__)

        return eval(name, namespace)

