jusi-shell 0.2.0__tar.gz → 0.2.2__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.
- jusi_shell-0.2.2/.gitignore +4 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/PKG-INFO +1 -1
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/pyproject.toml +1 -1
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/__init__.py +1 -1
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/completion.py +9 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/worker.py +10 -2
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/tests/test_shell_plugin.py +30 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/LICENSE +0 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/README.md +0 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/application.py +0 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/catalog.py +0 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/control.py +0 -0
- {jusi_shell-0.2.0 → jusi_shell-0.2.2}/src/jusi_shell/kernel.py +0 -0
|
@@ -5,6 +5,13 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
MAX_COMPLETION_ITEMS = 500
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CompletionLimitExceeded(Exception):
|
|
12
|
+
"""The completion would exceed Jusi's protocol item limit."""
|
|
13
|
+
|
|
14
|
+
|
|
8
15
|
def _active_word(prefix: str) -> tuple[str, int]:
|
|
9
16
|
line_start = prefix.rfind("\n") + 1
|
|
10
17
|
line = prefix[line_start:]
|
|
@@ -39,6 +46,8 @@ def complete_paths(payload: dict[str, Any], cwd: str) -> dict[str, list[dict[str
|
|
|
39
46
|
for entry in entries:
|
|
40
47
|
if not entry.name.startswith(name_prefix):
|
|
41
48
|
continue
|
|
49
|
+
if len(items) == MAX_COMPLETION_ITEMS:
|
|
50
|
+
raise CompletionLimitExceeded
|
|
42
51
|
text = typed_parent + entry.name
|
|
43
52
|
if entry.is_dir():
|
|
44
53
|
text += "/"
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from jusi.plugin_api import OperationRejected, WorkerResult, copy_text, open_text, show_diff, terminal_surface
|
|
13
13
|
|
|
14
|
-
from .completion import complete_paths
|
|
14
|
+
from .completion import CompletionLimitExceeded, MAX_COMPLETION_ITEMS, complete_paths
|
|
15
15
|
from .control import exchange
|
|
16
16
|
|
|
17
17
|
|
|
@@ -40,7 +40,15 @@ class ShellWorker:
|
|
|
40
40
|
if operation == "complete":
|
|
41
41
|
status = self._request({"type": "status"})
|
|
42
42
|
cwd = str(status.get("cwd", "")).strip() or self.cwd
|
|
43
|
-
|
|
43
|
+
try:
|
|
44
|
+
completion = complete_paths(payload, cwd)
|
|
45
|
+
except CompletionLimitExceeded:
|
|
46
|
+
raise OperationRejected(
|
|
47
|
+
f"Path completion has more than {MAX_COMPLETION_ITEMS} matches; "
|
|
48
|
+
"type more characters to narrow it down",
|
|
49
|
+
reason="invalid_request",
|
|
50
|
+
) from None
|
|
51
|
+
return WorkerResult(completion)
|
|
44
52
|
if operation == "editor_action":
|
|
45
53
|
return self._editor_action(payload)
|
|
46
54
|
raise OperationRejected(f"Unsupported shell operation: {operation}", reason="unsupported")
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import pty
|
|
7
|
+
import re
|
|
7
8
|
import select
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
@@ -24,6 +25,15 @@ from jusi_shell.kernel import configure_jusi_runtime_v1, jusi_kernel_adapter_v1,
|
|
|
24
25
|
from jusi_shell.worker import ShellWorker, _followup_body
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
class VersionTest(unittest.TestCase):
|
|
29
|
+
def test_runtime_version_matches_project_metadata(self) -> None:
|
|
30
|
+
pyproject = Path(__file__).parents[1] / "pyproject.toml"
|
|
31
|
+
match = re.search(r'^version = "([^"]+)"$', pyproject.read_text(), re.MULTILINE)
|
|
32
|
+
|
|
33
|
+
self.assertIsNotNone(match)
|
|
34
|
+
self.assertEqual(match.group(1), __version__)
|
|
35
|
+
|
|
36
|
+
|
|
27
37
|
class FakeIPython:
|
|
28
38
|
def __init__(self) -> None:
|
|
29
39
|
self.magics_manager = type("MagicsManager", (), {"magics": {"cell": {}}})()
|
|
@@ -137,6 +147,26 @@ class WorkerTest(unittest.TestCase):
|
|
|
137
147
|
worker.handle("execute", {"cwd": "/definitely/not/a/jusi-shell-directory"})
|
|
138
148
|
self.assertIsNone(worker.runtime_directory)
|
|
139
149
|
|
|
150
|
+
def test_too_many_completions_are_recoverable(self) -> None:
|
|
151
|
+
from jusi.plugin_api import OperationRejected
|
|
152
|
+
|
|
153
|
+
worker = ShellWorker(object())
|
|
154
|
+
with tempfile.TemporaryDirectory() as crowded, tempfile.TemporaryDirectory() as narrow:
|
|
155
|
+
for index in range(501):
|
|
156
|
+
Path(crowded, f"item-{index:03}").touch()
|
|
157
|
+
Path(narrow, "item-only").touch()
|
|
158
|
+
|
|
159
|
+
with patch.object(worker, "_request", side_effect=({"cwd": crowded}, {"cwd": narrow})):
|
|
160
|
+
with self.assertRaisesRegex(OperationRejected, "more than 500 matches") as raised:
|
|
161
|
+
worker.handle("complete", {"prefix": "cat item-", "cursor_pos": len("cat item-")})
|
|
162
|
+
self.assertEqual("invalid_request", raised.exception.reason)
|
|
163
|
+
|
|
164
|
+
result = worker.handle(
|
|
165
|
+
"complete", {"prefix": "cat item-", "cursor_pos": len("cat item-")}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
self.assertEqual(["item-only"], [item["text"] for item in result.result["items"]])
|
|
169
|
+
|
|
140
170
|
def test_editor_actions_export_content_not_paths(self) -> None:
|
|
141
171
|
worker = ShellWorker(object())
|
|
142
172
|
copied = worker.handle("editor_action", {"action": "copy", "selection": {"text": "/target/file"}})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|