Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic completion candidates update? #471

Closed
mrbiggfoot opened this issue Apr 29, 2017 · 101 comments
Closed

Dynamic completion candidates update? #471

mrbiggfoot opened this issue Apr 29, 2017 · 101 comments

Comments

@mrbiggfoot
Copy link

Different sources have different speeds. Some are very fast - like "buffer" and "around", some may take many seconds to gather candidates from, e.g. "tag" source. Can we show the completion candidates as they become avaliable from the source? I.e. when I start typing, I should instantly get completions from "buffer" and "around", and if I'm willing to wait for so many seconds - the candidates from "tag" would appear and the candidates list will be dynamically redrawn.

@brunogsa
Copy link

+1

@Shougo
Copy link
Owner

Shougo commented May 1, 2017

Yes, it is on the todo list.

@mrbiggfoot
Copy link
Author

I implemented this deoplete source to help dealing with 'tags' slowness in the meantime:
https://github.com/mrbiggfoot/deoplete-filesrc

It is pretty dumb, but works for me. @Shougo if you want you can improve on it and put it into deoplete since it's generic enough.

@Shougo
Copy link
Owner

Shougo commented Jul 5, 2017

I will work for the issue after startup optimization.

@Shougo Shougo added this to the ver.4.0 milestone Aug 10, 2017
@Shougo
Copy link
Owner

Shougo commented Aug 10, 2017

It should be ver.4.0.

@Shougo
Copy link
Owner

Shougo commented Oct 19, 2017

It is the next task after Vim 8 support.
Please wait.

@balta2ar
Copy link
Contributor

