klaude-code 1.4.2__py3-none-any.whl → 1.5.0__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.
@@ -19,15 +19,15 @@ import re
19
19
  import shutil
20
20
  import subprocess
21
21
  import time
22
- from collections.abc import Iterable
22
+ from collections.abc import Callable, Iterable
23
23
  from pathlib import Path
24
24
  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 HTML
28
+ from prompt_toolkit.formatted_text import FormattedText
29
29
 
30
- from klaude_code.command import get_commands
30
+ from klaude_code.protocol.commands import CommandInfo
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).
@@ -40,12 +40,18 @@ AT_TOKEN_PATTERN = re.compile(r'(^|\s)@(?P<frag>"[^"]*"|[^\s]*)$')
40
40
  SKILL_TOKEN_PATTERN = re.compile(r"^[$¥](?P<frag>\S*)$")
41
41
 
42
42
 
43
- def create_repl_completer() -> Completer:
43
+ def create_repl_completer(
44
+ command_info_provider: Callable[[], list[CommandInfo]] | None = None,
45
+ ) -> Completer:
44
46
  """Create and return the combined REPL completer.
45
47
 
48
+ Args:
49
+ command_info_provider: Optional callable that returns command metadata.
50
+ If None, slash command completion is disabled.
51
+
46
52
  Returns a completer that handles both @ file paths and / slash commands.
47
53
  """
48
- return _ComboCompleter()
54
+ return _ComboCompleter(command_info_provider=command_info_provider)
49
55
 
50
56
 
51
57
  class _CmdResult(NamedTuple):
@@ -66,6 +72,9 @@ class _SlashCommandCompleter(Completer):
66
72
 
67
73
  _SLASH_TOKEN_RE = re.compile(r"^/(?P<frag>\S*)$")
68
74
 
75
+ def __init__(self, command_info_provider: Callable[[], list[CommandInfo]] | None = None) -> None:
76
+ self._command_info_provider = command_info_provider
77
+
69
78
  def get_completions(
70
79
  self,
71
80
  document: Document,
@@ -75,6 +84,9 @@ class _SlashCommandCompleter(Completer):
75
84
  if document.cursor_position_row != 0:
76
85
  return
77
86
 
87
+ if self._command_info_provider is None:
88
+ return
89
+
78
90
  text_before = document.current_line_before_cursor
79
91
  m = self._SLASH_TOKEN_RE.search(text_before)
80
92
  if not m:
@@ -84,38 +96,28 @@ class _SlashCommandCompleter(Completer):
84
96
  token_start = len(text_before) - len(f"/{frag}")
85
97
  start_position = token_start - len(text_before) # negative offset
86
98
 
87
- # Get available commands
88
- commands = get_commands()
99
+ # Get available commands from provider
100
+ command_infos = self._command_info_provider()
89
101
 
90
102
  # Filter commands that match the fragment (preserve registration order)
91
- matched: list[tuple[str, object, str]] = []
92
- for cmd_name, cmd_obj in commands.items():
93
- if cmd_name.startswith(frag):
94
- hint = f" [{cmd_obj.placeholder}]" if cmd_obj.support_addition_params else ""
95
- matched.append((cmd_name, cmd_obj, hint))
103
+ matched: list[tuple[str, CommandInfo, str]] = []
104
+ for cmd_info in command_infos:
105
+ if cmd_info.name.startswith(frag):
106
+ hint = f" [{cmd_info.placeholder}]" if cmd_info.support_addition_params else ""
107
+ matched.append((cmd_info.name, cmd_info, hint))
96
108
 
97
109
  if not matched:
98
110
  return
99
111
 
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
- 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
- )
112
+ for cmd_name, cmd_info, hint in matched:
114
113
  completion_text = f"/{cmd_name} "
114
+ # Use FormattedText to style the hint (placeholder) in bright black
115
+ display = FormattedText([("", cmd_name), ("ansibrightblack", hint)]) if hint else cmd_name
115
116
  yield Completion(
116
117
  text=completion_text,
117
118
  start_position=start_position,
118
- display=display_text,
119
+ display=display,
120
+ display_meta=cmd_info.summary,
119
121
  )
120
122
 
121
123
  def is_slash_command_context(self, document: Document) -> bool:
@@ -171,26 +173,18 @@ class _SkillCompleter(Completer):
171
173
  if not matched:
172
174
  return
173
175
 
174
- # Calculate max width for alignment
175
- max_name_len = max(len(name) for name, _, _ in matched)
176
- align_width = max(max_name_len, 20) + 2
176
+ # Calculate max location length for alignment
177
+ max_loc_len = max(len(loc) for _, _, loc in matched)
177
178
 
178
179
  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
180
  completion_text = f"${name} "
