11"""
22"""
33from abc import ABCMeta , abstractmethod
4- from typing import AsyncGenerator , Callable , Iterable , Optional , Sequence
4+ from typing import AsyncGenerator , Callable , Iterable , List , Optional , Sequence
55
66from prompt_toolkit .document import Document
7- from prompt_toolkit .eventloop import generator_to_async_generator
7+ from prompt_toolkit .eventloop import (
8+ aclosing ,
9+ generator_to_async_generator ,
10+ get_event_loop ,
11+ )
812from prompt_toolkit .filters import FilterOrBool , to_filter
913from prompt_toolkit .formatted_text import AnyFormattedText , StyleAndTextTuples
1014
@@ -224,10 +228,61 @@ async def get_completions_async(
224228 """
225229 Asynchronous generator of completions.
226230 """
227- async for completion in generator_to_async_generator (
228- lambda : self .completer .get_completions (document , complete_event )
229- ):
230- yield completion
231+ # NOTE: Right now, we are consuming the `get_completions` generator in
232+ # a synchronous background thread, then passing the results one
233+ # at a time over a queue, and consuming this queue in the main
234+ # thread (that's what `generator_to_async_generator` does). That
235+ # means that if the completer is *very* slow, we'll be showing
236+ # completions in the UI once they are computed.
237+
238+ # It's very tempting to replace this implementation with the
239+ # commented code below for several reasons:
240+
241+ # - `generator_to_async_generator` is not perfect and hard to get
242+ # right. It's a lot of complexity for little gain. The
243+ # implementation needs a huge buffer for it to be efficient
244+ # when there are many completions (like 50k+).
245+ # - Normally, a completer is supposed to be fast, users can have
246+ # "complete while typing" enabled, and want to see the
247+ # completions within a second. Handling one completion at a
248+ # time, and rendering once we get it here doesn't make any
249+ # sense if this is quick anyway.
250+ # - Completers like `FuzzyCompleter` prepare all completions
251+ # anyway so that they can be sorted by accuracy before they are
252+ # yielded. At the point that we start yielding completions
253+ # here, we already have all completions.
254+ # - The `Buffer` class has complex logic to invalidate the UI
255+ # while it is consuming the completions. We don't want to
256+ # invalidate the UI for every completion (if there are many),
257+ # but we want to do it often enough so that completions are
258+ # being displayed while they are produced.
259+
260+ # We keep the current behavior mainly for backward-compatibility.
261+ # Similarly, it would be better for this function to not return
262+ # an async generator, but simply be a coroutine that returns a
263+ # list of `Completion` objects, containing all completions at
264+ # once.
265+
266+ # Note that this argument doesn't mean we shouldn't use
267+ # `ThreadedCompleter`. It still makes sense to produce
268+ # completions in a background thread, because we don't want to
269+ # freeze the UI while the user is typing. But sending the
270+ # completions one at a time to the UI maybe isn't worth it.
271+
272+ # def get_all_in_thread() -> List[Completion]:
273+ # return list(self.get_completions(document, complete_event))
274+
275+ # completions = await get_event_loop().run_in_executor(None, get_all_in_thread)
276+ # for completion in completions:
277+ # yield completion
278+
279+ async with aclosing (
280+ generator_to_async_generator (
281+ lambda : self .completer .get_completions (document , complete_event )
282+ )
283+ ) as async_generator :
284+ async for completion in async_generator :
285+ yield completion
231286
232287 def __repr__ (self ) -> str :
233288 return f"ThreadedCompleter({ self .completer !r} )"
@@ -306,10 +361,11 @@ async def get_completions_async(
306361
307362 # Get all completions in a non-blocking way.
308363 if self .filter ():
309- async for item in self .completer .get_completions_async (
310- document , complete_event
311- ):
312- yield item
364+ async with aclosing (
365+ self .completer .get_completions_async (document , complete_event )
366+ ) as async_generator :
367+ async for item in async_generator :
368+ yield item
313369
314370
315371class _MergedCompleter (Completer ):
@@ -333,8 +389,11 @@ async def get_completions_async(
333389
334390 # Get all completions from the other completers in a non-blocking way.
335391 for completer in self .completers :
336- async for item in completer .get_completions_async (document , complete_event ):
337- yield item
392+ async with aclosing (
393+ completer .get_completions_async (document , complete_event )
394+ ) as async_generator :
395+ async for item in async_generator :
396+ yield item
338397
339398
340399def merge_completers (
0 commit comments