@Shougo Hi, I'd like to clarify things about is_async flag of gather_candidates.
I have a completion source that is somewhat slow. It takes about 0.3s to generate results. If I type some input, these 0.3s accumulate after each typed letter and I have to wait until previous requests are processed (even though I don't need them already). But I want to cancel the async request if new input arrives.

I've read the docs here (https://github.com/Shougo/deoplete.nvim/blob/master/doc/deoplete.txt#L980) and https://github.com/zchee/deoplete-jedi/blob/master/rplugin/python3/deoplete/sources/deoplete_jedi.py#L168 source. To do what I want I need to implement some kind of internal worker thread. When gather_candidates is called, I submit my request to my internal queue, set is_async flag to True, and then return an empty list, right?

Next time when gather_candidates is called, I need to check 2 things:

  1. If complete_str has changed, I should cancel my current request and summit a new one.
  2. Otherwise, I should check whether the request has completed and if it has, set is_async to False, and return an actual list of candidates.

Also, even though the word async is extensively used here, it has nothing to do with async coroutines in Python 3.4..3.6, right?

Is my understanding correct? Please correct me if I'm wrong.

@balta2ar
Copy link
Contributor

balta2ar commented Nov 11, 2017

It looks like what I need is the same is_async flag but for on_post_filter function. The reason is that I do my custom filtering both in gather_candidates and in on_post_filter. I do not rely on denite's filtering because it didn't match my needs well. I produced best results if I filtered on my own.

Is it possible to make on_post_filter async too?

Is there another way? If I need to apply a custom filter and that filter is known to be slow, what do I do? I would like to be able to cancel filtering requests if the input (complete_str) changes.

@balta2ar
Copy link
Contributor

balta2ar commented Nov 11, 2017

Apparently VSCode has something very close to my needs, specifically Promises and Cancellation Tokens: https://code.visualstudio.com/docs/extensionAPI/patterns-and-principles#_cancellation-tokens
Could something similar be implemented in deoplete?

@balta2ar
Copy link
Contributor

Could you please explain, how and when to use this flag? The documentation is a bit scarce...

is_volatile
		(Bool)				(Optional)
If it is True, the source depends on the user input.

@Shougo
Copy link
Owner

Shougo commented Nov 14, 2017

When gather_candidates is called, I submit my request to my internal queue, set is_async flag to True, and then return an empty list, right?

Yes.

@Shougo
Copy link
Owner

Shougo commented Nov 14, 2017

Also, even though the word async is extensively used here, it has nothing to do with async coroutines in Python 3.4..3.6, right?

You don't have to use async coroutines.

@Shougo
Copy link
Owner

Shougo commented Nov 14, 2017

Could you please explain, how and when to use this flag? The documentation is a bit scarce...

If the flag is enabled, deoplete does not use the internal cache.
The volatile source is called when the input is changed everytime.

@Shougo
Copy link
Owner

Shougo commented Nov 14, 2017

I do not rely on denite's filtering because it didn't match my needs well. I produced best results if I filtered on my own.

I don't understand what is the your filter.

Is it possible to make on_post_filter async too?

I don't understand it.
on_post_filter is called after gather_candidates().
So if gather_candidates() is async, on_post_filter is also async.

@Shougo
Copy link
Owner

Shougo commented Nov 14, 2017

I would like to be able to cancel filtering requests if the input (complete_str) changes.

It should be new feature.
Please create the new issue.

@balta2ar
Copy link
Contributor

Also, even though the word async is extensively used here, it has nothing to do with async coroutines in Python 3.4..3.6, right?

You don't have to use async coroutines.

How are you going to implement dynamic update them? I'm curious. A callback to update the results?

I don't understand what is the your filter.

It's basically the same fuzzy filtering & ranking in one place but done using a Python C extension. Nothing fancy though.

Is it possible to make on_post_filter async too?

I don't understand it.
on_post_filter is called after gather_candidates().
So if gather_candidates() is async, on_post_filter is also async.

Hm, not exactly. Suppose that I'm going against the suggestions and I'm doing my own filtering in gather_candidates and in on_post_filter. And that filtering is time-consuming. I could offload the filtering into a background thread in gather_candidates and use is_async flag, but then without is_volatile flag the method on_post_filter is still called on every input change. I can't return partial results from on_post_filter, right?

In my case, to be able to cancel time-consuming filtering in the middle (for example, if the input has changed) and to not block deoplete, I had to do the following:

  1. Do the filtering in a separate thread and use is_async flag in gather_candidates.
  2. Make sure the filtering is granular and can be canceled by a flag. The flag is frequently checked by the thread.
  3. Remove on_post_filter and do the filtering in gather_candidates.
  4. Set is_volatile=True.

It should be new feature.
Please create the new issue.

I should probably think more about the use cases. Now it looks like just detecting a change in complete_str is enough. Do you have any use cases in mind?

@Shougo
Copy link
Owner

Shougo commented Nov 15, 2017

How are you going to implement dynamic update them?

The solution is multi-process. async coroutines does not save the calculation.

It's basically the same fuzzy filtering & ranking in one place but done using a Python C extension. Nothing fancy though.

Hm. I can add matcher_cpsm like denite.nvim.

@Shougo
Copy link
Owner

Shougo commented Nov 15, 2017

but then without is_volatile flag the method on_post_filter is still called on every input change.

Yes. It is the feature.

I can't return partial results from on_post_filter, right?

Right.

@Shougo
Copy link
Owner

Shougo commented Nov 15, 2017

The current deoplete behavior is hard to cancel the results for deoplete sources.
It should be fixed.

The input change should be detected by is_async flag.

@balta2ar
Copy link
Contributor

Hm. I can add matcher_cpsm like denite.nvim.

That would be useful! Could you please add it? They say they're using multithreading to parallelise matching so it sounds promising. It could be faster than my current solution.

The current deoplete behavior is hard to cancel the results for deoplete sources.
It should be fixed.

On top of that any slow source can slow down the whole deoplete plugin. Having sources in separate processes could solve this issue probably. Another improvement could be to call all sources in parallel (this is what nvim-completion-manager claims to do).

@Shougo
Copy link
Owner

Shougo commented Nov 15, 2017

Another improvement could be to call all sources in parallel (this is what nvim-completion-manager claims to do).

I have the similar solutions. But it takes time to implement. Not easy.
I will improve source async feature before that.

@Shougo
Copy link
Owner

Shougo commented Nov 23, 2017

@balta2ar Please use is_async flag to check the input.
If the input is changed, it will be False.

@Shougo
Copy link
Owner

Shougo commented Nov 23, 2017

@balta2ar I have added matcher_cpsm in deoplete.

@balta2ar
Copy link
Contributor

Please use is_async flag to check the input.
If the input is changed, it will be False

Sorry, I don't quite understand it, why do I need a flag to detect input change? The source plugin can remember last input string and compare with the new one, thus detecting the change by itself. Is there something else to that?

Also, using the same flag both as input and output for different purposes ("input changed" versus "call me again soon") could be very confusing...

Thank you for the cpsm, I'll try it!

@Shougo
Copy link
Owner

Shougo commented Nov 23, 2017

Sorry, I don't quite understand it, why do I need a flag to detect input change? The source plugin can remember last input string and compare with the new one, thus detecting the change by itself. Is there something else to that?

Yes. But all async source needs the feature.
So deoplete should support it.

Also, using the same flag both as input and output for different purposes ("input changed" versus "call me again soon") could be very confusing...

OK. I have changed the flag.
Please use is_refresh instead.

@balta2ar
Copy link
Contributor

Yes. But all async source needs the feature.

Could you give an example, please? I have an async source and I remember the previous input and compare it with the current one, and it's working fine. I may be missing something.

Please use is_refresh instead

May I suggest input_changed flag name? I think it more clearly describes the meaning...

@mrbiggfoot
Copy link
Author

Frankly, I don't know the exact server configuration, but it is definitely not windows. The above hardware specs is what I see inside the VM, i.e. the provisioned resources.

@mrbiggfoot
Copy link
Author

mrbiggfoot commented Feb 7, 2018

Latest master (3dee4b1) with profile settings:
deoplete.log
attempted the same completion multiple times.

@mrbiggfoot
Copy link
Author

FWIW - the behavior is no any different on the latest nightly build of neovim.

@mrbiggfoot
Copy link
Author

Is there any log that I can turn on that would help to debug ":qa!" issue?

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

I think in VM timer interrupt is very slow.
So deoplete parallel is very slow.

I cannot fix the problem.
But I will add the serial support in deoplete later.

#638

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

Is there any log that I can turn on that would help to debug ":qa!" issue?

I think it is same problem with #637.
It depends on your configuration and the installed plugins.
Please test it in minimal init.vim.

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

Please test the latest version of deoplete in non VM environment.
I think it is faster.

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

Latest master (3dee4b1) with profile settings:
deoplete.log
attempted the same completion multiple times.

I have checked the log.
Switching overhead is 100ms ~ 200ms.
It is very high.

@mrbiggfoot
Copy link
Author

Please test the latest version of deoplete in non VM environment.

Even if it is faster, it's of no use to me. Sorry, but my work environment is not going to change for some objective reasons. I think I will just stay on the older version for now, which mostly works for me. Thank you!

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

Even if it is faster, it's of no use to me. Sorry, but my work environment is not going to change for some objective reasons.

I know it. But I want to know the slowness is whether VM reason.

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

I think I will just stay on the older version for now, which mostly works for me. Thank you!

And I will implement searial completion feature instead.

@mrbiggfoot
Copy link
Author

I know it. But I want to know the slowness is whether VM reason.

Still, I can't do a fair apples to apples comparison because I don't have a CentOS running on bare metal. The only unix that I have on bare metal is MacOS, which is pretty far from CentOS. So even if I get different results, it could be something else.

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

Still, I can't do a fair apples to apples comparison because I don't have a CentOS running on bare metal. The only unix that I have on bare metal is MacOS, which is pretty far from CentOS. So even if I get different results, it could be something else.

Yes. But you know CentOS bare metal is different from CentOS VM.
The performance is different.

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

@mrbiggfoot Please test below patch and profile.

diff --git a/rplugin/python3/deoplete/child.py b/rplugin/python3/deoplete/child.py
index 1e14e09..f171be9 100644
--- a/rplugin/python3/deoplete/child.py
+++ b/rplugin/python3/deoplete/child.py
@@ -79,6 +79,7 @@ class Child(logger.LoggingMixin):
                     self._on_event(args[0])
                 elif name == 'merge_results':
                     self._write(self._merge_results(args[0], queue_id))
+                self.debug('main_loop: end')

     def _write(self, expr):
         sys.stdout.buffer.write(self._packer.pack(expr))
@@ -141,6 +142,7 @@ class Child(logger.LoggingMixin):
                 self.debug('Loaded Filter: %s (%s)', f.name, path)

     def _merge_results(self, context, queue_id):
+        self.debug('merged_results: begin')
         results = self._gather_results(context)

         merged_results = []
@@ -162,6 +164,7 @@ class Child(logger.LoggingMixin):

         is_async = len([x for x in results if x['context']['is_async']]) > 0

+        self.debug('merged_results: end')
         return {
             'queue_id': queue_id,
             'is_async': is_async,

@Shougo
Copy link
Owner

Shougo commented Feb 7, 2018

And please test the patch.

diff --git a/rplugin/python3/deoplete/process.py b/rplugin/python3/deoplete/process.py
index d1ac7b3..f6e6c47 100644
--- a/rplugin/python3/deoplete/process.py
+++ b/rplugin/python3/deoplete/process.py
@@ -52,7 +52,10 @@ class Process(object):

     def enqueue_output(self):
         while self._proc:
-            b = self._proc.stdout.read(1)
+            b = self._proc.stdout.raw.read(102400)
+            if b == b'':
+                return
+
             self._unpacker.feed(b)
             for child_out in self._unpacker:
                 self._queue_out.put(child_out)

@brunogsa
Copy link

brunogsa commented Feb 7, 2018

Hi @Shougo

I think I'm also experiencing some issues now, after updating to the last version.
My completion stop working after a while, so I have to restart my nvim.

Also, It feels like my system is slower.
Neovim sometimes stucks, so I have to press CTRL+C to kill the process.

I'm trying to debug, following the docs:

let g:deoplete#enable_profile = 1
call deoplete#enable_logging('DEBUG', 'deoplete.log')

But now I'm retrieving an error when opening nvim:

Error detected while processing /home/brunogsa/.config/nvim/init.vim:
line  434:
E117: Unknown function: deoplete#enable_logging
Press ENTER or type command to continue

I would like to provide more useful information, coud you point me out how can I help?

Thanks for the great work!

@Shougo
Copy link
Owner

Shougo commented Feb 8, 2018

@brunogsa Please test the latest version of deoplete.

@mrbiggfoot
Copy link
Author

@Shougo now I don't see the difference in performance (using master at a24d95e), or it is very minor. Also, the ":qa!" problem is gone. Thank you!

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

OK. Cherrs.

@mrbiggfoot
Copy link
Author

Sorry for spamming here, but don't yet want to open an issue for what may well be my own stupidity... the filesource https://github.com/mrbiggfoot/deoplete-filesrc has stopped working. It is not related to this change - it does not work even if I roll it back. But it used to work some time ago. Were there any changes to the source API? I'm not seeing anything relevant in the doc... perhaps you can take a quick look and spot what is wrong immediately?
https://github.com/mrbiggfoot/deoplete-filesrc/blob/master/rplugin/python3/deoplete/sources/filesrc.py
(less than one screen of trivial code, and I checked the class separately and verified that the file path g:deoplete#filesrc#path is set in vim)

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

OK. I will test it.
Please upload the sample file.

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

I have tested it. And it works.

I think you must expand() g:deoplete#filesrc#path.
Like this.

let g:deoplete#filesrc#path = expand('~/src/neovim/completions')

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

@mrbiggfoot
Copy link
Author

Thanks for looking into it @Shougo, but that was not it. I have an absolute path set in that variable. There's something more weird going on... if I open a file like "vim file.cc", my completions from the file work. But if I open that same file via fzf (which I use from vim via fzf.vim) after staring vim, the same completions don't work! The #path var is set properly in both cases. I will investigate more. You already spent a lot of time on this.

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

https://github.com/mrbiggfoot/deoplete-filesrc/blob/master/rplugin/python3/deoplete/sources/filesrc.py#L36

close() is needed. You should use with statement instead.

https://github.com/mrbiggfoot/deoplete-filesrc/blob/master/rplugin/python3/deoplete/sources/filesrc.py#L37

You can use f.readlines() instead.

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

The code is broken.

    def on_event(self, context):
        if not exists(self.__filepath):
            self.__cache = []
            return

        mtime = getmtime(self.__filepath)
        if mtime == self.__mtime:
            return

        self.__mtime = mtime
        f = open(self.__filepath, 'r')
        self.__cache = list(f.read().split())

@Shougo
Copy link
Owner

Shougo commented Feb 9, 2018

The cache is cleared if if mtime == self.__mtime in previous code.

So,

But if I open that same file via fzf (which I use from vim via fzf.vim) after staring vim, the same completions don't work!

Yes.

It is your source's bug.

@mrbiggfoot
Copy link
Author

Thank you again @Shougo, you are right. Stupid mistakes in my code.

You can use f.readlines() instead.

Not quite, readlines() does not trim the newline chars.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants