roxma / nvim-completion-manager

:warning: PLEASE USE https://github.com/ncm2/ncm2 INSTEAD
MIT License
917 stars 49 forks source link

Add support for lbdb source. #123

Closed ghost closed 7 years ago

ghost commented 7 years ago

It's very useful to autocomplete emails from lbdb via lbdbq as per source for Deoplete here: mail.py

Do you think it would be possible to implement the same source in CM?

Thanks.

roxma commented 7 years ago

It's possible. Read https://github.com/roxma/nvim-completion-manager/blob/baadf0913233ce646b2ebfa891bb0f10873126ad/doc/nvim-completion-manager.txt#L645

ghost commented 7 years ago

Hey,

ok, so made a quick attempt to get candidates from lbdbq.

# -*- coding: utf-8 -*-

# For debugging, use this command to start neovim:
#
# NVIM_PYTHON_LOG_FILE=nvim.log NVIM_PYTHON_LOG_LEVEL=INFO nvim
#
#
# Please register source before executing any other code, this allow cm_core to
# read basic information about the source without loading the whole module, and
# modules required by this module
import subprocess
# import re
from cm import register_source, getLogger, Base

register_source(name='mail',
                priority=9,
                scoping=True,
                scopes=['mail'],
                abbreviation='mail',
                word_pattern=r'[\w/]+',
                cm_refresh_length=2)

# cm_refresh_patterns=re.compile('^(Bcc|Cc|From|Reply-To|To):(.*, ?| ?)'),

LOGGER = getLogger(__name__)

