deepy-cli 0.1.7__tar.gz → 0.1.9__tar.gz

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 (71) hide show
  1. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/PKG-INFO +2 -2
  2. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/README.md +1 -1
  3. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/pyproject.toml +1 -1
  4. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/shell.md +4 -0
  6. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/builtin.py +123 -13
  7. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/prompt_input.py +141 -0
  8. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/__main__.py +0 -0
  9. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/cli.py +0 -0
  10. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/config/__init__.py +0 -0
  11. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/config/settings.py +0 -0
  12. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/__init__.py +0 -0
  13. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  14. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/WebFetch.md +0 -0
  15. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/WebSearch.md +0 -0
  16. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/__init__.py +0 -0
  17. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/edit.md +0 -0
  18. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/modify.md +0 -0
  19. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/read.md +0 -0
  20. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/data/tools/write.md +0 -0
  21. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/errors.py +0 -0
  22. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/__init__.py +0 -0
  23. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/agent.py +0 -0
  24. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/compaction.py +0 -0
  25. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/context.py +0 -0
  26. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/events.py +0 -0
  27. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/model_capabilities.py +0 -0
  28. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/provider.py +0 -0
  29. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/replay.py +0 -0
  30. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/runner.py +0 -0
  31. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/llm/thinking.py +0 -0
  32. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/__init__.py +0 -0
  33. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/compact.py +0 -0
  34. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/rules.py +0 -0
  35. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/runtime_context.py +0 -0
  36. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/system.py +0 -0
  37. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/prompts/tool_docs.py +0 -0
  38. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/sessions/__init__.py +0 -0
  39. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/sessions/jsonl.py +0 -0
  40. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/sessions/manager.py +0 -0
  41. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/skills.py +0 -0
  42. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/status.py +0 -0
  43. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/__init__.py +0 -0
  44. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/agents.py +0 -0
  45. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/file_state.py +0 -0
  46. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/result.py +0 -0
  47. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/tools/shell_utils.py +0 -0
  48. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/__init__.py +0 -0
  49. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/app.py +0 -0
  50. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/ask_user_question.py +0 -0
  51. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/exit_summary.py +0 -0
  52. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/loading_text.py +0 -0
  53. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/markdown.py +0 -0
  54. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/message_view.py +0 -0
  55. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/model_picker.py +0 -0
  56. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/prompt_buffer.py +0 -0
  57. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/session_list.py +0 -0
  58. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/session_picker.py +0 -0
  59. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/slash_commands.py +0 -0
  60. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/styles.py +0 -0
  61. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/terminal.py +0 -0
  62. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/theme_picker.py +0 -0
  63. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/thinking_state.py +0 -0
  64. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/ui/welcome.py +0 -0
  65. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/update_check.py +0 -0
  66. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/usage.py +0 -0
  67. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/utils/__init__.py +0 -0
  68. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/utils/debug_logger.py +0 -0
  69. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/utils/error_logger.py +0 -0
  70. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/utils/json.py +0 -0
  71. {deepy_cli-0.1.7 → deepy_cli-0.1.9}/src/deepy/utils/notify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Deepy - Vibe coding for DeepSeek models in your terminal
5
5
  Keywords: deepseek,coding-agent,terminal,cli,agents
6
6
  Author: kirineko
@@ -238,5 +238,5 @@ assets live outside the package directory and are not included in the wheel.
238
238
 
239
239
  ## Release Status
240
240
 
241
- Deepy `0.1.7` is released through GitHub and PyPI. Standalone binaries and npm
241
+ Deepy `0.1.9` is released through GitHub and PyPI. Standalone binaries and npm
242
242
  wrappers can be added later, but the primary distribution is the Python CLI.
@@ -210,5 +210,5 @@ assets live outside the package directory and are not included in the wheel.
210
210
 
211
211
  ## Release Status
212
212
 
