jarvis-ai-assistant 0.3.18__py3-none-any.whl → 0.3.20__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +30 -12
- jarvis/jarvis_agent/config_editor.py +1 -1
- jarvis/jarvis_agent/edit_file_handler.py +8 -13
- jarvis/jarvis_agent/memory_manager.py +4 -4
- jarvis/jarvis_agent/shell_input_handler.py +17 -2
- jarvis/jarvis_agent/task_analyzer.py +4 -3
- jarvis/jarvis_agent/task_manager.py +6 -6
- jarvis/jarvis_agent/tool_executor.py +2 -2
- jarvis/jarvis_code_agent/code_agent.py +21 -29
- jarvis/jarvis_code_analysis/code_review.py +2 -4
- jarvis/jarvis_git_utils/git_commiter.py +17 -18
- jarvis/jarvis_methodology/main.py +12 -12
- jarvis/jarvis_platform/ai8.py +0 -4
- jarvis/jarvis_platform/base.py +16 -15
- jarvis/jarvis_platform/kimi.py +13 -13
- jarvis/jarvis_platform/tongyi.py +17 -15
- jarvis/jarvis_platform/yuanbao.py +11 -11
- jarvis/jarvis_platform_manager/service.py +2 -2
- jarvis/jarvis_rag/cli.py +36 -32
- jarvis/jarvis_rag/embedding_manager.py +11 -6
- jarvis/jarvis_rag/llm_interface.py +6 -5
- jarvis/jarvis_rag/rag_pipeline.py +9 -8
- jarvis/jarvis_rag/reranker.py +3 -2
- jarvis/jarvis_rag/retriever.py +18 -8
- jarvis/jarvis_smart_shell/main.py +307 -47
- jarvis/jarvis_stats/cli.py +2 -1
- jarvis/jarvis_stats/stats.py +45 -5
- jarvis/jarvis_stats/storage.py +220 -9
- jarvis/jarvis_tools/clear_memory.py +0 -11
- jarvis/jarvis_tools/cli/main.py +18 -17
- jarvis/jarvis_tools/edit_file.py +4 -4
- jarvis/jarvis_tools/execute_script.py +5 -1
- jarvis/jarvis_tools/file_analyzer.py +6 -6
- jarvis/jarvis_tools/generate_new_tool.py +6 -17
- jarvis/jarvis_tools/read_code.py +3 -6
- jarvis/jarvis_tools/read_webpage.py +4 -4
- jarvis/jarvis_tools/registry.py +8 -28
- jarvis/jarvis_tools/retrieve_memory.py +5 -16
- jarvis/jarvis_tools/rewrite_file.py +0 -4
- jarvis/jarvis_tools/save_memory.py +2 -10
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +22 -40
- jarvis/jarvis_utils/clipboard.py +2 -2
- jarvis/jarvis_utils/input.py +316 -30
- jarvis/jarvis_utils/methodology.py +3 -3
- jarvis/jarvis_utils/output.py +215 -135
- jarvis/jarvis_utils/utils.py +35 -58
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/RECORD +54 -54
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/input.py
CHANGED
@@ -9,7 +9,10 @@
|
|
9
9
|
- 用于输入控制的自定义键绑定
|
10
10
|
"""
|
11
11
|
import os
|
12
|
+
import sys
|
13
|
+
import base64
|
12
14
|
from typing import Iterable, List
|
15
|
+
import wcwidth
|
13
16
|
|
14
17
|
from colorama import Fore
|
15
18
|
from colorama import Style as ColoramaStyle
|
@@ -26,6 +29,8 @@ from prompt_toolkit.document import Document
|
|
26
29
|
from prompt_toolkit.formatted_text import FormattedText
|
27
30
|
from prompt_toolkit.history import FileHistory
|
28
31
|
from prompt_toolkit.key_binding import KeyBindings
|
32
|
+
from prompt_toolkit.enums import DEFAULT_BUFFER
|
33
|
+
from prompt_toolkit.filters import has_focus
|
29
34
|
from prompt_toolkit.layout.containers import Window
|
30
35
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
31
36
|
from prompt_toolkit.layout.layout import Layout
|
@@ -39,10 +44,63 @@ from jarvis.jarvis_utils.tag import ot
|
|
39
44
|
|
40
45
|
# Sentinel value to indicate that Ctrl+O was pressed
|
41
46
|
CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
|
47
|
+
# Sentinel prefix to indicate that Ctrl+F (fzf) inserted content should prefill next prompt
|
48
|
+
FZF_INSERT_SENTINEL_PREFIX = "__FZF_INSERT__::"
|
49
|
+
# Sentinel to request running fzf outside the prompt and then prefill next prompt
|
50
|
+
FZF_REQUEST_SENTINEL_PREFIX = "__FZF_REQUEST__::"
|
42
51
|
|
43
52
|
# Persistent hint marker for multiline input (shown only once across runs)
|
44
53
|
_MULTILINE_HINT_MARK_FILE = os.path.join(get_data_dir(), "multiline_enter_hint_shown")
|
45
54
|
|
55
|
+
def _display_width(s: str) -> int:
|
56
|
+
"""Calculate printable width of a string in terminal columns (handles wide chars)."""
|
57
|
+
try:
|
58
|
+
w = 0
|
59
|
+
for ch in s:
|
60
|
+
cw = wcwidth.wcwidth(ch)
|
61
|
+
if cw is None or cw < 0:
|
62
|
+
# Fallback for unknown width chars (e.g. emoji on some terminals)
|
63
|
+
cw = 1
|
64
|
+
w += cw
|
65
|
+
return w
|
66
|
+
except Exception:
|
67
|
+
return len(s)
|
68
|
+
|
69
|
+
def _calc_prompt_rows(prev_text: str) -> int:
|
70
|
+
"""
|
71
|
+
Estimate how many terminal rows the previous prompt occupied.
|
72
|
+
Considers prompt prefix and soft-wrapping across terminal columns.
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
cols = os.get_terminal_size().columns
|
76
|
+
except Exception:
|
77
|
+
cols = 80
|
78
|
+
prefix = "👤 > "
|
79
|
+
prefix_w = _display_width(prefix)
|
80
|
+
|
81
|
+
if prev_text is None:
|
82
|
+
return 1
|
83
|
+
|
84
|
+
lines = prev_text.splitlines()
|
85
|
+
if not lines:
|
86
|
+
lines = [""]
|
87
|
+
# If the text ends with a newline, there is a visible empty line at the end.
|
88
|
+
if prev_text.endswith("\n"):
|
89
|
+
lines.append("")
|
90
|
+
total_rows = 0
|
91
|
+
for i, line in enumerate(lines):
|
92
|
+
lw = _display_width(line)
|
93
|
+
if i == 0:
|
94
|
+
width = prefix_w + lw
|
95
|
+
else:
|
96
|
+
width = lw
|
97
|
+
rows = max(1, (width + cols - 1) // cols)
|
98
|
+
total_rows += rows
|
99
|
+
return max(1, total_rows)
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
46
104
|
|
47
105
|
def _multiline_hint_already_shown() -> bool:
|
48
106
|
"""Check if the multiline Enter hint has been shown before (persisted)."""
|
@@ -68,8 +126,9 @@ def get_single_line_input(tip: str, default: str = "") -> str:
|
|
68
126
|
获取支持历史记录的单行输入。
|
69
127
|
"""
|
70
128
|
session: PromptSession = PromptSession(history=None)
|
71
|
-
style = PromptStyle.from_dict({"prompt": "ansicyan"})
|
72
|
-
|
129
|
+
style = PromptStyle.from_dict({"prompt": "ansicyan", "bottom-toolbar": "fg:#888888"})
|
130
|
+
prompt = FormattedText([("class:prompt", f"👤 > {tip}")])
|
131
|
+
return session.prompt(prompt, default=default, style=style)
|
73
132
|
|
74
133
|
|
75
134
|
def get_choice(tip: str, choices: List[str]) -> str:
|
@@ -289,14 +348,14 @@ def _show_history_and_copy():
|
|
289
348
|
PrettyOutput.print("没有可复制的消息", OutputType.INFO)
|
290
349
|
return
|
291
350
|
|
292
|
-
print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
|
351
|
+
PrettyOutput.print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20, OutputType.INFO)
|
293
352
|
for i, msg in enumerate(history):
|
294
353
|
cleaned_msg = msg.replace("\n", r"\n")
|
295
354
|
display_msg = (
|
296
355
|
(cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
|
297
356
|
)
|
298
|
-
print(f" {i + 1}: {display_msg.strip()}")
|
299
|
-
print("=" * 58 + "\n")
|
357
|
+
PrettyOutput.print(f" {i + 1}: {display_msg.strip()}", OutputType.INFO)
|
358
|
+
PrettyOutput.print("=" * 58 + "\n", OutputType.INFO)
|
300
359
|
|
301
360
|
while True:
|
302
361
|
try:
|
@@ -305,11 +364,11 @@ def _show_history_and_copy():
|
|
305
364
|
|
306
365
|
if not choice_str: # User pressed Enter
|
307
366
|
if not history:
|
308
|
-
print("没有历史记录可供选择。")
|
367
|
+
PrettyOutput.print("没有历史记录可供选择。", OutputType.INFO)
|
309
368
|
break
|
310
369
|
choice = len(history) - 1
|
311
370
|
elif choice_str.lower() == "c":
|
312
|
-
print("已取消")
|
371
|
+
PrettyOutput.print("已取消", OutputType.INFO)
|
313
372
|
break
|
314
373
|
else:
|
315
374
|
choice = int(choice_str) - 1
|
@@ -322,23 +381,23 @@ def _show_history_and_copy():
|
|
322
381
|
)
|
323
382
|
break
|
324
383
|
else:
|
325
|
-
print("无效的序号,请重试。")
|
384
|
+
PrettyOutput.print("无效的序号,请重试。", OutputType.WARNING)
|
326
385
|
except ValueError:
|
327
|
-
print("无效的输入,请输入数字。")
|
386
|
+
PrettyOutput.print("无效的输入,请输入数字。", OutputType.WARNING)
|
328
387
|
except (KeyboardInterrupt, EOFError):
|
329
|
-
print("\n操作取消")
|
388
|
+
PrettyOutput.print("\n操作取消", OutputType.INFO)
|
330
389
|
break
|
331
390
|
|
332
391
|
|
333
|
-
def _get_multiline_input_internal(tip: str) -> str:
|
392
|
+
def _get_multiline_input_internal(tip: str, preset: str | None = None, preset_cursor: int | None = None) -> str:
|
334
393
|
"""
|
335
394
|
Internal function to get multiline input using prompt_toolkit.
|
336
395
|
Returns a sentinel value if Ctrl+O is pressed.
|
337
396
|
"""
|
338
397
|
bindings = KeyBindings()
|
339
398
|
|
340
|
-
# Show a one-time hint on the first Enter press in this invocation
|
341
|
-
first_enter_hint_shown =
|
399
|
+
# Show a one-time hint on the first Enter press in this invocation (disabled; using inlay toolbar instead)
|
400
|
+
first_enter_hint_shown = True
|
342
401
|
|
343
402
|
@bindings.add("enter")
|
344
403
|
def _(event):
|
@@ -347,8 +406,9 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
347
406
|
first_enter_hint_shown = True
|
348
407
|
|
349
408
|
def _show_notice():
|
350
|
-
print(
|
351
|
-
|
409
|
+
PrettyOutput.print(
|
410
|
+
"提示:当前支持多行输入。输入完成请使用 Ctrl+J 确认;Enter 仅用于换行。",
|
411
|
+
OutputType.INFO,
|
352
412
|
)
|
353
413
|
try:
|
354
414
|
input("按回车继续...")
|
@@ -372,16 +432,112 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
372
432
|
else:
|
373
433
|
event.current_buffer.insert_text("\n")
|
374
434
|
|
375
|
-
@bindings.add("c-j")
|
435
|
+
@bindings.add("c-j", filter=has_focus(DEFAULT_BUFFER))
|
376
436
|
def _(event):
|
377
437
|
event.current_buffer.validate_and_handle()
|
378
438
|
|
379
|
-
@bindings.add("c-o")
|
439
|
+
@bindings.add("c-o", filter=has_focus(DEFAULT_BUFFER))
|
380
440
|
def _(event):
|
381
441
|
"""Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
|
382
442
|
event.app.exit(result=CTRL_O_SENTINEL)
|
383
443
|
|
384
|
-
|
444
|
+
@bindings.add("c-t", filter=has_focus(DEFAULT_BUFFER))
|
445
|
+
def _(event):
|
446
|
+
"""Return a shell command like '!bash' for upper input_handler to execute."""
|
447
|
+
def _gen_shell_cmd() -> str: # type: ignore
|
448
|
+
try:
|
449
|
+
import os
|
450
|
+
import shutil
|
451
|
+
|
452
|
+
if os.name == "nt":
|
453
|
+
# Prefer PowerShell if available, otherwise fallback to cmd
|
454
|
+
for name in ("pwsh", "powershell", "cmd"):
|
455
|
+
if name == "cmd" or shutil.which(name):
|
456
|
+
return f"!{name}"
|
457
|
+
else:
|
458
|
+
shell_path = os.environ.get("SHELL", "")
|
459
|
+
if shell_path:
|
460
|
+
base = os.path.basename(shell_path)
|
461
|
+
if base:
|
462
|
+
return f"!{base}"
|
463
|
+
for name in ("fish", "zsh", "bash", "sh"):
|
464
|
+
if shutil.which(name):
|
465
|
+
return f"!{name}"
|
466
|
+
return "!bash"
|
467
|
+
except Exception:
|
468
|
+
return "!bash"
|
469
|
+
|
470
|
+
# Append a special marker to indicate no-confirm execution in shell_input_handler
|
471
|
+
event.app.exit(result=_gen_shell_cmd() + " # JARVIS-NOCONFIRM")
|
472
|
+
|
473
|
+
|
474
|
+
@bindings.add("@", filter=has_focus(DEFAULT_BUFFER), eager=True)
|
475
|
+
def _(event):
|
476
|
+
"""
|
477
|
+
使用 @ 触发 fzf(当 fzf 存在);否则仅插入 @ 以启用内置补全
|
478
|
+
逻辑:
|
479
|
+
- 若检测到系统存在 fzf,则先插入 '@',随后请求外层运行 fzf 并在返回后进行替换/插入
|
480
|
+
- 若不存在 fzf 或发生异常,则直接插入 '@'
|
481
|
+
"""
|
482
|
+
try:
|
483
|
+
import shutil
|
484
|
+
buf = event.current_buffer
|
485
|
+
if shutil.which("fzf") is None:
|
486
|
+
buf.insert_text("@")
|
487
|
+
return
|
488
|
+
# 先插入 '@',以便外层根据最后一个 '@' 进行片段替换
|
489
|
+
buf.insert_text("@")
|
490
|
+
doc = buf.document
|
491
|
+
text = doc.text
|
492
|
+
cursor = doc.cursor_position
|
493
|
+
payload = f"{cursor}:{base64.b64encode(text.encode('utf-8')).decode('ascii')}"
|
494
|
+
event.app.exit(result=FZF_REQUEST_SENTINEL_PREFIX + payload)
|
495
|
+
return
|
496
|
+
except Exception:
|
497
|
+
try:
|
498
|
+
event.current_buffer.insert_text("@")
|
499
|
+
except Exception:
|
500
|
+
pass
|
501
|
+
|
502
|
+
style = PromptStyle.from_dict(
|
503
|
+
{
|
504
|
+
"prompt": "ansibrightmagenta bold",
|
505
|
+
"bottom-toolbar": "bg:#4b145b #ffd6ff bold",
|
506
|
+
"bt.tip": "bold fg:#ff5f87",
|
507
|
+
"bt.sep": "fg:#ffb3de",
|
508
|
+
"bt.key": "bg:#d7005f #ffffff bold",
|
509
|
+
"bt.label": "fg:#ffd6ff",
|
510
|
+
}
|
511
|
+
)
|
512
|
+
|
513
|
+
def _bottom_toolbar():
|
514
|
+
return FormattedText(
|
515
|
+
[
|
516
|
+
("class:bt.tip", f" {tip} "),
|
517
|
+
("class:bt.sep", " • "),
|
518
|
+
("class:bt.label", "快捷键: "),
|
519
|
+
("class:bt.key", "@"),
|
520
|
+
("class:bt.label", " 文件补全 "),
|
521
|
+
("class:bt.sep", " • "),
|
522
|
+
("class:bt.key", "Tab"),
|
523
|
+
("class:bt.label", " 选择 "),
|
524
|
+
("class:bt.sep", " • "),
|
525
|
+
("class:bt.key", "Ctrl+J"),
|
526
|
+
("class:bt.label", " 确认 "),
|
527
|
+
("class:bt.sep", " • "),
|
528
|
+
("class:bt.key", "Ctrl+O"),
|
529
|
+
("class:bt.label", " 历史复制 "),
|
530
|
+
("class:bt.sep", " • "),
|
531
|
+
("class:bt.key", "@"),
|
532
|
+
("class:bt.label", " FZF文件 "),
|
533
|
+
("class:bt.sep", " • "),
|
534
|
+
("class:bt.key", "Ctrl+T"),
|
535
|
+
("class:bt.label", " 终端(!SHELL) "),
|
536
|
+
("class:bt.sep", " • "),
|
537
|
+
("class:bt.key", "Ctrl+C/D"),
|
538
|
+
("class:bt.label", " 取消 "),
|
539
|
+
]
|
540
|
+
)
|
385
541
|
|
386
542
|
history_dir = get_data_dir()
|
387
543
|
session: PromptSession = PromptSession(
|
@@ -394,33 +550,163 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
394
550
|
mouse_support=False,
|
395
551
|
)
|
396
552
|
|
397
|
-
print
|
398
|
-
prompt = FormattedText([("class:prompt", "
|
553
|
+
# Tip is shown in bottom toolbar; avoid extra print
|
554
|
+
prompt = FormattedText([("class:prompt", "👤 > ")])
|
555
|
+
|
556
|
+
def _pre_run():
|
557
|
+
try:
|
558
|
+
from prompt_toolkit.application.current import get_app as _ga
|
559
|
+
app = _ga()
|
560
|
+
buf = app.current_buffer
|
561
|
+
if preset is not None and preset_cursor is not None:
|
562
|
+
cp = max(0, min(len(buf.text), preset_cursor))
|
563
|
+
buf.cursor_position = cp
|
564
|
+
except Exception:
|
565
|
+
pass
|
399
566
|
|
400
567
|
try:
|
401
|
-
return session.prompt(
|
568
|
+
return session.prompt(
|
569
|
+
prompt,
|
570
|
+
style=style,
|
571
|
+
pre_run=_pre_run,
|
572
|
+
bottom_toolbar=_bottom_toolbar,
|
573
|
+
default=(preset or ""),
|
574
|
+
).strip()
|
402
575
|
except (KeyboardInterrupt, EOFError):
|
403
576
|
return ""
|
404
577
|
|
405
578
|
|
406
|
-
def get_multiline_input(tip: str) -> str:
|
579
|
+
def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
|
407
580
|
"""
|
408
581
|
获取带有增强补全和确认功能的多行输入。
|
409
582
|
此函数处理控制流,允许在不破坏终端状态的情况下处理历史记录复制。
|
410
|
-
"""
|
411
|
-
PrettyOutput.section(
|
412
|
-
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 确认,Ctrl+O 从历史记录中选择消息复制,按 Ctrl+C/D 取消输入",
|
413
|
-
OutputType.USER,
|
414
|
-
)
|
415
583
|
|
584
|
+
参数:
|
585
|
+
tip: 提示文本,将显示在底部工具栏中
|
586
|
+
print_on_empty: 当输入为空字符串时,是否打印“输入已取消”提示。默认打印。
|
587
|
+
"""
|
588
|
+
preset: str | None = None
|
589
|
+
preset_cursor: int | None = None
|
416
590
|
while True:
|
417
|
-
user_input = _get_multiline_input_internal(tip)
|
591
|
+
user_input = _get_multiline_input_internal(tip, preset=preset, preset_cursor=preset_cursor)
|
418
592
|
|
419
593
|
if user_input == CTRL_O_SENTINEL:
|
420
594
|
_show_history_and_copy()
|
421
595
|
tip = "请继续输入(或按Ctrl+J确认):"
|
422
596
|
continue
|
597
|
+
elif isinstance(user_input, str) and user_input.startswith(FZF_REQUEST_SENTINEL_PREFIX):
|
598
|
+
# Handle fzf request outside the prompt, then prefill new text.
|
599
|
+
try:
|
600
|
+
payload = user_input[len(FZF_REQUEST_SENTINEL_PREFIX) :]
|
601
|
+
sep_index = payload.find(":")
|
602
|
+
cursor = int(payload[:sep_index])
|
603
|
+
text = base64.b64decode(payload[sep_index + 1 :].encode("ascii")).decode("utf-8")
|
604
|
+
except Exception:
|
605
|
+
# Malformed payload; just continue without change.
|
606
|
+
preset = None
|
607
|
+
tip = "FZF 预填失败,继续输入:"
|
608
|
+
continue
|
609
|
+
|
610
|
+
# Run fzf to get a file selection synchronously (outside prompt)
|
611
|
+
selected_path = ""
|
612
|
+
try:
|
613
|
+
import shutil
|
614
|
+
import subprocess
|
615
|
+
|
616
|
+
if shutil.which("fzf") is None:
|
617
|
+
PrettyOutput.print("未检测到 fzf,无法打开文件选择器。", OutputType.WARNING)
|
618
|
+
else:
|
619
|
+
files: list[str] = []
|
620
|
+
try:
|
621
|
+
r = subprocess.run(
|
622
|
+
["git", "ls-files"],
|
623
|
+
stdout=subprocess.PIPE,
|
624
|
+
stderr=subprocess.PIPE,
|
625
|
+
text=True,
|
626
|
+
)
|
627
|
+
if r.returncode == 0:
|
628
|
+
files = [line for line in r.stdout.splitlines() if line.strip()]
|
629
|
+
except Exception:
|
630
|
+
files = []
|
631
|
+
|
632
|
+
if not files:
|
633
|
+
import os as _os
|
634
|
+
for root, _, fnames in _os.walk(".", followlinks=False):
|
635
|
+
for name in fnames:
|
636
|
+
files.append(_os.path.relpath(_os.path.join(root, name), "."))
|
637
|
+
if len(files) > 10000:
|
638
|
+
break
|
639
|
+
|
640
|
+
if not files:
|
641
|
+
PrettyOutput.print("未找到可选择的文件。", OutputType.INFO)
|
642
|
+
else:
|
643
|
+
try:
|
644
|
+
specials = [ot("Summary"), ot("Clear"), ot("ToolUsage"), ot("ReloadConfig"), ot("SaveSession")]
|
645
|
+
except Exception:
|
646
|
+
specials = []
|
647
|
+
items = [s for s in specials if isinstance(s, str) and s.strip()] + files
|
648
|
+
proc = subprocess.run(
|
649
|
+
["fzf", "--prompt", "Files> ", "--height", "40%", "--border"],
|
650
|
+
input="\n".join(items),
|
651
|
+
stdout=subprocess.PIPE,
|
652
|
+
stderr=subprocess.PIPE,
|
653
|
+
text=True,
|
654
|
+
)
|
655
|
+
sel = proc.stdout.strip()
|
656
|
+
if sel:
|
657
|
+
selected_path = sel
|
658
|
+
except Exception as e:
|
659
|
+
PrettyOutput.print(f"FZF 执行失败: {e}", OutputType.ERROR)
|
660
|
+
|
661
|
+
# Compute new text based on selection (or keep original if none)
|
662
|
+
if selected_path:
|
663
|
+
text_before = text[:cursor]
|
664
|
+
last_at = text_before.rfind("@")
|
665
|
+
if last_at != -1 and " " not in text_before[last_at + 1 :]:
|
666
|
+
# Replace @... segment
|
667
|
+
inserted = f"'{selected_path}'"
|
668
|
+
new_text = text[:last_at] + inserted + text[cursor:]
|
669
|
+
new_cursor = last_at + len(inserted)
|
670
|
+
else:
|
671
|
+
# Plain insert
|
672
|
+
inserted = f"'{selected_path}'"
|
673
|
+
new_text = text[:cursor] + inserted + text[cursor:]
|
674
|
+
new_cursor = cursor + len(inserted)
|
675
|
+
preset = new_text
|
676
|
+
preset_cursor = new_cursor
|
677
|
+
tip = "已插入文件,继续编辑或按Ctrl+J确认:"
|
678
|
+
else:
|
679
|
+
# No selection; keep original text and cursor
|
680
|
+
preset = text
|
681
|
+
preset_cursor = cursor
|
682
|
+
tip = "未选择文件或已取消,继续编辑:"
|
683
|
+
# 清除上一条输入行(多行安全),避免多清,保守仅按提示行估算
|
684
|
+
try:
|
685
|
+
rows_total = _calc_prompt_rows(text)
|
686
|
+
for _ in range(rows_total):
|
687
|
+
sys.stdout.write("\x1b[1A") # 光标上移一行
|
688
|
+
sys.stdout.write("\x1b[2K\r") # 清除整行
|
689
|
+
sys.stdout.flush()
|
690
|
+
except Exception:
|
691
|
+
pass
|
692
|
+
continue
|
693
|
+
elif isinstance(user_input, str) and user_input.startswith(FZF_INSERT_SENTINEL_PREFIX):
|
694
|
+
# 从哨兵载荷中提取新文本,作为下次进入提示的预填内容
|
695
|
+
preset = user_input[len(FZF_INSERT_SENTINEL_PREFIX) :]
|
696
|
+
preset_cursor = len(preset)
|
697
|
+
|
698
|
+
# 清除上一条输入行(多行安全),避免多清,保守仅按提示行估算
|
699
|
+
try:
|
700
|
+
rows_total = _calc_prompt_rows(preset)
|
701
|
+
for _ in range(rows_total):
|
702
|
+
sys.stdout.write("\x1b[1A")
|
703
|
+
sys.stdout.write("\x1b[2K\r")
|
704
|
+
sys.stdout.flush()
|
705
|
+
except Exception:
|
706
|
+
pass
|
707
|
+
tip = "已插入文件,继续编辑或按Ctrl+J确认:"
|
708
|
+
continue
|
423
709
|
else:
|
424
|
-
if not user_input:
|
425
|
-
PrettyOutput.print("
|
710
|
+
if not user_input and print_on_empty:
|
711
|
+
PrettyOutput.print("输入已取消", OutputType.INFO)
|
426
712
|
return user_input
|
@@ -203,12 +203,12 @@ def load_methodology(
|
|
203
203
|
|
204
204
|
try:
|
205
205
|
# 加载所有方法论
|
206
|
-
print(
|
206
|
+
PrettyOutput.print("📁 加载方法论文件...", OutputType.INFO)
|
207
207
|
methodologies = _load_all_methodologies()
|
208
208
|
if not methodologies:
|
209
|
-
print(
|
209
|
+
PrettyOutput.print("没有找到方法论文件", OutputType.WARNING)
|
210
210
|
return ""
|
211
|
-
print(f"
|
211
|
+
PrettyOutput.print(f"加载方法论文件完成 (共 {len(methodologies)} 个)", OutputType.SUCCESS)
|
212
212
|
|
213
213
|
if platform_name:
|
214
214
|
platform = PlatformRegistry().create_platform(platform_name)
|