class Source(Base):

    def __init__(self, nvim):
        super(Source, self).__init__(nvim)

        # dependency check
        try:
            from distutils.spawn import find_executable
            if not find_executable("lbdbq"):
                self.message('error', 'No lbdbq found.')
        except Exception as ex:
            LOGGER.exception(ex)

    def cm_refresh(self, info, ctx, *args):
        base = (ctx['base']).encode('utf-8')
        args = ['/usr/local/bin/lbdbq', base]

        proc = subprocess.Popen(args=args,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        results = proc.communicate('', timeout=5)[0]

        matches = []

        for result in results.decode('utf-8').splitlines():
            try:
                address, name, source = result.strip().split('\t')
                del source
                if name:
                    address = name + ' <' + address + '>'
                    matches.append(address)
            except ValueError:
                pass

        self.complete(info, ctx, ctx['startcol'], matches)

There are couple of problem I can't seem to be able to solve on my own:

Is it something you could help me with, please?

roxma commented 7 years ago

get fuzzy completion

8

get caching for ctx['base'] equal '.', this would return content of entire address book during first completion attempt

NCM caches the candidates until you start another word.

make it only work on lines prefixed with ...

:help cm#register_source() have some explanations.

automatically pop full list when cursor is placed on matching lines (as above) and only limit candidates as something is typed.

Send all the candidates to NCM when possible, which will do the matching and sorting.

roxma commented 7 years ago

get caching for ctx['base'] equal '.', this would return content of entire address book during first completion attempt

Do you mean you want always caching while vim is running? I think you should be able to do that by yourself, with a flag variable, cache variable and some extra code.

But normally the address book is relative small, there's no need for caching.

ghost commented 7 years ago

Than you very much. It works as expected now apart of fuzzy matching. Can matcher be specified in source? cm_matchers.abbrev_matcher for example.

As for caching, I just don't want it to call external program for each typed letter. Correct me if I am wrong, but if I change args to args = ['/usr/local/bin/lbdbq', '.'], does it get called only once at the beginning or after every single character?

Also, just noticed that it does not preset completion menu for after , (comma and space) character. Is it easily fixable? I tried to add r', ' to cm_refresh_patterns but it causes completion to pop up even in email body.

Thanks once again!

roxma commented 7 years ago

Can matcher be specified in source?

No.

As for caching, I just don't want it to call external program for each typed letter.

NCM caches the result in the same word. That is, when the same pattern is matched, and startcol calculated by NCM keeps unchanged. (:help NCM-word_pattern)

For example:

To:foo_bar, cm_refresh is triggered at To:|, when you type foo_bar, NCM uses the cached result. startcol is 4 in this case.

Then you type another line From:foo_baz, cm_refresh will be triggered at From:|. startcol is 6 in this case.

...

but if I change args to args = ['/usr/local/bin/lbdbq', '.'], does it get called only once at the beginning or after every single character?

No. As is said before.

You could test the behavior by enabling logging.

https://github.com/roxma/nvim-completion-manager/wiki/Trouble-shooting

Also, just noticed that it does not preset completion menu for after , (comma and space) character. Is it easily fixable? I tried to add r', ' to cm_refresh_patterns but it causes completion to pop up even in email body.

You'll need something like

cm_refresh_patterns=[r'^(Bcc|Cc|From|Reply-To|To):\s*', r'^(Bcc|Cc|From|Reply-To|To):.*,\s*']
ghost commented 7 years ago

Thanks. Updated documentation and your explanation above make sense and clears things up. There are two more things that I would like to accomplish.

Is first point even possible?

Thanks!

roxma commented 7 years ago

disable other sources for when this source is active (cm_refresh_patterns from above is matched)

If the source is bound to a filetype, then you could set the priority to 9 (:help NCM-priority), the highest value. It won't disable other sources, but its candidates shows before all others.

Even if it's not, your cm_refresh_patterns is quite special, that normally other sources won't have a chance to popup. So it doesn't really matter.

fuzzy matching

Currently doesn't support per-source matching. I'm not sure per-source matching is a good behavior, and I'm not sure how it should be.

Currently matching is a global setting by g:cm_matcher. A source sends all candadites to NCM. And NCM do the rest.

ghost commented 7 years ago

Ah, thank you for both answers! I will get used to current behaviour then.

Thanks for your help. For reference, this is what I ended up with:

# -*- coding: utf-8 -*-

# For debugging, use this command to start neovim:
#
# NVIM_PYTHON_LOG_FILE=nvim.log NVIM_PYTHON_LOG_LEVEL=INFO nvim
#
#
# Please register source before executing any other code, this allow cm_core to
# read basic information about the source without loading the whole module, and
# modules required by this module
import subprocess
from cm import register_source, getLogger, Base

register_source(name='mail',
                abbreviation='mail',
                scopes=['mail'],
                priority=9,
                scoping=True,
                early_cache=True,
                word_pattern=r'[\w/]+',
                cm_refresh_patterns=[r'^(Bcc|Cc|From|Reply-To|To):\s*',
                                     r'^(Bcc|Cc|From|Reply-To|To):.*,\s*'],
                cm_refresh_length=-1)

LOGGER = getLogger(__name__)

class Source(Base):

    def __init__(self, nvim):
        super(Source, self).__init__(nvim)

        # dependency check
        try:
            from distutils.spawn import find_executable
            if not find_executable("lbdbq"):
                self.message('error', 'No lbdbq found.')
        except Exception as ex:
            LOGGER.exception(ex)

    def cm_refresh(self, info, ctx, *args):
        args = ['/usr/local/bin/lbdbq', '.']

        proc = subprocess.Popen(args=args,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        results = proc.communicate('')[0]

        LOGGER.debug("args: %s, result: [%s]", args, results.decode())

        matches = []

        for result in results.decode('utf-8').splitlines():
            try:
                address, name, source = result.strip().split('\t')
                del source
                if name:
                    address = name + ' <' + address + '>'
                    matches.append(address)
            except ValueError:
                pass

        self.complete(info, ctx, ctx['startcol'], matches)
roxma commented 7 years ago

You could setup a github repo for this source, like ncm-github, so that others could easilly download it with a plugin manager. (e.g. Plug "roxma/ncm-github")

roxma commented 7 years ago

A similar use case came up to me: https://github.com/roxma/nvim-completion-manager/issues/97#issuecomment-311247700

It's completion for contacts. And it's probably useful for you.

The alias name may not match the prefix of the alias pattern (e.g. foo and My Foo foo@bar.com), items will be filtered out by NCM. This is why I convert it into snippet.

ghost commented 7 years ago

Thanks, had a look at it before, but I am not using mutt aliases at all.

Created repo for lbdb source: katsika/ncm-lbdb.