klaude-code 1.4.2__py3-none-any.whl → 1.4.3__py3-none-any.whl
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.
- klaude_code/ui/modes/repl/completers.py +24 -46
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +121 -9
- {klaude_code-1.4.2.dist-info → klaude_code-1.4.3.dist-info}/METADATA +1 -1
- {klaude_code-1.4.2.dist-info → klaude_code-1.4.3.dist-info}/RECORD +6 -6
- {klaude_code-1.4.2.dist-info → klaude_code-1.4.3.dist-info}/WHEEL +0 -0
- {klaude_code-1.4.2.dist-info → klaude_code-1.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -25,9 +25,9 @@ from typing import NamedTuple
|
|
|
25
25
|
|
|
26
26
|
from prompt_toolkit.completion import Completer, Completion
|
|
27
27
|
from prompt_toolkit.document import Document
|
|
28
|
-
from prompt_toolkit.formatted_text import
|
|
28
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
29
29
|
|
|
30
|
-
from klaude_code.command import get_commands
|
|
30
|
+
from klaude_code.command import CommandABC, get_commands
|
|
31
31
|
from klaude_code.trace.log import DebugType, log_debug
|
|
32
32
|
|
|
33
33
|
# Pattern to match @token for completion refresh (used by key bindings).
|
|
@@ -88,7 +88,7 @@ class _SlashCommandCompleter(Completer):
|
|
|
88
88
|
commands = get_commands()
|
|
89
89
|
|
|
90
90
|
# Filter commands that match the fragment (preserve registration order)
|
|
91
|
-
matched: list[tuple[str,
|
|
91
|
+
matched: list[tuple[str, CommandABC, str]] = []
|
|
92
92
|
for cmd_name, cmd_obj in commands.items():
|
|
93
93
|
if cmd_name.startswith(frag):
|
|
94
94
|
hint = f" [{cmd_obj.placeholder}]" if cmd_obj.support_addition_params else ""
|
|
@@ -97,25 +97,15 @@ class _SlashCommandCompleter(Completer):
|
|
|
97
97
|
if not matched:
|
|
98
98
|
return
|
|
99
99
|
|
|
100
|
-
# Calculate max width for alignment
|
|
101
|
-
# Find the longest command+hint length
|
|
102
|
-
max_len = max(len(name) + len(hint) for name, _, hint in matched)
|
|
103
|
-
# Set a minimum width (e.g. 20) and add some padding
|
|
104
|
-
align_width = max(max_len, 20) + 2
|
|
105
|
-
|
|
106
100
|
for cmd_name, cmd_obj, hint in matched:
|
|
107
|
-
label_len = len(cmd_name) + len(hint)
|
|
108
|
-
padding = " " * (align_width - label_len)
|
|
109
|
-
|
|
110
|
-
# Using HTML for formatting: default color command name, normal hint, gray summary
|
|
111
|
-
display_text = HTML(
|
|
112
|
-
f"<style color='ansidefault'>{cmd_name}</style>{hint}{padding}<style color='ansibrightblack'>{cmd_obj.summary}</style>" # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
|
113
|
-
)
|
|
114
101
|
completion_text = f"/{cmd_name} "
|
|
102
|
+
# Use FormattedText to style the hint (placeholder) in bright black
|
|
103
|
+
display = FormattedText([("", cmd_name), ("ansibrightblack", hint)]) if hint else cmd_name
|
|
115
104
|
yield Completion(
|
|
116
105
|
text=completion_text,
|
|
117
106
|
start_position=start_position,
|
|
118
|
-
display=
|
|
107
|
+
display=display,
|
|
108
|
+
display_meta=str(cmd_obj.summary),
|
|
119
109
|
)
|
|
120
110
|
|
|
121
111
|
def is_slash_command_context(self, document: Document) -> bool:
|
|
@@ -171,26 +161,18 @@ class _SkillCompleter(Completer):
|
|
|
171
161
|
if not matched:
|
|
172
162
|
return
|
|
173
163
|
|
|
174
|
-
# Calculate max
|
|
175
|
-
|
|
176
|
-
align_width = max(max_name_len, 20) + 2
|
|
164
|
+
# Calculate max location length for alignment
|
|
165
|
+
max_loc_len = max(len(loc) for _, _, loc in matched)
|
|
177
166
|
|
|
178
167
|
for name, desc, location in matched:
|
|
179
|
-
# Format: name [location] description
|
|
180
|
-
# Align location tags (max length is "project" = 7, plus brackets = 9)
|
|
181
|
-
padding_name = " " * (align_width - len(name))
|
|
182
|
-
location_tag = f"[{location}]".ljust(9)
|
|
183
|
-
|
|
184
|
-
# Using HTML for formatting: default color skill name, cyan location tag, gray description
|
|
185
|
-
display_text = HTML(
|
|
186
|
-
f"<style color='ansidefault'>{name}</style>{padding_name}<style color='ansicyan'>{location_tag}</style> "
|
|
187
|
-
f"<style color='ansibrightblack'>{desc}</style>"
|
|
188
|
-
)
|
|
189
168
|
completion_text = f"${name} "
|
|
169
|
+
# Pad location to align descriptions
|
|
170
|
+
padded_location = f"[{location}]".ljust(max_loc_len + 2) # +2 for brackets
|
|
190
171
|
yield Completion(
|
|
191
172
|
text=completion_text,
|
|
192
173
|
start_position=start_position,
|
|
193
|
-
display=
|
|
174
|
+
display=name,
|
|
175
|
+
display_meta=f"{padded_location} {desc}",
|
|
194
176
|
)
|
|
195
177
|
|
|
196
178
|
def _get_available_skills(self) -> list[tuple[str, str, str]]:
|
|
@@ -318,14 +300,12 @@ class _AtFilesCompleter(Completer):
|
|
|
318
300
|
if not suggestions:
|
|
319
301
|
return [] # type: ignore[reportUnknownVariableType]
|
|
320
302
|
start_position = token_start_in_input - len(text_before)
|
|
321
|
-
|
|
322
|
-
display_align = self._display_align_width(visible_suggestions)
|
|
323
|
-
|
|
324
|
-
for s in visible_suggestions:
|
|
303
|
+
for s in suggestions[: self._max_results]:
|
|
325
304
|
yield Completion(
|
|
326
305
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
327
306
|
start_position=start_position,
|
|
328
|
-
display=self._format_display_label(s,
|
|
307
|
+
display=self._format_display_label(s, 0),
|
|
308
|
+
display_meta=s,
|
|
329
309
|
)
|
|
330
310
|
return [] # type: ignore[reportUnknownVariableType]
|
|
331
311
|
|
|
@@ -336,15 +316,13 @@ class _AtFilesCompleter(Completer):
|
|
|
336
316
|
|
|
337
317
|
# Prepare Completion objects. Replace from the '@' character.
|
|
338
318
|
start_position = token_start_in_input - len(text_before) # negative
|
|
339
|
-
|
|
340
|
-
display_align = self._display_align_width(visible_suggestions)
|
|
341
|
-
|
|
342
|
-
for s in visible_suggestions:
|
|
319
|
+
for s in suggestions[: self._max_results]:
|
|
343
320
|
# Insert formatted text (with quoting when needed) so that subsequent typing does not keep triggering
|
|
344
321
|
yield Completion(
|
|
345
322
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
346
323
|
start_position=start_position,
|
|
347
|
-
display=self._format_display_label(s,
|
|
324
|
+
display=self._format_display_label(s, 0),
|
|
325
|
+
display_meta=s,
|
|
348
326
|
)
|
|
349
327
|
|
|
350
328
|
# ---- Core logic ----
|
|
@@ -539,15 +517,15 @@ class _AtFilesCompleter(Completer):
|
|
|
539
517
|
return f'@"{suggestion}" '
|
|
540
518
|
return f"@{suggestion} "
|
|
541
519
|
|
|
542
|
-
def _format_display_label(self, suggestion: str, align_width: int) ->
|
|
520
|
+
def _format_display_label(self, suggestion: str, align_width: int) -> str:
|
|
543
521
|
"""Format visible label for a completion option.
|
|
544
522
|
|
|
545
|
-
|
|
523
|
+
Keep this unstyled so that the completion menu's selection style can
|
|
524
|
+
fully override the selected row.
|
|
546
525
|
"""
|
|
547
526
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return HTML(f"<style color='ansidefault'>{name}</style>{padding}<style color='ansibrightblack'>{suggestion}</style>")
|
|
527
|
+
_ = align_width
|
|
528
|
+
return self._display_name(suggestion)
|
|
551
529
|
|
|
552
530
|
def _display_align_width(self, suggestions: list[str]) -> int:
|
|
553
531
|
"""Calculate alignment width for display labels."""
|
|
@@ -6,15 +6,20 @@ from collections.abc import AsyncIterator, Callable
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import NamedTuple, override
|
|
8
8
|
|
|
9
|
+
import prompt_toolkit.layout.menus as pt_menus
|
|
9
10
|
from prompt_toolkit import PromptSession
|
|
10
|
-
from prompt_toolkit.
|
|
11
|
+
from prompt_toolkit.application.current import get_app
|
|
12
|
+
from prompt_toolkit.completion import Completion, ThreadedCompleter
|
|
11
13
|
from prompt_toolkit.cursor_shapes import CursorShape
|
|
12
|
-
from prompt_toolkit.
|
|
14
|
+
from prompt_toolkit.data_structures import Point
|
|
15
|
+
from prompt_toolkit.formatted_text import FormattedText, StyleAndTextTuples, to_formatted_text
|
|
13
16
|
from prompt_toolkit.history import FileHistory
|
|
14
|
-
from prompt_toolkit.layout.containers import Container, FloatContainer
|
|
17
|
+
from prompt_toolkit.layout.containers import Container, FloatContainer, Window
|
|
18
|
+
from prompt_toolkit.layout.controls import UIContent
|
|
15
19
|
from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
|
|
16
20
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
17
21
|
from prompt_toolkit.styles import Style
|
|
22
|
+
from prompt_toolkit.utils import get_cwidth
|
|
18
23
|
|
|
19
24
|
from klaude_code.protocol.model import UserInputPayload
|
|
20
25
|
from klaude_code.ui.core.input import InputProviderABC
|
|
@@ -31,9 +36,9 @@ class REPLStatusSnapshot(NamedTuple):
|
|
|
31
36
|
update_message: str | None = None
|
|
32
37
|
|
|
33
38
|
|
|
34
|
-
COMPLETION_SELECTED_DARK_BG = "
|
|
35
|
-
COMPLETION_SELECTED_LIGHT_BG = "
|
|
36
|
-
COMPLETION_SELECTED_UNKNOWN_BG = "
|
|
39
|
+
COMPLETION_SELECTED_DARK_BG = "ansigreen"
|
|
40
|
+
COMPLETION_SELECTED_LIGHT_BG = "ansigreen"
|
|
41
|
+
COMPLETION_SELECTED_UNKNOWN_BG = "ansigreen"
|
|
37
42
|
COMPLETION_MENU = "ansibrightblack"
|
|
38
43
|
INPUT_PROMPT_STYLE = "ansimagenta bold"
|
|
39
44
|
PLACEHOLDER_TEXT_STYLE_DARK_BG = "fg:#5a5a5a italic"
|
|
@@ -63,6 +68,109 @@ def _left_align_completion_menus(container: Container) -> None:
|
|
|
63
68
|
_left_align_completion_menus(child)
|
|
64
69
|
|
|
65
70
|
|
|
71
|
+
class _KlaudeCompletionsMenuControl(pt_menus.CompletionsMenuControl):
|
|
72
|
+
"""CompletionsMenuControl with stable 2-char left prefix.
|
|
73
|
+
|
|
74
|
+
Requirements:
|
|
75
|
+
- Add a 2-character prefix for every row.
|
|
76
|
+
- Render "→ " for the selected row, and " " for non-selected rows.
|
|
77
|
+
|
|
78
|
+
Keep completion text unstyled so that the menu's current-row style can
|
|
79
|
+
override it entirely.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
_PREFIX_WIDTH = 2
|
|
83
|
+
|
|
84
|
+
def _get_menu_width(self, max_width: int, complete_state: pt_menus.CompletionState) -> int: # pyright: ignore[reportPrivateImportUsage]
|
|
85
|
+
"""Return the width of the main column.
|
|
86
|
+
|
|
87
|
+
This is prompt_toolkit's default implementation, except we reserve one
|
|
88
|
+
extra character for the 2-char prefix ("→ "/" ").
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
return min(
|
|
92
|
+
max_width,
|
|
93
|
+
max(
|
|
94
|
+
self.MIN_WIDTH,
|
|
95
|
+
max(get_cwidth(c.display_text) for c in complete_state.completions) + 3,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def create_content(self, width: int, height: int) -> UIContent:
|
|
100
|
+
complete_state = get_app().current_buffer.complete_state
|
|
101
|
+
if complete_state:
|
|
102
|
+
completions = complete_state.completions
|
|
103
|
+
index = complete_state.complete_index
|
|
104
|
+
|
|
105
|
+
menu_width = self._get_menu_width(width, complete_state)
|
|
106
|
+
menu_meta_width = self._get_menu_meta_width(width - menu_width, complete_state)
|
|
107
|
+
show_meta = self._show_meta(complete_state)
|
|
108
|
+
|
|
109
|
+
def get_line(i: int) -> StyleAndTextTuples:
|
|
110
|
+
completion = completions[i]
|
|
111
|
+
is_current_completion = i == index
|
|
112
|
+
|
|
113
|
+
result = self._get_menu_item_fragments_with_cursor(
|
|
114
|
+
completion,
|
|
115
|
+
is_current_completion,
|
|
116
|
+
menu_width,
|
|
117
|
+
space_after=True,
|
|
118
|
+
)
|
|
119
|
+
if show_meta:
|
|
120
|
+
result += self._get_menu_item_meta_fragments(
|
|
121
|
+
completion,
|
|
122
|
+
is_current_completion,
|
|
123
|
+
menu_meta_width,
|
|
124
|
+
)
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
return UIContent(
|
|
128
|
+
get_line=get_line,
|
|
129
|
+
cursor_position=Point(x=0, y=index or 0),
|
|
130
|
+
line_count=len(completions),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return UIContent()
|
|
134
|
+
|
|
135
|
+
def _get_menu_item_fragments_with_cursor(
|
|
136
|
+
self,
|
|
137
|
+
completion: Completion,
|
|
138
|
+
is_current_completion: bool,
|
|
139
|
+
width: int,
|
|
140
|
+
*,
|
|
141
|
+
space_after: bool = False,
|
|
142
|
+
) -> StyleAndTextTuples:
|
|
143
|
+
if is_current_completion:
|
|
144
|
+
style_str = f"class:completion-menu.completion.current {completion.style} {completion.selected_style}"
|
|
145
|
+
prefix = "→ "
|
|
146
|
+
else:
|
|
147
|
+
style_str = "class:completion-menu.completion " + completion.style
|
|
148
|
+
prefix = " "
|
|
149
|
+
|
|
150
|
+
max_text_width = width - self._PREFIX_WIDTH - (1 if space_after else 0)
|
|
151
|
+
text, text_width = pt_menus._trim_formatted_text(completion.display, max_text_width) # pyright: ignore[reportPrivateUsage]
|
|
152
|
+
padding = " " * (width - self._PREFIX_WIDTH - text_width)
|
|
153
|
+
|
|
154
|
+
return to_formatted_text(
|
|
155
|
+
[("", prefix), *text, ("", padding)],
|
|
156
|
+
style=style_str,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _patch_completion_menu_controls(container: Container) -> None:
|
|
161
|
+
"""Replace prompt_toolkit completion menu controls with customized versions."""
|
|
162
|
+
|
|
163
|
+
if isinstance(container, Window):
|
|
164
|
+
content = container.content
|
|
165
|
+
if isinstance(content, pt_menus.CompletionsMenuControl) and not isinstance(
|
|
166
|
+
content, _KlaudeCompletionsMenuControl
|
|
167
|
+
):
|
|
168
|
+
container.content = _KlaudeCompletionsMenuControl()
|
|
169
|
+
|
|
170
|
+
for child in container.get_children():
|
|
171
|
+
_patch_completion_menu_controls(child)
|
|
172
|
+
|
|
173
|
+
|
|
66
174
|
class PromptToolkitInput(InputProviderABC):
|
|
67
175
|
def __init__(
|
|
68
176
|
self,
|
|
@@ -119,10 +227,10 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
119
227
|
"completion-menu.border": "bg:default",
|
|
120
228
|
"scrollbar.background": "bg:default",
|
|
121
229
|
"scrollbar.button": "bg:default",
|
|
122
|
-
"completion-menu.completion":
|
|
230
|
+
"completion-menu.completion": "bg:default fg:default",
|
|
123
231
|
"completion-menu.meta.completion": f"bg:default fg:{COMPLETION_MENU}",
|
|
124
|
-
"completion-menu.completion.current": f"noreverse bg:default fg:{completion_selected}
|
|
125
|
-
"completion-menu.meta.completion.current": f"bg:default fg:{completion_selected}
|
|
232
|
+
"completion-menu.completion.current": f"noreverse bg:default fg:{completion_selected}",
|
|
233
|
+
"completion-menu.meta.completion.current": f"bg:default fg:{completion_selected}",
|
|
126
234
|
}
|
|
127
235
|
),
|
|
128
236
|
)
|
|
@@ -131,6 +239,10 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
131
239
|
with contextlib.suppress(Exception):
|
|
132
240
|
_left_align_completion_menus(self._session.app.layout.container)
|
|
133
241
|
|
|
242
|
+
# Customize completion rendering (2-space indent + selected arrow prefix).
|
|
243
|
+
with contextlib.suppress(Exception):
|
|
244
|
+
_patch_completion_menu_controls(self._session.app.layout.container)
|
|
245
|
+
|
|
134
246
|
def _select_first_completion_on_open(buf) -> None: # type: ignore[no-untyped-def]
|
|
135
247
|
"""Default to selecting the first completion without inserting it."""
|
|
136
248
|
|
|
@@ -169,10 +169,10 @@ klaude_code/ui/modes/exec/__init__.py,sha256=RsYa-DmDJj6g7iXb4H9mm2_Cu-KDQOD10RJ
|
|
|
169
169
|
klaude_code/ui/modes/exec/display.py,sha256=m2kkgaUoGD9rEVUmcm7Vs_PyAI2iruKCJYRhANjSsKo,1965
|
|
170
170
|
klaude_code/ui/modes/repl/__init__.py,sha256=_0II73jlz5JUtvJsZ9sGRJzeHIQyJJpaI0egvmryYNw,286
|
|
171
171
|
klaude_code/ui/modes/repl/clipboard.py,sha256=ZCpk7kRSXGhh0Q_BWtUUuSYT7ZOqRjAoRcg9T9n48Wo,5137
|
|
172
|
-
klaude_code/ui/modes/repl/completers.py,sha256=
|
|
172
|
+
klaude_code/ui/modes/repl/completers.py,sha256=Q2W3oCXrxjBUH-daVvTNDTqT-aZGNpljMSHQD-XxW4s,31197
|
|
173
173
|
klaude_code/ui/modes/repl/display.py,sha256=06wawOHWO2ItEA9EIEh97p3GDID7TJhAtpaA03nPQXs,2335
|
|
174
174
|
klaude_code/ui/modes/repl/event_handler.py,sha256=pXjiLGilSzrsrr9lsk19NeiRGFjVq91Rtfp1PNLK36A,26026
|
|
175
|
-
klaude_code/ui/modes/repl/input_prompt_toolkit.py,sha256=
|
|
175
|
+
klaude_code/ui/modes/repl/input_prompt_toolkit.py,sha256=WrQ5lyFflerFpeiFdxWyFzRAIQ0O_fOhqMF-IZBvbSU,14122
|
|
176
176
|
klaude_code/ui/modes/repl/key_bindings.py,sha256=NKllb1W0LfJAZEqDjlW2aedBQ2PBEOOddoTiRNY1_mU,12121
|
|
177
177
|
klaude_code/ui/modes/repl/renderer.py,sha256=7cum6SuKSuuBePDSyk4UvWs6q5dwgLA0NrJZ3eD9tHw,15902
|
|
178
178
|
klaude_code/ui/renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -205,7 +205,7 @@ klaude_code/ui/terminal/progress_bar.py,sha256=MDnhPbqCnN4GDgLOlxxOEVZPDwVC_XL2N
|
|
|
205
205
|
klaude_code/ui/terminal/selector.py,sha256=v0akaR4Jw5U_3fgK4INqf0R2nIhRZC-JqhECsF3z22I,9633
|
|
206
206
|
klaude_code/ui/utils/__init__.py,sha256=YEsCLjbCPaPza-UXTPUMTJTrc9BmNBUP5CbFWlshyOQ,15
|
|
207
207
|
klaude_code/ui/utils/common.py,sha256=tqHqwgLtAyP805kwRFyoAL4EgMutcNb3Y-GAXJ4IeuM,2263
|
|
208
|
-
klaude_code-1.4.
|
|
209
|
-
klaude_code-1.4.
|
|
210
|
-
klaude_code-1.4.
|
|
211
|
-
klaude_code-1.4.
|
|
208
|
+
klaude_code-1.4.3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
209
|
+
klaude_code-1.4.3.dist-info/entry_points.txt,sha256=kkXIXedaTOtjXPr2rVjRVVXZYlFUcBHELaqmyVlWUFA,92
|
|
210
|
+
klaude_code-1.4.3.dist-info/METADATA,sha256=QOXxiUSRdWeasTsWC7u-WqN3JIEzSxE0fyK3D2gQIjM,9091
|
|
211
|
+
klaude_code-1.4.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|