9
9
import datetime
10
10
import os
11
11
from abc import ABCMeta , abstractmethod
12
- from typing import AsyncGenerator , Iterable , List
13
-
14
- from prompt_toolkit .application .current import get_app
15
-
16
- from .eventloop import generator_to_async_generator
17
- from .utils import Event
12
+ from threading import Thread
13
+ from typing import Iterable , List , Callable , Optional
18
14
19
15
__all__ = [
20
16
'History' ,
@@ -33,38 +29,48 @@ class History(metaclass=ABCMeta):
33
29
"""
34
30
def __init__ (self ) -> None :
35
31
# In memory storage for strings.
36
- self ._loading = False
32
+ self ._loaded = False
37
33
self ._loaded_strings : List [str ] = []
38
- self ._item_loaded : Event ['History' ] = Event (self )
39
-
40
- async def _start_loading (self ) -> None :
41
- """
42
- Consume the asynchronous generator: `load_history_strings_async`.
43
-
44
- This is only called once, because once the history is loaded, we don't
45
- have to load it again.
46
- """
47
- def add_string (string : str ) -> None :
48
- " Got one string from the asynchronous history generator. "
49
- self ._loaded_strings .insert (0 , string )
50
- self ._item_loaded .fire ()
51
-
52
- async for item in self .load_history_strings_async ():
53
- add_string (item )
54
34
55
35
#
56
36
# Methods expected by `Buffer`.
57
37
#
58
38
59
- def start_loading (self ) -> None :
60
- " Start loading the history. "
61
- if not self ._loading :
62
- self ._loading = True
63
- get_app ().create_background_task (self ._start_loading ())
64
-
65
- def get_item_loaded_event (self ) -> Event ['History' ]:
66
- " Event which is triggered when a new item is loaded. "
67
- return self ._item_loaded
39
+ def load (self , item_loaded_callback : Callable [[str ], None ]) -> None :
40
+ """
41
+ Load the history and call the callback for every entry in the history.
42
+
43
+ XXX: The callback can be called from another thread, which happens in
44
+ case of `ThreadedHistory`.
45
+
46
+ We can't assume that an asyncio event loop is running, and
47
+ schedule the insertion into the `Buffer` using the event loop.
48
+
49
+ The reason is that the creation of the :class:`.History` object as
50
+ well as the start of the loading happens *before*
51
+ `Application.run()` is called, and it can continue even after
52
+ `Application.run()` terminates. (Which is useful to have a
53
+ complete history during the next prompt.)
54
+
55
+ Calling `get_event_loop()` right here is also not guaranteed to
56
+ return the same event loop which is used in `Application.run`,
57
+ because a new event loop can be created during the `run`. This is
58
+ useful in Python REPLs, where we want to use one event loop for
59
+ the prompt, and have another one active during the `eval` of the
60
+ commands. (Otherwise, the user can schedule a while/true loop and
61
+ freeze the UI.)
62
+ """
63
+ if self ._loaded :
64
+ for item in self ._loaded_strings [::- 1 ]:
65
+ item_loaded_callback (item )
66
+ return
67
+
68
+ try :
69
+ for item in self .load_history_strings ():
70
+ self ._loaded_strings .insert (0 , item )
71
+ item_loaded_callback (item )
72
+ finally :
73
+ self ._loaded = True
68
74
69
75
def get_strings (self ) -> List [str ]:
70
76
"""
@@ -93,16 +99,6 @@ def load_history_strings(self) -> Iterable[str]:
93
99
while False :
94
100
yield
95
101
96
- async def load_history_strings_async (self ) -> AsyncGenerator [str , None ]:
97
- """
98
- Asynchronous generator for history strings. (Probably, you won't have
99
- to override this.)
100
-
101
- This is an asynchronous generator of `str` objects.
102
- """
103
- for item in self .load_history_strings ():
104
- yield item
105
-
106
102
@abstractmethod
107
103
def store_string (self , string : str ) -> None :
108
104
"""
@@ -120,15 +116,29 @@ class ThreadedHistory(History):
120
116
"""
121
117
def __init__ (self , history : History ) -> None :
122
118
self .history = history
119
+ self ._load_thread : Optional [Thread ] = None
120
+ self ._item_loaded_callbacks : List [Callable [[str ], None ]] = []
123
121
super ().__init__ ()
124
122
125
- async def load_history_strings_async (self ) -> AsyncGenerator [str , None ]:
126
- """
127
- Asynchronous generator of completions.
128
- This yields both Future and Completion objects.
129
- """
130
- async for item in generator_to_async_generator (self .history .load_history_strings ):
131
- yield item
123
+ def load (self , item_loaded_callback : Callable [[str ], None ]) -> None :
124
+ self ._item_loaded_callbacks .append (item_loaded_callback )
125
+
126
+ # Start the load thread, if we don't have a thread yet.
127
+ if not self ._load_thread :
128
+ def call_all_callbacks (item : str ) -> None :
129
+ for cb in self ._item_loaded_callbacks :
130
+ cb (item )
131
+
132
+ self ._load_thread = Thread (target = self .history .load , args = (call_all_callbacks , ))
133
+ print (self ._load_thread .daemon )
134
+ self ._load_thread .daemon = True
135
+ self ._load_thread .start ()
136
+
137
+ def get_strings (self ) -> List [str ]:
138
+ return self .history .get_strings ()
139
+
140
+ def append_string (self , string : str ) -> None :
141
+ self .history .append_string (string )
132
142
133
143
# All of the following are proxied to `self.history`.
134
144
0 commit comments