omni-ai-cli 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +615 -2
  3. package/bin/ai.js +84 -0
  4. package/docs/README.en.md +615 -0
  5. package/docs/README.es.md +618 -0
  6. package/docs/README.fr.md +630 -0
  7. package/omni_ai/__init__.py +1 -0
  8. package/omni_ai/__main__.py +6 -0
  9. package/omni_ai/chat.py +1458 -0
  10. package/omni_ai/cli.py +991 -0
  11. package/omni_ai/config.py +531 -0
  12. package/omni_ai/deepseek.py +57 -0
  13. package/omni_ai/grok.py +111 -0
  14. package/omni_ai/onboarding.py +196 -0
  15. package/omni_ai/openrouter.py +335 -0
  16. package/omni_ai/osutils.py +86 -0
  17. package/omni_ai/pii.py +281 -0
  18. package/omni_ai/profiles.json +69 -0
  19. package/omni_ai/skills.py +124 -0
  20. package/omni_ai/sync.py +199 -0
  21. package/omni_ai/tools.py +75 -0
  22. package/omni_ai/ui.py +595 -0
  23. package/omni_ai/usage.py +305 -0
  24. package/omni_ai/vendor/prompt_toolkit/LICENSE +27 -0
  25. package/omni_ai/vendor/prompt_toolkit/__init__.py +52 -0
  26. package/omni_ai/vendor/prompt_toolkit/application/__init__.py +32 -0
  27. package/omni_ai/vendor/prompt_toolkit/application/application.py +1626 -0
  28. package/omni_ai/vendor/prompt_toolkit/application/current.py +195 -0
  29. package/omni_ai/vendor/prompt_toolkit/application/dummy.py +55 -0
  30. package/omni_ai/vendor/prompt_toolkit/application/run_in_terminal.py +114 -0
  31. package/omni_ai/vendor/prompt_toolkit/auto_suggest.py +177 -0
  32. package/omni_ai/vendor/prompt_toolkit/buffer.py +2029 -0
  33. package/omni_ai/vendor/prompt_toolkit/cache.py +127 -0
  34. package/omni_ai/vendor/prompt_toolkit/clipboard/__init__.py +17 -0
  35. package/omni_ai/vendor/prompt_toolkit/clipboard/base.py +109 -0
  36. package/omni_ai/vendor/prompt_toolkit/clipboard/in_memory.py +44 -0
  37. package/omni_ai/vendor/prompt_toolkit/clipboard/pyperclip.py +42 -0
  38. package/omni_ai/vendor/prompt_toolkit/completion/__init__.py +43 -0
  39. package/omni_ai/vendor/prompt_toolkit/completion/base.py +438 -0
  40. package/omni_ai/vendor/prompt_toolkit/completion/deduplicate.py +45 -0
  41. package/omni_ai/vendor/prompt_toolkit/completion/filesystem.py +118 -0
  42. package/omni_ai/vendor/prompt_toolkit/completion/fuzzy_completer.py +213 -0
  43. package/omni_ai/vendor/prompt_toolkit/completion/nested.py +109 -0
  44. package/omni_ai/vendor/prompt_toolkit/completion/word_completer.py +94 -0
  45. package/omni_ai/vendor/prompt_toolkit/contrib/__init__.py +0 -0
  46. package/omni_ai/vendor/prompt_toolkit/contrib/completers/__init__.py +5 -0
  47. package/omni_ai/vendor/prompt_toolkit/contrib/completers/system.py +64 -0
  48. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/__init__.py +80 -0
  49. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/compiler.py +579 -0
  50. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/completion.py +100 -0
  51. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/lexer.py +94 -0
  52. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/regex_parser.py +279 -0
  53. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/validation.py +60 -0
  54. package/omni_ai/vendor/prompt_toolkit/contrib/ssh/__init__.py +8 -0
  55. package/omni_ai/vendor/prompt_toolkit/contrib/ssh/server.py +178 -0
  56. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/__init__.py +7 -0
  57. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/log.py +13 -0
  58. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/protocol.py +209 -0
  59. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/server.py +428 -0
  60. package/omni_ai/vendor/prompt_toolkit/cursor_shapes.py +117 -0
  61. package/omni_ai/vendor/prompt_toolkit/data_structures.py +18 -0
  62. package/omni_ai/vendor/prompt_toolkit/document.py +1182 -0
  63. package/omni_ai/vendor/prompt_toolkit/enums.py +19 -0
  64. package/omni_ai/vendor/prompt_toolkit/eventloop/__init__.py +31 -0
  65. package/omni_ai/vendor/prompt_toolkit/eventloop/async_generator.py +125 -0
  66. package/omni_ai/vendor/prompt_toolkit/eventloop/inputhook.py +191 -0
  67. package/omni_ai/vendor/prompt_toolkit/eventloop/utils.py +101 -0
  68. package/omni_ai/vendor/prompt_toolkit/eventloop/win32.py +72 -0
  69. package/omni_ai/vendor/prompt_toolkit/filters/__init__.py +71 -0
  70. package/omni_ai/vendor/prompt_toolkit/filters/app.py +419 -0
  71. package/omni_ai/vendor/prompt_toolkit/filters/base.py +261 -0
  72. package/omni_ai/vendor/prompt_toolkit/filters/cli.py +65 -0
  73. package/omni_ai/vendor/prompt_toolkit/filters/utils.py +41 -0
  74. package/omni_ai/vendor/prompt_toolkit/formatted_text/__init__.py +59 -0
  75. package/omni_ai/vendor/prompt_toolkit/formatted_text/ansi.py +297 -0
  76. package/omni_ai/vendor/prompt_toolkit/formatted_text/base.py +179 -0
  77. package/omni_ai/vendor/prompt_toolkit/formatted_text/html.py +145 -0
  78. package/omni_ai/vendor/prompt_toolkit/formatted_text/pygments.py +32 -0
  79. package/omni_ai/vendor/prompt_toolkit/formatted_text/utils.py +103 -0
  80. package/omni_ai/vendor/prompt_toolkit/history.py +306 -0
  81. package/omni_ai/vendor/prompt_toolkit/input/__init__.py +14 -0
  82. package/omni_ai/vendor/prompt_toolkit/input/ansi_escape_sequences.py +344 -0
  83. package/omni_ai/vendor/prompt_toolkit/input/base.py +153 -0
  84. package/omni_ai/vendor/prompt_toolkit/input/defaults.py +79 -0
  85. package/omni_ai/vendor/prompt_toolkit/input/posix_pipe.py +118 -0
  86. package/omni_ai/vendor/prompt_toolkit/input/posix_utils.py +97 -0
  87. package/omni_ai/vendor/prompt_toolkit/input/typeahead.py +78 -0
  88. package/omni_ai/vendor/prompt_toolkit/input/vt100.py +309 -0
  89. package/omni_ai/vendor/prompt_toolkit/input/vt100_parser.py +250 -0
  90. package/omni_ai/vendor/prompt_toolkit/input/win32.py +749 -0
  91. package/omni_ai/vendor/prompt_toolkit/input/win32_pipe.py +156 -0
  92. package/omni_ai/vendor/prompt_toolkit/key_binding/__init__.py +22 -0
  93. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/__init__.py +0 -0
  94. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/auto_suggest.py +66 -0
  95. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/basic.py +257 -0
  96. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/completion.py +206 -0
  97. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/cpr.py +30 -0
  98. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/emacs.py +563 -0
  99. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/focus.py +26 -0
  100. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/mouse.py +348 -0
  101. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/named_commands.py +691 -0
  102. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/open_in_editor.py +52 -0
  103. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/page_navigation.py +85 -0
  104. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/scroll.py +190 -0
  105. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/search.py +96 -0
  106. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/vi.py +2233 -0
  107. package/omni_ai/vendor/prompt_toolkit/key_binding/defaults.py +63 -0
  108. package/omni_ai/vendor/prompt_toolkit/key_binding/digraphs.py +1378 -0
  109. package/omni_ai/vendor/prompt_toolkit/key_binding/emacs_state.py +36 -0
  110. package/omni_ai/vendor/prompt_toolkit/key_binding/key_bindings.py +670 -0
  111. package/omni_ai/vendor/prompt_toolkit/key_binding/key_processor.py +526 -0
  112. package/omni_ai/vendor/prompt_toolkit/key_binding/vi_state.py +107 -0
  113. package/omni_ai/vendor/prompt_toolkit/keys.py +222 -0
  114. package/omni_ai/vendor/prompt_toolkit/layout/__init__.py +147 -0
  115. package/omni_ai/vendor/prompt_toolkit/layout/containers.py +2738 -0
  116. package/omni_ai/vendor/prompt_toolkit/layout/controls.py +945 -0
  117. package/omni_ai/vendor/prompt_toolkit/layout/dimension.py +220 -0
  118. package/omni_ai/vendor/prompt_toolkit/layout/dummy.py +40 -0
  119. package/omni_ai/vendor/prompt_toolkit/layout/layout.py +412 -0
  120. package/omni_ai/vendor/prompt_toolkit/layout/margins.py +304 -0
  121. package/omni_ai/vendor/prompt_toolkit/layout/menus.py +748 -0
  122. package/omni_ai/vendor/prompt_toolkit/layout/mouse_handlers.py +56 -0
  123. package/omni_ai/vendor/prompt_toolkit/layout/processors.py +1010 -0
  124. package/omni_ai/vendor/prompt_toolkit/layout/screen.py +323 -0
  125. package/omni_ai/vendor/prompt_toolkit/layout/scrollable_pane.py +494 -0
  126. package/omni_ai/vendor/prompt_toolkit/layout/utils.py +80 -0
  127. package/omni_ai/vendor/prompt_toolkit/lexers/__init__.py +21 -0
  128. package/omni_ai/vendor/prompt_toolkit/lexers/base.py +85 -0
  129. package/omni_ai/vendor/prompt_toolkit/lexers/pygments.py +328 -0
  130. package/omni_ai/vendor/prompt_toolkit/log.py +13 -0
  131. package/omni_ai/vendor/prompt_toolkit/mouse_events.py +85 -0
  132. package/omni_ai/vendor/prompt_toolkit/output/__init__.py +15 -0
  133. package/omni_ai/vendor/prompt_toolkit/output/base.py +332 -0
  134. package/omni_ai/vendor/prompt_toolkit/output/color_depth.py +64 -0
  135. package/omni_ai/vendor/prompt_toolkit/output/conemu.py +65 -0
  136. package/omni_ai/vendor/prompt_toolkit/output/defaults.py +106 -0
  137. package/omni_ai/vendor/prompt_toolkit/output/flush_stdout.py +87 -0
  138. package/omni_ai/vendor/prompt_toolkit/output/plain_text.py +143 -0
  139. package/omni_ai/vendor/prompt_toolkit/output/vt100.py +748 -0
  140. package/omni_ai/vendor/prompt_toolkit/output/win32.py +683 -0
  141. package/omni_ai/vendor/prompt_toolkit/output/windows10.py +128 -0
  142. package/omni_ai/vendor/prompt_toolkit/patch_stdout.py +297 -0
  143. package/omni_ai/vendor/prompt_toolkit/py.typed +0 -0
  144. package/omni_ai/vendor/prompt_toolkit/renderer.py +814 -0
  145. package/omni_ai/vendor/prompt_toolkit/search.py +226 -0
  146. package/omni_ai/vendor/prompt_toolkit/selection.py +58 -0
  147. package/omni_ai/vendor/prompt_toolkit/shortcuts/__init__.py +46 -0
  148. package/omni_ai/vendor/prompt_toolkit/shortcuts/dialogs.py +330 -0
  149. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/__init__.py +33 -0
  150. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/base.py +449 -0
  151. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/formatters.py +431 -0
  152. package/omni_ai/vendor/prompt_toolkit/shortcuts/prompt.py +1513 -0
  153. package/omni_ai/vendor/prompt_toolkit/shortcuts/utils.py +239 -0
  154. package/omni_ai/vendor/prompt_toolkit/styles/__init__.py +67 -0
  155. package/omni_ai/vendor/prompt_toolkit/styles/base.py +184 -0
  156. package/omni_ai/vendor/prompt_toolkit/styles/defaults.py +236 -0
  157. package/omni_ai/vendor/prompt_toolkit/styles/named_colors.py +162 -0
  158. package/omni_ai/vendor/prompt_toolkit/styles/pygments.py +70 -0
  159. package/omni_ai/vendor/prompt_toolkit/styles/style.py +401 -0
  160. package/omni_ai/vendor/prompt_toolkit/styles/style_transformation.py +374 -0
  161. package/omni_ai/vendor/prompt_toolkit/token.py +9 -0
  162. package/omni_ai/vendor/prompt_toolkit/utils.py +327 -0
  163. package/omni_ai/vendor/prompt_toolkit/validation.py +192 -0
  164. package/omni_ai/vendor/prompt_toolkit/widgets/__init__.py +63 -0
  165. package/omni_ai/vendor/prompt_toolkit/widgets/base.py +995 -0
  166. package/omni_ai/vendor/prompt_toolkit/widgets/dialogs.py +108 -0
  167. package/omni_ai/vendor/prompt_toolkit/widgets/menus.py +374 -0
  168. package/omni_ai/vendor/prompt_toolkit/widgets/toolbars.py +370 -0
  169. package/omni_ai/vendor/prompt_toolkit/win32_types.py +229 -0
  170. package/omni_ai/vendor/wcwidth/LICENSE +27 -0
  171. package/omni_ai/vendor/wcwidth/__init__.py +29 -0
  172. package/omni_ai/vendor/wcwidth/table_vs16.py +125 -0
  173. package/omni_ai/vendor/wcwidth/table_wide.py +1493 -0
  174. package/omni_ai/vendor/wcwidth/table_zero.py +4843 -0
  175. package/omni_ai/vendor/wcwidth/unicode_versions.py +38 -0
  176. package/omni_ai/vendor/wcwidth/wcwidth.py +345 -0
  177. package/package.json +40 -4