213
- Deepy `0.1.7` is released through GitHub and PyPI. Standalone binaries and npm
213
+ Deepy `0.1.9` is released through GitHub and PyPI. Standalone binaries and npm
214
214
  wrappers can be added later, but the primary distribution is the Python CLI.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.1.7"
3
+ version = "0.1.9"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.9"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -9,5 +9,9 @@ Use the runtime context's command dialect and path style: PowerShell uses
9
9
  PowerShell commands and Windows paths, `cmd` uses cmd syntax, and `posix` uses
10
10
  POSIX shell syntax.
11
11
 
12
+ On Windows PowerShell, Python child processes run with UTF-8 I/O defaults for
13
+ the command invocation; do not ask users to run `chcp` or change their
14
+ PowerShell profile for Unicode output.
15
+
12
16
  Runs in the session cwd, preserves cwd between calls when supported, and returns
13
17
  stdout/stderr JSON with cwd, exit-code, and shell metadata.
@@ -141,6 +141,13 @@ class MatchOccurrence:
141
141
  end_line: int
142
142
 
143
143
 
144
+ @dataclass(frozen=True)
145
+ class EditMatchResult:
146
+ matches: list[MatchOccurrence]
147
+ matched_text: str
148
+ matched_via: str
149
+
150
+
144
151
  @dataclass(frozen=True)
145
152
  class ClosestMatch:
146
153
  text: str
@@ -208,6 +215,7 @@ class ShellInvocation:
208
215
  shell_path: str
209
216
  args: list[str]
210
217
  runtime_environment: RuntimeEnvironment
218
+ env: dict[str, str] | None = None
211
219
 
212
220
 
213
221
  def _find_occurrences(text: str, needle: str, scope: tuple[int, int]) -> list[MatchOccurrence]:
