# bindings.py                    -*- coding: iso-8859-1 -*-
# Copyright (c) 2005 Günter Milde
# Released under the terms of the GNU General Public License (ver. 2 or later)

"""
A dictionary of LyX keybindings

Scans recursively for `\bind_file`s and stores keybindings in a dictionary
`bindings`. Starts with the preferences file `rcfile`.

Uses the following defaults from LyX.constants.py
   SYSTEM_DIR, USER_DIR, LYXRC_FILE
Change this variables in `~/.lyx/scripts/python/config.py`, if your settings differ

Doesnot currently 'normalize' the keynames, i.e. if different spellings for 
the same key occure in different bind-files, both bindings will be reported.
(Normally, the latter binding will overwrite the first one, get_bindings()
will only overwrite bindings with equal spelling.)

TODO: emulate the keyname 'normalization'
"""

import os, sys, logging, re
from LyX import __path__

# Customizable values
from constants import SYSTEM_DIR, USER_DIR, LYXRC_FILE, LOG_LEVEL

# set up the logger instance
logger = logging.getLogger("bindings")
logging.basicConfig()
#set verbosity to show all messages of severity >= LOG_LEVEL
logger.setLevel(LOG_LEVEL) 

# debugging
# logger.setLevel(logging.DEBUG)

def bindfile_path(filename):
    """Return the full path for the bind file `filename`
    
    Return empty string, if file is not found.
    
    Like the LyX binary, this looks first in USER_DIR and then in SYSTEM_DIR
    """
    # Add default extension ('.bind') if no extension is given
    ext = os.path.splitext(filename)[1]
    if not ext:
        filename += '.bind'
    for base_path in (USER_DIR, SYSTEM_DIR):
        path = os.path.join(base_path, 'bind', filename)
        if os.path.isfile(path):
            return path
    logger.warning("bind-file %s not found in %s or %s"%
                   (filename, USER_DIR, SYSTEM_DIR))
    return ''


def get_bindings(path=LYXRC_FILE):
    """Return a list of bindfiles and a dictionary of keybindings.
    
    Scan `path` recursively for `\bind_file`s, return
    
      * a list of included bind-files, and
      * a "keyname: binding" dictionary of key-bindings
    """
    # if not path: 
    #     return {}
    bindings = {}
    bindfiles = []
    if path != LYXRC_FILE:
        path = bindfile_path(path)
    logger.debug(" scanning '%s'"%path)
    for line in file(path):
        # logger.debug(" get_bindigs: parsing %s"%line.strip)
        if line.startswith(r'\bind_file'):
            (command, bindfile) = line.split(None, 1)
            bindfile = bindfile_path(bindfile.strip())
            if bindfile:
                bindfiles.append(bindfile)
                bf, kb = get_bindings(bindfile)
                bindfiles += bf
                bindings.update(kb)
        elif line.startswith(r'\bind'):
            (command, key, binding) = line.split('"', 2)
            bindings[key] = binding.strip()
    return bindfiles, bindings


# extended Help functions (do not need a running LyX)
# ---------------------------------------------------


def apropos(pattern, path="doc/lfuns.txt"):
    """re.search for `pattern` in text file `path`, return list of matches
    
    A non-absolute pathname will be expanded relative to the packages
    __path__.
    
    Together with the file lfuns.txt in the doc subdir, this is a hack to get
    a behaviour similar to JEDs apropos(), i.e. return a list of 
    lyx-functions matching `pattern`.
    """
    # get the description file
    for root in  __path__ + [""]:
        try:
            fp = file(os.path.join(root, path))
            break
        except IOError:
            pass
    # filter relevant lines
    pattern_re = re.compile(pattern)
    return [line for line in fp if pattern_re.search(line)]


def where_is(lfun, path=LYXRC_FILE):
    """Return dictionary with the key(s) `lfun` is bound to
    
    Does a regexp search for `lfun` in the keybindings dictionary values
    """
    lfun_re = re.compile(lfun)
    bindings = {}
    for key, value in get_bindings(path)[1].iteritems():
        if lfun_re.search(value):
            bindings[key] = value
    return bindings


def showkey(key, path=LYXRC_FILE):
    """Return lfun bound to `key`
    
    Doesnot currently 'normalize' the keynames, i.e. only works for 
    keynames as found in the *.bind files
    """
    try:
        return get_bindings(path)[1][key]
    except KeyError:
        return "Key %s undefined (maybe different spelling?)"%key


def print_bindings(bindings, verbose=False):
    """Pretty print the keybindings dictionary with adjusted columns and
    TAB separated keyname and function (to facilitate reading as CSV data)
    """
    if not bindings:
        print "no bindings"
        return
    keys = bindings.keys()
    keys.sort()
    # sort case insensitive
    # tmplist = [(key.lower(), key) for key in keys]
    # tmplist.sort()
    # keys = [key for (lowkey, key) in tmplist]
    max_keylen = max(map(len, keys))
    for key in keys:
        if verbose or (bindings[key] != '"self-insert"'):
            print "%s\t%s"%(key.ljust(max_keylen), bindings[key])

# ---------------------------------------------------------------------------

if __name__ == '__main__':
    print_bindings(get_bindings()[1])
