1
1
"""
2
2
"""
3
3
from abc import ABCMeta , abstractmethod
4
- from typing import AsyncGenerator , Callable , Iterable , Optional , Sequence
4
+ from typing import AsyncGenerator , Callable , Iterable , List , Optional , Sequence
5
5
6
6
from 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
+ )
8
12
from prompt_toolkit .filters import FilterOrBool , to_filter
9
13
from prompt_toolkit .formatted_text import AnyFormattedText , StyleAndTextTuples
10
14
@@ -224,10 +228,61 @@ async def get_completions_async(
224
228
"""
225
229
Asynchronous generator of completions.
226
230
"""
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
231
286
232
287
def __repr__ (self ) -> str :
233
288
return f"ThreadedCompleter({ self .completer !r} )"
@@ -306,10 +361,11 @@ async def get_completions_async(
306
361
307
362
# Get all completions in a non-blocking way.
308
363
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
313
369
314
370
315
371
class _MergedCompleter (Completer ):
@@ -333,8 +389,11 @@ async def get_completions_async(
333
389
334
390
# Get all completions from the other completers in a non-blocking way.
335
391
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
338
397
339
398
340
399
def merge_completers (
0 commit comments