@@ -0,0 +1,749 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from abc import abstractmethod
6
+ from asyncio import get_running_loop
7
+ from contextlib import contextmanager
8
+
9
+ from ..utils import SPHINX_AUTODOC_RUNNING
10
+
11
+ assert sys.platform == "win32"
12
+
13
+ # Do not import win32-specific stuff when generating documentation.
14
+ # Otherwise RTD would be unable to generate docs for this module.
15
+ if not SPHINX_AUTODOC_RUNNING:
16
+ import msvcrt
17
+ from ctypes import windll
18
+
19
+ from ctypes import Array, pointer
20
+ from ctypes.wintypes import DWORD, HANDLE
21
+ from typing import Callable, ContextManager, Iterable, Iterator, TextIO
22
+
23
+ from prompt_toolkit.eventloop import run_in_executor_with_context
24
+ from prompt_toolkit.eventloop.win32 import create_win32_event, wait_for_handles
25
+ from prompt_toolkit.key_binding.key_processor import KeyPress
26
+ from prompt_toolkit.keys import Keys
27
+ from prompt_toolkit.mouse_events import MouseButton, MouseEventType
28
+ from prompt_toolkit.win32_types import (
29
+ INPUT_RECORD,
30
+ KEY_EVENT_RECORD,
31
+ MOUSE_EVENT_RECORD,
32
+ STD_INPUT_HANDLE,
33
+ EventTypes,
34
+ )
35
+
36
+ from .ansi_escape_sequences import REVERSE_ANSI_SEQUENCES
37
+ from .base import Input
38
+
39
+ __all__ = [
40
+ "Win32Input",
41
+ "ConsoleInputReader",
42
+ "raw_mode",
43
+ "cooked_mode",
44
+ "attach_win32_input",
45
+ "detach_win32_input",
46
+ ]
47
+
48
+ # Win32 Constants for MOUSE_EVENT_RECORD.
49
+ # See: https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
50
+ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1
51
+ RIGHTMOST_BUTTON_PRESSED = 0x2
52
+ MOUSE_MOVED = 0x0001
53
+ MOUSE_WHEELED = 0x0004
54
+
55
+
56
+ class _Win32InputBase(Input):
57
+ """
58
+ Base class for `Win32Input` and `Win32PipeInput`.
59
+ """
60
+
61
+ def __init__(self) -> None:
62
+ self.win32_handles = _Win32Handles()
63
+
64
+ @property
65
+ @abstractmethod
66
+ def handle(self) -> HANDLE:
67
+ pass
68
+
69
+
70
+ class Win32Input(_Win32InputBase):
71
+ """
72
+ `Input` class that reads from the Windows console.
73
+ """
74
+
75
+ def __init__(self, stdin: TextIO | None = None) -> None:
76
+ super().__init__()
77
+ self.console_input_reader = ConsoleInputReader()
78
+
79
+ def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]:
80
+ """
81
+ Return a context manager that makes this input active in the current
82
+ event loop.
83
+ """
84
+ return attach_win32_input(self, input_ready_callback)
85
+
86
+ def detach(self) -> ContextManager[None]:
87
+ """
88
+ Return a context manager that makes sure that this input is not active
89
+ in the current event loop.
90
+ """
91
+ return detach_win32_input(self)
92
+
93
+ def read_keys(self) -> list[KeyPress]:
94
+ return list(self.console_input_reader.read())
95
+
96
+ def flush(self) -> None:
97
+ pass
98
+
99
+ @property
100
+ def closed(self) -> bool:
101
+ return False
102
+
103
+ def raw_mode(self) -> ContextManager[None]:
104
+ return raw_mode()
105
+
106
+ def cooked_mode(self) -> ContextManager[None]:
107
+ return cooked_mode()
108
+
109
+ def fileno(self) -> int:
110
+ # The windows console doesn't depend on the file handle, so
111
+ # this is not used for the event loop (which uses the
112
+ # handle instead). But it's used in `Application.run_system_command`
113
+ # which opens a subprocess with a given stdin/stdout.
114
+ return sys.stdin.fileno()
115
+
116
+ def typeahead_hash(self) -> str:
117
+ return "win32-input"
118
+
119
+ def close(self) -> None:
120
+ self.console_input_reader.close()
121
+
122
+ @property
123
+ def handle(self) -> HANDLE:
124
+ return self.console_input_reader.handle
125
+
126
+
127
+ class ConsoleInputReader:
128
+ """
129
+ :param recognize_paste: When True, try to discover paste actions and turn
130
+ the event into a BracketedPaste.
131
+ """
132
+
133
+ # Keys with character data.
134
+ mappings = {
135
+ b"\x1b": Keys.Escape,
136
+ b"\x00": Keys.ControlSpace, # Control-Space (Also for Ctrl-@)
137
+ b"\x01": Keys.ControlA, # Control-A (home)
138
+ b"\x02": Keys.ControlB, # Control-B (emacs cursor left)
139
+ b"\x03": Keys.ControlC, # Control-C (interrupt)
140
+ b"\x04": Keys.ControlD, # Control-D (exit)
141
+ b"\x05": Keys.ControlE, # Control-E (end)
142
+ b"\x06": Keys.ControlF, # Control-F (cursor forward)
143
+ b"\x07": Keys.ControlG, # Control-G
144
+ b"\x08": Keys.ControlH, # Control-H (8) (Identical to '\b')
145
+ b"\x09": Keys.ControlI, # Control-I (9) (Identical to '\t')
146
+ b"\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n')
147
+ b"\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab)
148
+ b"\x0c": Keys.ControlL, # Control-L (clear; form feed)
149
+ b"\x0d": Keys.ControlM, # Control-M (enter)
150
+ b"\x0e": Keys.ControlN, # Control-N (14) (history forward)
151
+ b"\x0f": Keys.ControlO, # Control-O (15)
152
+ b"\x10": Keys.ControlP, # Control-P (16) (history back)
153
+ b"\x11": Keys.ControlQ, # Control-Q
154
+ b"\x12": Keys.ControlR, # Control-R (18) (reverse search)
155
+ b"\x13": Keys.ControlS, # Control-S (19) (forward search)
156
+ b"\x14": Keys.ControlT, # Control-T
157
+ b"\x15": Keys.ControlU, # Control-U
158
+ b"\x16": Keys.ControlV, # Control-V
159
+ b"\x17": Keys.ControlW, # Control-W
160
+ b"\x18": Keys.ControlX, # Control-X
161
+ b"\x19": Keys.ControlY, # Control-Y (25)
162
+ b"\x1a": Keys.ControlZ, # Control-Z
163
+ b"\x1c": Keys.ControlBackslash, # Both Control-\ and Ctrl-|
164
+ b"\x1d": Keys.ControlSquareClose, # Control-]
165
+ b"\x1e": Keys.ControlCircumflex, # Control-^
166
+ b"\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.)
167
+ b"\x7f": Keys.Backspace, # (127) Backspace (ASCII Delete.)
168
+ }
169
+
170
+ # Keys that don't carry character data.
171
+ keycodes = {
172
+ # Home/End
173
+ 33: Keys.PageUp,
174
+ 34: Keys.PageDown,
175
+ 35: Keys.End,
176
+ 36: Keys.Home,
177
+ # Arrows
178
+ 37: Keys.Left,
179
+ 38: Keys.Up,
180
+ 39: Keys.Right,
181
+ 40: Keys.Down,
182
+ 45: Keys.Insert,
183
+ 46: Keys.Delete,
184
+ # F-keys.
185
+ 112: Keys.F1,
186
+ 113: Keys.F2,
187
+ 114: Keys.F3,
188
+ 115: Keys.F4,
189
+ 116: Keys.F5,
190
+ 117: Keys.F6,
191
+ 118: Keys.F7,
192
+ 119: Keys.F8,
193
+ 120: Keys.F9,
194
+ 121: Keys.F10,
195
+ 122: Keys.F11,
196
+ 123: Keys.F12,
197
+ }
198
+
199
+ LEFT_ALT_PRESSED = 0x0002
200
+ RIGHT_ALT_PRESSED = 0x0001
201
+ SHIFT_PRESSED = 0x0010
202
+ LEFT_CTRL_PRESSED = 0x0008
203
+ RIGHT_CTRL_PRESSED = 0x0004
204
+
205
+ def __init__(self, recognize_paste: bool = True) -> None:
206
+ self._fdcon = None
207
+ self.recognize_paste = recognize_paste
208
+
209
+ # When stdin is a tty, use that handle, otherwise, create a handle from
210
+ # CONIN$.
211
+ self.handle: HANDLE
212
+ if sys.stdin.isatty():
213
+ self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
214
+ else:
215
+ self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
216
+ self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon))
217
+
218
+ def close(self) -> None:
219
+ "Close fdcon."
220
+ if self._fdcon is not None:
221
+ os.close(self._fdcon)
222
+
223
+ def read(self) -> Iterable[KeyPress]:
224
+ """
225
+ Return a list of `KeyPress` instances. It won't return anything when
226
+ there was nothing to read. (This function doesn't block.)
227
+
228
+ http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
229
+ """
230
+ max_count = 2048 # Max events to read at the same time.
231
+
232
+ read = DWORD(0)
233
+ arrtype = INPUT_RECORD * max_count
234
+ input_records = arrtype()
235
+
236
+ # Check whether there is some input to read. `ReadConsoleInputW` would
237
+ # block otherwise.
238
+ # (Actually, the event loop is responsible to make sure that this
239
+ # function is only called when there is something to read, but for some
240
+ # reason this happened in the asyncio_win32 loop, and it's better to be
241
+ # safe anyway.)
242
+ if not wait_for_handles([self.handle], timeout=0):
243
+ return
244
+
245
+ # Get next batch of input event.
246
+ windll.kernel32.ReadConsoleInputW(
247
+ self.handle, pointer(input_records), max_count, pointer(read)
248
+ )
249
+
250
+ # First, get all the keys from the input buffer, in order to determine
251
+ # whether we should consider this a paste event or not.
252
+ all_keys = list(self._get_keys(read, input_records))
253
+
254
+ # Fill in 'data' for key presses.
255
+ all_keys = [self._insert_key_data(key) for key in all_keys]
256
+
257
+ # Correct non-bmp characters that are passed as separate surrogate codes
258
+ all_keys = list(self._merge_paired_surrogates(all_keys))
259
+
260
+ if self.recognize_paste and self._is_paste(all_keys):
261
+ gen = iter(all_keys)
262
+ k: KeyPress | None
263
+
264
+ for k in gen:
265
+ # Pasting: if the current key consists of text or \n, turn it
266
+ # into a BracketedPaste.
267
+ data = []
268
+ while k and (
269
+ not isinstance(k.key, Keys)
270
+ or k.key in {Keys.ControlJ, Keys.ControlM}
271
+ ):
272
+ data.append(k.data)
273
+ try:
274
+ k = next(gen)
275
+ except StopIteration:
276
+ k = None
277
+
278
+ if data:
279
+ yield KeyPress(Keys.BracketedPaste, "".join(data))
280
+ if k is not None:
281
+ yield k
282
+ else:
283
+ yield from all_keys
284
+
285
+ def _insert_key_data(self, key_press: KeyPress) -> KeyPress:
286
+ """
287
+ Insert KeyPress data, for vt100 compatibility.
288
+ """
289
+ if key_press.data:
290
+ return key_press
291
+
292
+ if isinstance(key_press.key, Keys):
293
+ data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "")
294
+ else:
295
+ data = ""
296
+
297
+ return KeyPress(key_press.key, data)
298
+
299
+ def _get_keys(
300
+ self, read: DWORD, input_records: Array[INPUT_RECORD]
301
+ ) -> Iterator[KeyPress]:
302
+ """
303
+ Generator that yields `KeyPress` objects from the input records.
304
+ """
305
+ for i in range(read.value):
306
+ ir = input_records[i]
307
+
308
+ # Get the right EventType from the EVENT_RECORD.
309
+ # (For some reason the Windows console application 'cmder'
310
+ # [http://gooseberrycreative.com/cmder/] can return '0' for
311
+ # ir.EventType. -- Just ignore that.)
312
+ if ir.EventType in EventTypes:
313
+ ev = getattr(ir.Event, EventTypes[ir.EventType])
314
+
315
+ # Process if this is a key event. (We also have mouse, menu and
316
+ # focus events.)
317
+ if isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown:
318
+ yield from self._event_to_key_presses(ev)
319
+
320
+ elif isinstance(ev, MOUSE_EVENT_RECORD):
321
+ yield from self._handle_mouse(ev)
322
+
323
+ @staticmethod
324
+ def _merge_paired_surrogates(key_presses: list[KeyPress]) -> Iterator[KeyPress]:
325
+ """
326
+ Combines consecutive KeyPresses with high and low surrogates into
327
+ single characters
328
+ """
329
+ buffered_high_surrogate = None
330
+ for key in key_presses:
331
+ is_text = not isinstance(key.key, Keys)
332
+ is_high_surrogate = is_text and "\ud800" <= key.key <= "\udbff"
333
+ is_low_surrogate = is_text and "\udc00" <= key.key <= "\udfff"
334
+
335
+ if buffered_high_surrogate:
336
+ if is_low_surrogate:
337
+ # convert high surrogate + low surrogate to single character
338
+ fullchar = (
339
+ (buffered_high_surrogate.key + key.key)
340
+ .encode("utf-16-le", "surrogatepass")
341
+ .decode("utf-16-le")
342
+ )
343
+ key = KeyPress(fullchar, fullchar)
344
+ else:
345
+ yield buffered_high_surrogate
346
+ buffered_high_surrogate = None
347
+
348
+ if is_high_surrogate:
349
+ buffered_high_surrogate = key
350
+ else:
351
+ yield key
352
+
353
+ if buffered_high_surrogate:
354
+ yield buffered_high_surrogate
355
+
356
+ @staticmethod
357
+ def _is_paste(keys: list[KeyPress]) -> bool:
358
+ """
359
+ Return `True` when we should consider this list of keys as a paste
360
+ event. Pasted text on windows will be turned into a
361
+ `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably
362
+ the best possible way to detect pasting of text and handle that
363
+ correctly.)
364
+ """
365
+ # Consider paste when it contains at least one newline and at least one
366
+ # other character.
367
+ text_count = 0
368
+ newline_count = 0
369
+
370
+ for k in keys:
371
+ if not isinstance(k.key, Keys):
372
+ text_count += 1
373
+ if k.key == Keys.ControlM:
374
+ newline_count += 1
375
+
376
+ return newline_count >= 1 and text_count >= 1
377
+
378
+ def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> list[KeyPress]:
379
+ """
380
+ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances.
381
+ """
382
+ assert isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown
383
+
384
+ result: KeyPress | None = None
385
+
386
+ control_key_state = ev.ControlKeyState
387
+ u_char = ev.uChar.UnicodeChar
388
+ # Use surrogatepass because u_char may be an unmatched surrogate
389
+ ascii_char = u_char.encode("utf-8", "surrogatepass")
390
+
391
+ # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the
392
+ # unicode code point truncated to 1 byte. See also:
393
+ # https://github.com/ipython/ipython/issues/10004
394
+ # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389
395
+
396
+ if u_char == "\x00":
397
+ if ev.VirtualKeyCode in self.keycodes:
398
+ result = KeyPress(self.keycodes[ev.VirtualKeyCode], "")
399
+ else:
400
+ if ascii_char in self.mappings:
401
+ if self.mappings[ascii_char] == Keys.ControlJ:
402
+ u_char = (
403
+ "\n" # Windows sends \n, turn into \r for unix compatibility.
404
+ )
405
+ result = KeyPress(self.mappings[ascii_char], u_char)
406
+ else:
407
+ result = KeyPress(u_char, u_char)
408
+
409
+ # First we handle Shift-Control-Arrow/Home/End (need to do this first)
410
+ if (
411
+ (
412
+ control_key_state & self.LEFT_CTRL_PRESSED
413
+ or control_key_state & self.RIGHT_CTRL_PRESSED
414
+ )
415
+ and control_key_state & self.SHIFT_PRESSED
416
+ and result
417
+ ):
418
+ mapping: dict[str, str] = {
419
+ Keys.Left: Keys.ControlShiftLeft,
420
+ Keys.Right: Keys.ControlShiftRight,
421
+ Keys.Up: Keys.ControlShiftUp,
422
+ Keys.Down: Keys.ControlShiftDown,
423
+ Keys.Home: Keys.ControlShiftHome,
424
+ Keys.End: Keys.ControlShiftEnd,
425
+ Keys.Insert: Keys.ControlShiftInsert,
426
+ Keys.PageUp: Keys.ControlShiftPageUp,
427
+ Keys.PageDown: Keys.ControlShiftPageDown,
428
+ }
429
+ result.key = mapping.get(result.key, result.key)
430
+
431
+ # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys.
432
+ if (
433
+ control_key_state & self.LEFT_CTRL_PRESSED
434
+ or control_key_state & self.RIGHT_CTRL_PRESSED
435
+ ) and result:
436
+ mapping = {
437
+ Keys.Left: Keys.ControlLeft,
438
+ Keys.Right: Keys.ControlRight,
439
+ Keys.Up: Keys.ControlUp,
440
+ Keys.Down: Keys.ControlDown,
441
+ Keys.Home: Keys.ControlHome,
442
+ Keys.End: Keys.ControlEnd,
443
+ Keys.Insert: Keys.ControlInsert,
444
+ Keys.Delete: Keys.ControlDelete,
445
+ Keys.PageUp: Keys.ControlPageUp,
446
+ Keys.PageDown: Keys.ControlPageDown,
447
+ }
448
+ result.key = mapping.get(result.key, result.key)
449
+
450
+ # Turn 'Tab' into 'BackTab' when shift was pressed.
451
+ # Also handle other shift-key combination
452
+ if control_key_state & self.SHIFT_PRESSED and result:
453
+ mapping = {
454
+ Keys.Tab: Keys.BackTab,
455
+ Keys.Left: Keys.ShiftLeft,
456
+ Keys.Right: Keys.ShiftRight,
457
+ Keys.Up: Keys.ShiftUp,
458
+ Keys.Down: Keys.ShiftDown,
459
+ Keys.Home: Keys.ShiftHome,
460
+ Keys.End: Keys.ShiftEnd,
461
+ Keys.Insert: Keys.ShiftInsert,
462
+ Keys.Delete: Keys.ShiftDelete,
463
+ Keys.PageUp: Keys.ShiftPageUp,
464
+ Keys.PageDown: Keys.ShiftPageDown,
465
+ }
466
+ result.key = mapping.get(result.key, result.key)
467
+
468
+ # Turn 'Space' into 'ControlSpace' when control was pressed.
469
+ if (
470
+ (
471
+ control_key_state & self.LEFT_CTRL_PRESSED
472
+ or control_key_state & self.RIGHT_CTRL_PRESSED
473
+ )
474
+ and result
475
+ and result.data == " "
476
+ ):
477
+ result = KeyPress(Keys.ControlSpace, " ")
478
+
479
+ # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot
480
+ # detect this combination. But it's really practical on Windows.)
481
+ if (
482
+ (
483
+ control_key_state & self.LEFT_CTRL_PRESSED
484
+ or control_key_state & self.RIGHT_CTRL_PRESSED
485
+ )
486
+ and result
487
+ and result.key == Keys.ControlJ
488
+ ):
489
+ return [KeyPress(Keys.Escape, ""), result]
490
+
491
+ # Return result. If alt was pressed, prefix the result with an
492
+ # 'Escape' key, just like unix VT100 terminals do.
493
+
494
+ # NOTE: Only replace the left alt with escape. The right alt key often
495
+ # acts as altgr and is used in many non US keyboard layouts for
496
+ # typing some special characters, like a backslash. We don't want
497
+ # all backslashes to be prefixed with escape. (Esc-\ has a
498
+ # meaning in E-macs, for instance.)
499
+ if result:
500
+ meta_pressed = control_key_state & self.LEFT_ALT_PRESSED
501
+
502
+ if meta_pressed:
503
+ return [KeyPress(Keys.Escape, ""), result]
504
+ else:
505
+ return [result]
506
+
507
+ else:
508
+ return []
509
+
510
+ def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> list[KeyPress]:
511
+ """
512
+ Handle mouse events. Return a list of KeyPress instances.
513
+ """
514
+ event_flags = ev.EventFlags
515
+ button_state = ev.ButtonState
516
+
517
+ event_type: MouseEventType | None = None
518
+ button: MouseButton = MouseButton.NONE
519
+
520
+ # Scroll events.
521
+ if event_flags & MOUSE_WHEELED:
522
+ if button_state > 0:
523
+ event_type = MouseEventType.SCROLL_UP
524
+ else:
525
+ event_type = MouseEventType.SCROLL_DOWN
526
+ else:
527
+ # Handle button state for non-scroll events.
528
+ if button_state == FROM_LEFT_1ST_BUTTON_PRESSED:
529
+ button = MouseButton.LEFT
530
+
531
+ elif button_state == RIGHTMOST_BUTTON_PRESSED:
532
+ button = MouseButton.RIGHT
533
+
534
+ # Move events.
535
+ if event_flags & MOUSE_MOVED:
536
+ event_type = MouseEventType.MOUSE_MOVE
537
+
538
+ # No key pressed anymore: mouse up.
539
+ if event_type is None:
540
+ if button_state > 0:
541
+ # Some button pressed.
542
+ event_type = MouseEventType.MOUSE_DOWN
543
+ else:
544
+ # No button pressed.
545
+ event_type = MouseEventType.MOUSE_UP
546
+
547
+ data = ";".join(
548
+ [
549
+ button.value,
550
+ event_type.value,
551
+ str(ev.MousePosition.X),
552
+ str(ev.MousePosition.Y),
553
+ ]
554
+ )
555
+ return [KeyPress(Keys.WindowsMouseEvent, data)]
556
+
557
+
558
+ class _Win32Handles:
559
+ """
560
+ Utility to keep track of which handles are connectod to which callbacks.
561
+
562
+ `add_win32_handle` starts a tiny event loop in another thread which waits
563
+ for the Win32 handle to become ready. When this happens, the callback will
564
+ be called in the current asyncio event loop using `call_soon_threadsafe`.
565
+
566
+ `remove_win32_handle` will stop this tiny event loop.
567
+
568
+ NOTE: We use this technique, so that we don't have to use the
569
+ `ProactorEventLoop` on Windows and we can wait for things like stdin
570
+ in a `SelectorEventLoop`. This is important, because our inputhook
571
+ mechanism (used by IPython), only works with the `SelectorEventLoop`.
572
+ """
573
+
574
+ def __init__(self) -> None:
575
+ self._handle_callbacks: dict[int, Callable[[], None]] = {}
576
+
577
+ # Windows Events that are triggered when we have to stop watching this
578
+ # handle.
579
+ self._remove_events: dict[int, HANDLE] = {}
580
+
581
+ def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None:
582
+ """
583
+ Add a Win32 handle to the event loop.
584
+ """
585
+ handle_value = handle.value
586
+
587
+ if handle_value is None:
588
+ raise ValueError("Invalid handle.")
589
+
590
+ # Make sure to remove a previous registered handler first.
591
+ self.remove_win32_handle(handle)
592
+
593
+ loop = get_running_loop()
594
+ self._handle_callbacks[handle_value] = callback
595
+
596
+ # Create remove event.
597
+ remove_event = create_win32_event()
598
+ self._remove_events[handle_value] = remove_event
599
+
600
+ # Add reader.
601
+ def ready() -> None:
602
+ # Tell the callback that input's ready.
603
+ try:
604
+ callback()
605
+ finally:
606
+ run_in_executor_with_context(wait, loop=loop)
607
+
608
+ # Wait for the input to become ready.
609
+ # (Use an executor for this, the Windows asyncio event loop doesn't
610
+ # allow us to wait for handles like stdin.)
611
+ def wait() -> None:
612
+ # Wait until either the handle becomes ready, or the remove event
613
+ # has been set.
614
+ result = wait_for_handles([remove_event, handle])
615
+
616
+ if result is remove_event:
617
+ windll.kernel32.CloseHandle(remove_event)
618
+ return
619
+ else:
620
+ loop.call_soon_threadsafe(ready)
621
+
622
+ run_in_executor_with_context(wait, loop=loop)
623
+
624
+ def remove_win32_handle(self, handle: HANDLE) -> Callable[[], None] | None:
625
+ """
626
+ Remove a Win32 handle from the event loop.
627
+ Return either the registered handler or `None`.
628
+ """
629
+ if handle.value is None:
630
+ return None # Ignore.
631
+
632
+ # Trigger remove events, so that the reader knows to stop.
633
+ try:
634
+ event = self._remove_events.pop(handle.value)
635
+ except KeyError:
636
+ pass
637
+ else:
638
+ windll.kernel32.SetEvent(event)
639
+
640
+ try:
641
+ return self._handle_callbacks.pop(handle.value)
642
+ except KeyError:
643
+ return None
644
+
645
+
646
+ @contextmanager
647
+ def attach_win32_input(
648
+ input: _Win32InputBase, callback: Callable[[], None]
649
+ ) -> Iterator[None]:
650
+ """
651
+ Context manager that makes this input active in the current event loop.
652
+
653
+ :param input: :class:`~prompt_toolkit.input.Input` object.
654
+ :param input_ready_callback: Called when the input is ready to read.
655
+ """
656
+ win32_handles = input.win32_handles
657
+ handle = input.handle
658
+
659
+ if handle.value is None:
660
+ raise ValueError("Invalid handle.")
661
+
662
+ # Add reader.
663
+ previous_callback = win32_handles.remove_win32_handle(handle)
664
+ win32_handles.add_win32_handle(handle, callback)
665
+
666
+ try:
667
+ yield
668
+ finally:
669
+ win32_handles.remove_win32_handle(handle)
670
+
671
+ if previous_callback:
672
+ win32_handles.add_win32_handle(handle, previous_callback)
673
+
674
+
675
+ @contextmanager
676
+ def detach_win32_input(input: _Win32InputBase) -> Iterator[None]:
677
+ win32_handles = input.win32_handles
678
+ handle = input.handle
679
+
680
+ if handle.value is None:
681
+ raise ValueError("Invalid handle.")
682
+
683
+ previous_callback = win32_handles.remove_win32_handle(handle)
684
+
685
+ try:
686
+ yield
687
+ finally:
688
+ if previous_callback:
689
+ win32_handles.add_win32_handle(handle, previous_callback)
690
+
691
+
692
+ class raw_mode:
693
+ """
694
+ ::
695
+
696
+ with raw_mode(stdin):
697
+ ''' the windows terminal is now in 'raw' mode. '''
698
+
699
+ The ``fileno`` attribute is ignored. This is to be compatible with the
700
+ `raw_input` method of `.vt100_input`.
701
+ """
702
+
703
+ def __init__(self, fileno: int | None = None) -> None:
704
+ self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
705
+
706
+ def __enter__(self) -> None:
707
+ # Remember original mode.
708
+ original_mode = DWORD()
709
+ windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode))
710
+ self.original_mode = original_mode
711
+
712
+ self._patch()
713
+
714
+ def _patch(self) -> None:
715
+ # Set raw
716
+ ENABLE_ECHO_INPUT = 0x0004
717
+ ENABLE_LINE_INPUT = 0x0002
718
+ ENABLE_PROCESSED_INPUT = 0x0001
719
+
720
+ windll.kernel32.SetConsoleMode(
721
+ self.handle,
722
+ self.original_mode.value
723
+ & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT),
724
+ )
725
+
726
+ def __exit__(self, *a: object) -> None:
727
+ # Restore original mode
728
+ windll.kernel32.SetConsoleMode(self.handle, self.original_mode)
729
+
730
+
731
+ class cooked_mode(raw_mode):
732
+ """
733
+ ::
734
+
735
+ with cooked_mode(stdin):
736
+ ''' The pseudo-terminal stdin is now used in cooked mode. '''
737
+ """
738
+
739
+ def _patch(self) -> None:
740
+ # Set cooked.
741
+ ENABLE_ECHO_INPUT = 0x0004
742
+ ENABLE_LINE_INPUT = 0x0002
743
+ ENABLE_PROCESSED_INPUT = 0x0001
744
+
745
+ windll.kernel32.SetConsoleMode(
746
+ self.handle,
747
+ self.original_mode.value
748
+ | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT),
749
+ )