@@ -231,6 +239,63 @@ def _find_occurrences(text: str, needle: str, scope: tuple[int, int]) -> list[Ma
231
239
  search_index = found + len(needle)
232
240
 
233
241
 
242
+ def _find_edit_occurrences(
243
+ text: str,
244
+ needle: str,
245
+ scope: tuple[int, int],
246
+ line_endings: str,
247
+ *,
248
+ matched_via: str = "exact",
249
+ ) -> EditMatchResult | None:
250
+ matches = _find_occurrences(text, needle, scope)
251
+ if matches:
252
+ return EditMatchResult(matches=matches, matched_text=needle, matched_via=matched_via)
253
+ normalized_needle = _normalize_line_endings(needle, line_endings)
254
+ if normalized_needle == needle:
255
+ return None
256
+ normalized_matches = _find_occurrences(text, normalized_needle, scope)
257
+ if not normalized_matches:
258
+ return None
259
+ return EditMatchResult(
260
+ matches=normalized_matches,
261
+ matched_text=normalized_needle,
262
+ matched_via=_line_ending_matched_via(matched_via),
263
+ )
264
+
265
+
266
+ def _find_loose_escape_edit_occurrences(
267
+ text: str,
268
+ needle: str,
269
+ scope: tuple[int, int],
270
+ line_endings: str,
271
+ ) -> list[tuple[MatchOccurrence, float, str, str]]:
272
+ matches = [
273
+ (occurrence, score, matched_text, "loose_escape")
274
+ for occurrence, score, matched_text in _find_loose_escape_occurrences(text, needle, scope)
275
+ ]
276
+ normalized_needle = _normalize_line_endings(needle, line_endings)
277
+ if normalized_needle == needle:
278
+ return matches
279
+ matches.extend(
280
+ (
281
+ occurrence,
282
+ score,
283
+ matched_text,
284
+ "loose_escape_line_endings",
285
+ )
286
+ for occurrence, score, matched_text in _find_loose_escape_occurrences(
287
+ text,
288
+ normalized_needle,
289
+ scope,
290
+ )
291
+ )
292
+ return matches
293
+
294
+
295
+ def _line_ending_matched_via(matched_via: str) -> str:
296
+ return "line_endings" if matched_via == "exact" else f"{matched_via}_line_endings"
297
+
298
+
234
299
  def _offset_to_line(text: str, offset: int) -> int:
235
300
  if offset <= 0:
236
301
  return 1
@@ -1308,12 +1373,14 @@ class ToolRuntime:
1308
1373
  return ToolResult.error_result(name, error or "File is not writable.").to_json()
1309
1374
  text_metadata = _read_text_metadata(target)
1310
1375
  text = text_metadata.content
1376
+ line_endings = text_metadata.line_endings
1311
1377
  scope = _edit_scope(text, snippet)
1312
- matches = _find_occurrences(text, old, scope)
1313
- matched_via = "exact"
1378
+ match_result = _find_edit_occurrences(text, old, scope, line_endings)
1379
+ matches = match_result.matches if match_result is not None else []
1380
+ matched_via = match_result.matched_via if match_result is not None else "exact"
1314
1381
  replacement_new = new
1315
1382
  if not matches:
1316
- loose_matches = _find_loose_escape_occurrences(text, old, scope)
1383
+ loose_matches = _find_loose_escape_edit_occurrences(text, old, scope, line_endings)
1317
1384
  if len(loose_matches) == 1 and loose_matches[0][1] == 1.0:
1318
1385
  corrected = _correct_escaped_strings_with_llm(
1319
1386
  self.settings,
@@ -1324,14 +1391,20 @@ class ToolRuntime:
1324
1391
  )
1325
1392
  if corrected is not None:
1326
1393
  corrected_old, corrected_new = corrected
1327
- corrected_matches = _find_occurrences(text, corrected_old, scope)
1328
- if corrected_matches:
1329
- matches = corrected_matches
1394
+ corrected_match_result = _find_edit_occurrences(
1395
+ text,
1396
+ corrected_old,
1397
+ scope,
1398
+ line_endings,
1399
+ matched_via="llm_escape_correction",
1400
+ )
1401
+ if corrected_match_result is not None:
1402
+ matches = corrected_match_result.matches
1330
1403
  replacement_new = corrected_new
1331
- matched_via = "llm_escape_correction"
1404
+ matched_via = corrected_match_result.matched_via
1332
1405
  if not matches:
1333
1406
  matches = [loose_matches[0][0]]
1334
- matched_via = "loose_escape"
1407
+ matched_via = loose_matches[0][3]
1335
1408
  if not matches:
1336
1409
  closest_match = _find_closest_match(text, old, scope)
1337
1410
  metadata = {"scope": _format_scope_metadata(target, snippet, scope, text)}
@@ -1363,7 +1436,6 @@ class ToolRuntime:
1363
1436
  ),
1364
1437
  },
1365
1438
  ).to_json()
1366
- line_endings = text_metadata.line_endings
1367
1439
  normalized_new = _normalize_line_endings(replacement_new, line_endings)
1368
1440
  updated = _apply_replacements(text, matches, normalized_new, replace_all)
1369
1441
  _write_text_with_encoding(target, updated, text_metadata.encoding)