181
+ # Pad location to align descriptions
182
+ padded_location = f"[{location}]".ljust(max_loc_len + 2) # +2 for brackets
190
183
  yield Completion(
191
184
  text=completion_text,
192
185
  start_position=start_position,
193
- display=display_text,
186
+ display=name,
187
+ display_meta=f"{padded_location} {desc}",
194
188
  )
195
189
 
196
190
  def _get_available_skills(self) -> list[tuple[str, str, str]]:
@@ -218,9 +212,9 @@ class _SkillCompleter(Completer):
218
212
  class _ComboCompleter(Completer):
219
213
  """Combined completer that handles @ file paths, / slash commands, and $ skills."""
220
214
 
221
- def __init__(self) -> None:
215
+ def __init__(self, command_info_provider: Callable[[], list[CommandInfo]] | None = None) -> None:
222
216
  self._at_completer = _AtFilesCompleter()
223
- self._slash_completer = _SlashCommandCompleter()
217
+ self._slash_completer = _SlashCommandCompleter(command_info_provider=command_info_provider)
224
218
  self._skill_completer = _SkillCompleter()
225
219
 
226
220
  def get_completions(
@@ -318,14 +312,12 @@ class _AtFilesCompleter(Completer):
318
312
  if not suggestions:
319
313
  return [] # type: ignore[reportUnknownVariableType]
320
314
  start_position = token_start_in_input - len(text_before)
321
- visible_suggestions = suggestions[: self._max_results]
322
- display_align = self._display_align_width(visible_suggestions)
323
-
324
- for s in visible_suggestions:
315
+ for s in suggestions[: self._max_results]:
325
316
  yield Completion(
326
317
  text=self._format_completion_text(s, is_quoted=is_quoted),
327
318
  start_position=start_position,
328
- display=self._format_display_label(s, display_align),
319
+ display=self._format_display_label(s, 0),
320
+ display_meta=s,
329
321
  )
330
322
  return [] # type: ignore[reportUnknownVariableType]
331
323
 
@@ -336,15 +328,13 @@ class _AtFilesCompleter(Completer):
336
328
 
337
329
  # Prepare Completion objects. Replace from the '@' character.
338
330
  start_position = token_start_in_input - len(text_before) # negative
339
- visible_suggestions = suggestions[: self._max_results]
340
- display_align = self._display_align_width(visible_suggestions)
341
-
342
- for s in visible_suggestions:
331
+ for s in suggestions[: self._max_results]:
343
332
  # Insert formatted text (with quoting when needed) so that subsequent typing does not keep triggering
344
333
  yield Completion(
345
334
  text=self._format_completion_text(s, is_quoted=is_quoted),
346
335
  start_position=start_position,
347
- display=self._format_display_label(s, display_align),
336
+ display=self._format_display_label(s, 0),
337
+ display_meta=s,
348
338
  )
349
339
 
350
340
  # ---- Core logic ----
@@ -539,15 +529,15 @@ class _AtFilesCompleter(Completer):
539
529
  return f'@"{suggestion}" '
540
530
  return f"@{suggestion} "
541
531
 
542
- def _format_display_label(self, suggestion: str, align_width: int) -> HTML:
532
+ def _format_display_label(self, suggestion: str, align_width: int) -> str:
543
533
  """Format visible label for a completion option.
544
534
 
545
- Shows the basename (or directory name) aligned, followed by the full path in gray.
535
+ Keep this unstyled so that the completion menu's selection style can
536
+ fully override the selected row.
546
537
  """
547
538
 
548
- name = self._display_name(suggestion)
549
- padding = " " * (max(align_width - len(name), 0) + 2)
550
- return HTML(f"<style color='ansidefault'>{name}</style>{padding}<style color='ansibrightblack'>{suggestion}</style>")
539
+ _ = align_width
540
+ return self._display_name(suggestion)
551
541
 
552
542
  def _display_align_width(self, suggestions: list[str]) -> int:
553
543
  """Calculate alignment width for display labels."""
@@ -15,7 +15,7 @@ from klaude_code.ui.renderers.thinking import THINKING_MESSAGE_MARK, normalize_t
15
15
  from klaude_code.ui.rich import status as r_status
16
16
  from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
17
17
  from klaude_code.ui.rich.theme import ThemeKey
18
- from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier
18
+ from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier, emit_tmux_signal
19
19
  from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
20
20
 
21
21
 
@@ -509,6 +509,7 @@ class DisplayEventHandler:
509
509
  self.spinner_status.reset()
510
510
  self.renderer.spinner_stop()
511
511
  self.renderer.console.print(Rule(characters="─", style=ThemeKey.LINES))
512
+ emit_tmux_signal() # Signal test harness if KLAUDE_TEST_SIGNAL is set
512
513
  await self.stage_manager.transition_to(Stage.WAITING)
513
514
  self._maybe_notify_task_finish(event)
514
515