Blob Blame History Raw
# -*- coding: utf-8 -*-
#
#  completion.py - commander
#
#  Copyright (C) 2010 - Jesse van den Kieboom
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
#  Boston, MA 02110-1301, USA.

import commander.commands as commands
import bisect
import sys
import os
import re

from xml.sax import saxutils

__all__ = ['command', 'filename']

def _common_prefix_part(first, second):
    length = min(len(first), len(second))

    for i in range(0, length):
        if first[i] != second[i]:
            return first[:i]

    return first[:length]

def common_prefix(args, sep=None):
    # A common prefix can be something like
    # first: some-thing
    # second: sho-tar
    # res: s-t
    args = list(args)

    if not args:
        return ''

    if len(args) == 1:
        return str(args[0])

    first = str(args[0])
    second = str(args[1])

    if not sep:
        ret = _common_prefix_part(first, second)
    else:
        first = first.split(sep)
        second = second.split(sep)
        ret = []

        for i in range(0, min(len(first), len(second))):
            ret.append(_common_prefix_part(first[i], second[i]))

        ret = sep.join(ret)

    del args[0]
    args[0] = ret

    return common_prefix(args, sep)

def _expand_commands(cmds):
    if not cmds:
        cmds.extend(commands.Commands().modules())
        return

    old = list(cmds)
    del cmds[:]

    # Expand 'commands' to all the respective subcommands

    for cmd in old:
        for c in cmd.commands():
            bisect.insort(cmds, c)

def _filter_command(cmd, subs):
    parts = cmd.name.split('-')

    if len(subs) > len(parts):
        return False

    for i in range(0, len(subs)):
        if not parts[i].startswith(subs[i]):
            return False

    return True

def _filter_commands(cmds, subs):
    # See what parts of cmds still match the parts in subs
    idx = bisect.bisect_left(cmds, subs[0])
    ret = []

    while idx < len(cmds):
        if not cmds[idx].name.startswith(subs[0]):
            break

        if _filter_command(cmds[idx], subs):
            ret.append(cmds[idx])

        idx += 1

    return ret

def single_command(words, idx):
    ret = command(words, idx)

    if not ret:
        return None

    ret[0] = list(filter(lambda x: x.method, ret[0]))

    if not ret[0]:
        return None

    return ret[0][0]

def command(words, idx):
    s = words[idx].strip()

    parts = s.split('.')
    cmds = []

    for i in parts:
        # Expand all the parents to their child commands
        _expand_commands(cmds)

        if not cmds:
            return None

        subs = i.split('-')
        cmds = _filter_commands(cmds, subs)

        if not cmds:
            return None

    if not cmds:
        return None

    if len(parts) == 1:
        completed = common_prefix(cmds)
    else:
        completed = '.'.join(parts[0:-1]) + '.' + common_prefix(cmds, '-')

    return [cmds, completed]

def _file_color(path):
    if os.path.isdir(path):
        format = '<span color="#799ec6">%s</span>'
    else:
        format = '%s'

    return format % (saxutils.escape(os.path.basename(path)),)

def _sort_nicely(l):
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]

    l.sort(key=alphanum_key)

def filename(words, idx, view):
    prefix = os.path.dirname(words[idx])
    partial = os.path.expanduser(words[idx])

    doc = view.get_buffer()

    if not doc.is_untitled():
        root = os.path.dirname(doc.get_file().get_location().get_path())
    else:
        root = os.path.expanduser('~/')

    if not os.path.isabs(partial):
        partial = os.path.join(root, partial)

    dirname = os.path.dirname(partial)

    try:
        files = os.listdir(dirname)
    except OSError:
        return None

    base = os.path.basename(partial)
    ret = []
    real = []

    for f in files:
        if f.startswith(base) and (base or not f.startswith('.')):
            real.append(os.path.join(dirname, f))
            ret.append(os.path.join(prefix, f))

    _sort_nicely(real)

    if len(ret) == 1:
        if os.path.isdir(real[0]):
            after = '/'
        else:
            after = ' '

        return ret, ret[0], after
    else:
        return list(map(lambda x: _file_color(x), real), common_prefix(ret))

def words(ret):
    def decorator(words, idx):
        rr = list(filter(lambda x: x.startswith(words[idx]), ret))
        return rr, common_prefix(rr)

    return decorator

# ex:ts=4:et