@@ -1399,6 +1471,7 @@ class ToolRuntime:
1399
1471
  process = subprocess.Popen(
1400
1472
  [shell_invocation.shell_path, *shell_invocation.args],
1401
1473
  cwd=self.cwd,
1474
+ env=shell_invocation.env,
1402
1475
  text=True,
1403
1476
  stdout=stdout_file,
1404
1477
  stderr=stderr_file,
@@ -1750,8 +1823,7 @@ def _read_text_preserving_newlines(path: Path) -> str:
1750
1823
  def _read_text_metadata(path: Path) -> TextFileMetadata:
1751
1824
  data = path.read_bytes()
1752
1825
  encoding = _detect_text_encoding(data)
1753
- python_encoding = "utf-16" if encoding == "utf16le" else "utf-8"
1754
- text = data.decode(python_encoding, errors="replace")
1826
+ text = data.decode(_python_text_encoding(encoding), errors="replace")
1755
1827
  return TextFileMetadata(
1756
1828
  content=text,
1757
1829
  encoding=encoding,
@@ -1762,12 +1834,32 @@ def _read_text_metadata(path: Path) -> TextFileMetadata:
1762
1834
  def _detect_text_encoding(data: bytes) -> str:
1763
1835
  if len(data) >= 2 and data[0] == 0xFF and data[1] == 0xFE:
1764
1836
  return "utf16le"
1837
+ if data.startswith(b"\xef\xbb\xbf"):
1838
+ return "utf8-sig"
1839
+ try:
1840
+ data.decode("utf-8", errors="strict")
1841
+ return "utf8"
1842
+ except UnicodeDecodeError:
1843
+ pass
1844
+ try:
1845
+ data.decode("gb18030", errors="strict")
1846
+ return "gb18030"
1847
+ except UnicodeDecodeError:
1848
+ return "utf8"
1849
+
1850
+
1851
+ def _python_text_encoding(encoding: str) -> str:
1852
+ if encoding == "utf16le":
1853
+ return "utf-16"
1854
+ if encoding == "utf8-sig":
1855
+ return "utf-8-sig"
1856
+ if encoding == "gb18030":
1857
+ return "gb18030"
1765
1858
  return "utf8"
1766
1859
 
1767
1860
 
1768
1861
  def _write_text_with_encoding(path: Path, content: str, encoding: str) -> None:
1769
- python_encoding = "utf-16" if encoding == "utf16le" else "utf-8"
1770
- path.write_text(content, encoding=python_encoding)
1862
+ path.write_text(content, encoding=_python_text_encoding(encoding))
1771
1863
 
1772
1864
 
1773
1865
  def _coerce_write_content(path: Path, content: object) -> tuple[str, dict[str, object], str | None]:
@@ -2034,25 +2126,41 @@ def _build_shell_command(
2034
2126
  platform_name=platform_name,
2035
2127
  os_name=os_name,
2036
2128
  )
2129
+ process_env = _build_shell_process_env(runtime_environment, env)
2037
2130
  if runtime_environment.command_dialect == "powershell":
2038
2131
  return ShellInvocation(
2039
2132
  shell_path=resolved_shell,
2040
2133
  args=_build_powershell_args(command, marker),
2041
2134
  runtime_environment=runtime_environment,
2135
+ env=process_env,
2042
2136
  )
2043
2137
  if runtime_environment.command_dialect == "cmd":
2044
2138
  return ShellInvocation(
2045
2139
  shell_path=resolved_shell,
2046
2140
  args=_build_cmd_args(command, marker),
2047
2141
  runtime_environment=runtime_environment,
2142
+ env=process_env,
2048
2143
  )
2049
2144
  return ShellInvocation(
2050
2145
  shell_path=resolved_shell,
2051
2146
  args=_build_posix_shell_args(command, marker, resolved_shell),
2052
2147
  runtime_environment=runtime_environment,
2148
+ env=process_env,
2053
2149
  )
2054
2150
 
2055
2151
 
2152
+ def _build_shell_process_env(
2153
+ runtime_environment: RuntimeEnvironment,
2154
+ env: dict[str, str] | None = None,
2155
+ ) -> dict[str, str] | None:
2156
+ if runtime_environment.os_family != "windows":
2157
+ return dict(env) if env is not None else None
2158
+ process_env = dict(os.environ if env is None else env)
2159
+ process_env.setdefault("PYTHONUTF8", "1")
2160
+ process_env.setdefault("PYTHONIOENCODING", "utf-8")
2161
+ return process_env
2162
+
2163
+
2056
2164
  def _build_posix_shell_args(command: str, marker: str, shell_path: str) -> list[str]:
2057
2165
  normalized_command = rewrite_windows_null_redirect(command)
2058
2166
  parts = [
@@ -2073,6 +2181,8 @@ def _build_posix_shell_args(command: str, marker: str, shell_path: str) -> list[
2073
2181
  def _build_powershell_args(command: str, marker: str) -> list[str]:
2074
2182
  script = "\n".join(
2075
2183
  [
2184
+ "$OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
2185
+ "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
2076
2186
  "$global:LASTEXITCODE = $null",
2077
2187
  "try {",
2078
2188
  command,
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
+ import sys
5
6
  from typing import Callable
6
7
  from unicodedata import normalize
7
8
 
@@ -32,6 +33,8 @@ SHIFT_ENTER_SEQUENCES = (
32
33
  "\x1b[27;2;13~", # xterm modified-key format.
33
34
  "\x1b[13;2u", # Kitty/fixterms CSI-u format, used by modern terminals.
34
35
  )
36
+ _WINDOWS_SHIFT_ENTER_PATCH_ATTR = "_deepy_shift_enter_patched"
37
+ _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR = "_deepy_shift_enter_vt100_patched"
35
38
 
36
39
 
37
40
  @dataclass(frozen=True)
@@ -90,6 +93,12 @@ def build_prompt_key_bindings(
90
93
  def _(event) -> None: # pragma: no cover - prompt_toolkit calls this callback
91
94
  event.current_buffer.insert_text("\n")
92
95
 
96
+ if is_windows_newline_fallback_enabled():
97
+
98
+ @bindings.add("c-j")
99
+ def _(event) -> None: # pragma: no cover - prompt_toolkit calls this callback
100
+ event.current_buffer.insert_text("\n")
101
+
93
102
  return bindings
94
103
 
95
104
 
@@ -102,6 +111,138 @@ def install_shift_enter_key_sequence_overrides() -> None:
102
111
  prefix_cache = getattr(vt100_parser, "_IS_PREFIX_OF_LONGER_MATCH_CACHE", None)
103
112
  if hasattr(prefix_cache, "clear"):
104
113
  prefix_cache.clear()
114
+ install_windows_shift_enter_key_sequence_override()
115
+
116
+
117
+ def install_windows_shift_enter_key_sequence_override(
118
+ *,
119
+ platform_name: str | None = None,
120
+ console_input_reader_cls: type | None = None,
121
+ vt100_console_input_reader_cls: type | None = None,
122
+ event_types: object | None = None,
123
+ key_event_record_cls: type | None = None,
124
+ ) -> bool:
125
+ resolved_platform = platform_name or sys.platform
126
+ if not resolved_platform.startswith("win"):
127
+ return False
128
+ if console_input_reader_cls is None and vt100_console_input_reader_cls is None:
129
+ try:
130
+ from prompt_toolkit.input import win32
131
+ except (AssertionError, ImportError):
132
+ return False
133
+ console_input_reader_cls = win32.ConsoleInputReader
134
+ vt100_console_input_reader_cls = getattr(win32, "Vt100ConsoleInputReader", None)
135
+ if event_types is None:
136
+ event_types = getattr(win32, "EventTypes", None)
137
+ if key_event_record_cls is None:
138
+ key_event_record_cls = getattr(win32, "KEY_EVENT_RECORD", None)
139
+ patched = False
140
+ if console_input_reader_cls is not None:
141
+ patched = _patch_windows_console_input_reader(console_input_reader_cls) or patched
142
+ if vt100_console_input_reader_cls is not None:
143
+ patched = (
144
+ _patch_windows_vt100_console_input_reader(
145
+ vt100_console_input_reader_cls,
146
+ event_types=event_types,
147
+ key_event_record_cls=key_event_record_cls,
148
+ )
149
+ or patched
150
+ )
151
+ return patched
152
+
153
+
154
+ def _patch_windows_console_input_reader(console_input_reader_cls: type) -> bool:
155
+ if getattr(console_input_reader_cls, _WINDOWS_SHIFT_ENTER_PATCH_ATTR, False):
156
+ return True
157
+
158
+ from prompt_toolkit.key_binding.key_processor import KeyPress
159
+
160
+ original_handler = console_input_reader_cls._event_to_key_presses
161
+ shift_pressed = getattr(console_input_reader_cls, "SHIFT_PRESSED", 0x0010)
162
+
163
+ def patched_event_to_key_presses(self, ev):
164
+ key_presses = original_handler(self, ev)
165
+ if _is_windows_shift_enter_key_press(ev, key_presses, shift_pressed=shift_pressed):
166
+ return [KeyPress(Keys.Escape, ""), key_presses[0]]
167
+ return key_presses
168
+
169
+ setattr(
170
+ console_input_reader_cls,
171
+ "_deepy_original_event_to_key_presses",
172
+ original_handler,
173
+ )
174
+ console_input_reader_cls._event_to_key_presses = patched_event_to_key_presses
175
+ setattr(console_input_reader_cls, _WINDOWS_SHIFT_ENTER_PATCH_ATTR, True)
176
+ return True
177
+
178
+
179
+ def _patch_windows_vt100_console_input_reader(
180
+ vt100_console_input_reader_cls: type,
181
+ *,
182
+ event_types: object | None,
183
+ key_event_record_cls: type | None,
184
+ ) -> bool:
185
+ if event_types is None or key_event_record_cls is None:
186
+ return False
187
+ if getattr(vt100_console_input_reader_cls, _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR, False):
188
+ return True
189
+
190
+ original_get_keys = vt100_console_input_reader_cls._get_keys
191
+ shift_pressed = getattr(vt100_console_input_reader_cls, "SHIFT_PRESSED", 0x0010)
192
+
193
+ def patched_get_keys(self, read, input_records):
194
+ for index in range(read.value):
195
+ input_record = input_records[index]
196
+ if input_record.EventType not in event_types:
197
+ continue
198
+ event_name = event_types[input_record.EventType]
199
+ event = getattr(input_record.Event, event_name)
200
+ if not isinstance(event, key_event_record_cls) or not event.KeyDown:
201
+ continue
202
+ if _is_windows_shift_enter_event(event, shift_pressed=shift_pressed):
203
+ yield SHIFT_ENTER_SEQUENCES[0]
204
+ continue
205
+ u_char = event.uChar.UnicodeChar
206
+ if u_char != "\x00":
207
+ yield u_char
208
+
209
+ setattr(
210
+ vt100_console_input_reader_cls,
211
+ "_deepy_original_get_keys",
212
+ original_get_keys,
213
+ )
214
+ vt100_console_input_reader_cls._get_keys = patched_get_keys
215
+ setattr(vt100_console_input_reader_cls, _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR, True)
216
+ return True
217
+
218
+
219
+ def _is_windows_shift_enter_key_press(
220
+ ev,
221
+ key_presses: list,
222
+ *,
223
+ shift_pressed: int,
224
+ ) -> bool:
225
+ if not key_presses or len(key_presses) != 1:
226
+ return False
227
+ control_key_state = getattr(ev, "ControlKeyState", 0)
228
+ if not control_key_state & shift_pressed:
229
+ return False
230
+ key = getattr(key_presses[0], "key", None)
231
+ return key in {Keys.ControlM, Keys.ControlJ, Keys.Enter}
232
+
233
+
234
+ def _is_windows_shift_enter_event(ev, *, shift_pressed: int) -> bool:
235
+ control_key_state = getattr(ev, "ControlKeyState", 0)
236
+ if not control_key_state & shift_pressed:
237
+ return False
238
+ u_char = getattr(getattr(ev, "uChar", None), "UnicodeChar", "")
239
+ virtual_key_code = getattr(ev, "VirtualKeyCode", None)
240
+ return u_char in {"\r", "\n"} or virtual_key_code == 13
241
+
242
+
243
+ def is_windows_newline_fallback_enabled(platform_name: str | None = None) -> bool:
244
+ resolved_platform = platform_name or sys.platform
245
+ return resolved_platform.startswith("win")
105
246
 
106
247
 
107
248
  def prompt_for_input(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes