kash-shell 0.3.9__py3-none-any.whl → 0.3.11__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.
- kash/actions/__init__.py +4 -4
- kash/actions/core/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +7 -6
- kash/actions/core/readability.py +7 -6
- kash/actions/core/render_as_html.py +37 -0
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +2 -6
- kash/actions/core/tabbed_webpage_config.py +31 -0
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
- kash/commands/__init__.py +8 -20
- kash/commands/base/basic_file_commands.py +15 -0
- kash/commands/base/debug_commands.py +13 -0
- kash/commands/base/files_command.py +28 -10
- kash/commands/base/general_commands.py +21 -16
- kash/commands/base/logs_commands.py +4 -2
- kash/commands/base/model_commands.py +8 -8
- kash/commands/base/search_command.py +3 -2
- kash/commands/base/show_command.py +5 -3
- kash/commands/extras/parse_uv_lock.py +186 -0
- kash/commands/help/doc_commands.py +2 -31
- kash/commands/help/welcome.py +33 -0
- kash/commands/workspace/selection_commands.py +11 -6
- kash/commands/workspace/workspace_commands.py +19 -17
- kash/config/colors.py +3 -1
- kash/config/env_settings.py +14 -1
- kash/config/init.py +2 -2
- kash/config/logger.py +59 -56
- kash/config/logger_basic.py +3 -3
- kash/config/settings.py +116 -57
- kash/config/setup.py +28 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/{concepts → embeddings}/text_similarity.py +2 -2
- kash/exec/__init__.py +20 -3
- kash/exec/action_decorators.py +24 -10
- kash/exec/action_exec.py +41 -23
- kash/exec/action_registry.py +13 -48
- kash/exec/command_registry.py +2 -1
- kash/exec/fetch_url_metadata.py +4 -6
- kash/exec/importing.py +56 -0
- kash/exec/llm_transforms.py +12 -10
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +22 -1
- kash/exec/resolve_args.py +4 -0
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +42 -27
- kash/file_storage/item_file_format.py +5 -2
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +1 -1
- kash/help/assistant_instructions.py +2 -1
- kash/help/function_param_info.py +1 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +7 -11
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_features.py +68 -0
- kash/llm_utils/llm_messages.py +1 -2
- kash/llm_utils/llm_names.py +1 -1
- kash/llm_utils/llms.py +8 -3
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +8 -5
- kash/local_server/local_server_commands.py +2 -2
- kash/local_server/local_server_routes.py +1 -7
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +5 -5
- kash/mcp/mcp_server_commands.py +5 -5
- kash/mcp/mcp_server_routes.py +5 -5
- kash/mcp/mcp_server_sse.py +4 -2
- kash/media_base/media_cache.py +8 -8
- kash/media_base/media_services.py +1 -1
- kash/media_base/media_tools.py +6 -6
- kash/media_base/services/local_file_media.py +2 -2
- kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
- kash/media_base/transcription_format.py +73 -0
- kash/media_base/transcription_whisper.py +38 -0
- kash/model/__init__.py +73 -5
- kash/model/actions_model.py +38 -4
- kash/model/concept_model.py +30 -0
- kash/model/items_model.py +115 -32
- kash/model/params_model.py +24 -0
- kash/shell/completions/completion_scoring.py +37 -5
- kash/shell/output/kerm_codes.py +1 -2
- kash/shell/output/shell_formatting.py +14 -4
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +6 -0
- kash/shell/utils/native_utils.py +26 -20
- kash/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +118 -0
- kash/text_handling/markdown_utils.py +226 -0
- kash/utils/common/function_inspect.py +360 -110
- kash/utils/common/import_utils.py +12 -3
- kash/utils/common/type_utils.py +0 -29
- kash/utils/common/url.py +27 -3
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/file_ext.py +4 -0
- kash/utils/file_utils/file_formats.py +2 -2
- kash/utils/file_utils/file_formats_model.py +20 -1
- kash/web_content/dir_store.py +1 -2
- kash/web_content/file_cache_utils.py +37 -10
- kash/web_content/file_processing.py +68 -0
- kash/web_content/local_file_cache.py +12 -9
- kash/web_content/web_extract.py +8 -3
- kash/web_content/web_fetch.py +12 -4
- kash/web_gen/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +24 -14
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +169 -43
- kash/web_gen/templates/base_webpage.html.jinja +110 -45
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +49 -39
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -33
- kash/workspaces/__init__.py +15 -2
- kash/workspaces/selections.py +18 -3
- kash/workspaces/source_items.py +0 -1
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +43 -11
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +22 -25
- kash/xonsh_custom/shell_load_commands.py +2 -2
- kash/xonsh_custom/xonsh_completers.py +2 -249
- kash/xonsh_custom/xonsh_keybindings.py +282 -0
- kash/xonsh_custom/xonsh_modern_tools.py +3 -3
- kash/xontrib/kash_extension.py +5 -6
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/METADATA +10 -8
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/RECORD +137 -136
- kash/actions/core/webpage_config.py +0 -21
- kash/concepts/concept_formats.py +0 -23
- kash/shell/clideps/api_keys.py +0 -100
- kash/shell/clideps/dotenv_setup.py +0 -115
- kash/shell/clideps/dotenv_utils.py +0 -98
- kash/shell/clideps/pkg_deps.py +0 -257
- kash/shell/clideps/platforms.py +0 -11
- kash/shell/clideps/terminal_features.py +0 -56
- kash/shell/utils/osc_utils.py +0 -95
- kash/shell/utils/terminal_images.py +0 -133
- kash/text_handling/markdown_util.py +0 -167
- kash/utils/common/atomic_var.py +0 -171
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/{concepts → embeddings}/cosine.py +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from enum import Enum
|
|
3
4
|
from pathlib import Path
|
|
@@ -29,8 +30,10 @@ class PromptInfo:
|
|
|
29
30
|
workspace_details: str
|
|
30
31
|
is_global_ws: bool
|
|
31
32
|
cwd_str: str
|
|
33
|
+
cwd_short_str: str
|
|
32
34
|
cwd_details: str
|
|
33
35
|
cwd_in_workspace: bool
|
|
36
|
+
cwd_in_home: bool
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
def get_prompt_info() -> PromptInfo:
|
|
@@ -42,28 +45,39 @@ def get_prompt_info() -> PromptInfo:
|
|
|
42
45
|
workspace_details = f"Workspace at {ws.base_dir}"
|
|
43
46
|
|
|
44
47
|
cwd = Path(".").resolve()
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
cwd_in_home = cwd.is_relative_to(Path.home())
|
|
49
|
+
cwd_in_workspace = cwd.is_relative_to(ws.base_dir)
|
|
50
|
+
if cwd_in_workspace:
|
|
47
51
|
rel_cwd = cwd.relative_to(ws.base_dir)
|
|
48
52
|
if rel_cwd != Path("."):
|
|
49
53
|
cwd_str = str(rel_cwd)
|
|
50
54
|
else:
|
|
51
55
|
cwd_str = ""
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
cwd_short_str = cwd_str
|
|
57
|
+
elif cwd_in_home:
|
|
54
58
|
rel_to_home = cwd.relative_to(Path.home())
|
|
55
|
-
if
|
|
56
|
-
cwd_str = "
|
|
59
|
+
if cwd == Path.home():
|
|
60
|
+
cwd_str = cwd_short_str = "~"
|
|
61
|
+
elif cwd.parent == Path.home():
|
|
62
|
+
cwd_str = cwd_short_str = os.path.join("~", cwd.name)
|
|
57
63
|
else:
|
|
58
|
-
cwd_str = "
|
|
64
|
+
cwd_str = "~/" + str(rel_to_home)
|
|
65
|
+
# Show only last two levels of dirs when inside home.
|
|
66
|
+
cwd_short_str = os.path.join(cwd.parent.name, cwd.name)
|
|
59
67
|
else:
|
|
60
|
-
|
|
61
|
-
cwd_str = cwd.as_posix()
|
|
68
|
+
cwd_str = cwd_short_str = str(cwd)
|
|
62
69
|
|
|
63
70
|
cwd_details = f"Current directory at {cwd}"
|
|
64
71
|
|
|
65
72
|
return PromptInfo(
|
|
66
|
-
ws_name,
|
|
73
|
+
ws_name,
|
|
74
|
+
workspace_details,
|
|
75
|
+
is_global_ws,
|
|
76
|
+
cwd_str,
|
|
77
|
+
cwd_short_str,
|
|
78
|
+
cwd_details,
|
|
79
|
+
cwd_in_workspace,
|
|
80
|
+
cwd_in_home,
|
|
67
81
|
)
|
|
68
82
|
|
|
69
83
|
|
|
@@ -167,17 +181,21 @@ def kash_xonsh_prompt() -> FormattedText:
|
|
|
167
181
|
).as_ptk_tokens(style=workspace_color)
|
|
168
182
|
|
|
169
183
|
cwd_style = settings.ptk_style_bright if info.cwd_in_workspace else settings.ptk_style_normal
|
|
170
|
-
#
|
|
171
|
-
|
|
184
|
+
# Separator could be "\n" if you like a 2-line prompt.
|
|
185
|
+
# separator = "\n" + settings.prompt_prefix
|
|
186
|
+
separator = ""
|
|
187
|
+
# Using shorter cwd str.
|
|
188
|
+
cwd_str = info.cwd_short_str
|
|
189
|
+
# If outside the workspace, show both workspace and cwd.
|
|
190
|
+
if info.cwd_short_str and not info.cwd_in_workspace:
|
|
172
191
|
cwd_tokens = (
|
|
173
|
-
[(settings.ptk_style_dim,
|
|
174
|
-
+ text_with_tooltip(
|
|
175
|
-
style=cwd_style
|
|
176
|
-
)
|
|
192
|
+
[(settings.ptk_style_dim, separator)]
|
|
193
|
+
+ text_with_tooltip(cwd_str, hover_text=info.cwd_details).as_ptk_tokens(style=cwd_style)
|
|
177
194
|
+ [(settings.ptk_style_dim, " ")]
|
|
178
195
|
)
|
|
179
|
-
elif
|
|
180
|
-
|
|
196
|
+
elif cwd_str:
|
|
197
|
+
# If inside the workspace, show just cwd.
|
|
198
|
+
cwd_tokens = text_with_tooltip(cwd_str, hover_text=info.cwd_details).as_ptk_tokens(
|
|
181
199
|
style=cwd_style
|
|
182
200
|
)
|
|
183
201
|
else:
|
|
@@ -187,9 +205,9 @@ def kash_xonsh_prompt() -> FormattedText:
|
|
|
187
205
|
ptk_tokens = (
|
|
188
206
|
settings.hrule
|
|
189
207
|
+ [(settings.ptk_style_dim, settings.prompt_prefix)]
|
|
190
|
-
+ [
|
|
191
|
-
|
|
192
|
-
]
|
|
208
|
+
# + [
|
|
209
|
+
# (settings.ptk_style_dim, "ws: "),
|
|
210
|
+
# ]
|
|
193
211
|
+ workspace_tokens
|
|
194
212
|
+ [
|
|
195
213
|
(settings.ptk_style_dim, " "),
|
|
@@ -1,52 +1,49 @@
|
|
|
1
|
-
from kash.config.setup import
|
|
2
|
-
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
3
|
-
from kash.mcp.mcp_server_commands import start_mcp_server
|
|
4
|
-
from kash.xonsh_custom.shell_load_commands import (
|
|
5
|
-
is_interactive,
|
|
6
|
-
log_command_action_info,
|
|
7
|
-
reload_shell_commands_and_actions,
|
|
8
|
-
set_env,
|
|
9
|
-
)
|
|
1
|
+
from kash.config.setup import kash_setup
|
|
10
2
|
|
|
11
|
-
|
|
3
|
+
kash_setup(rich_logging=True) # Set up logging first.
|
|
12
4
|
|
|
13
5
|
import time
|
|
14
6
|
|
|
7
|
+
from clideps.pkgs.pkg_check import pkg_check
|
|
15
8
|
from xonsh.built_ins import XSH
|
|
16
9
|
from xonsh.prompt.base import PromptFields
|
|
17
10
|
|
|
18
11
|
from kash.commands.base.general_commands import self_check
|
|
19
|
-
from kash.commands.help import
|
|
12
|
+
from kash.commands.help.welcome import welcome
|
|
20
13
|
from kash.config.logger import get_logger
|
|
21
|
-
from kash.config.settings import check_kerm_code_support
|
|
14
|
+
from kash.config.settings import RECOMMENDED_PKGS, check_kerm_code_support
|
|
15
|
+
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
22
16
|
from kash.local_server.local_server import start_ui_server
|
|
23
17
|
from kash.local_server.local_url_formatters import enable_local_urls
|
|
24
|
-
from kash.
|
|
18
|
+
from kash.mcp.mcp_server_commands import start_mcp_server
|
|
25
19
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
26
20
|
from kash.shell.version import get_version_tag
|
|
27
21
|
from kash.workspaces import current_ws
|
|
28
22
|
from kash.xonsh_custom.customize_prompt import get_prompt_info, kash_xonsh_prompt
|
|
23
|
+
from kash.xonsh_custom.shell_load_commands import (
|
|
24
|
+
is_interactive,
|
|
25
|
+
log_command_action_info,
|
|
26
|
+
reload_shell_commands_and_actions,
|
|
27
|
+
set_env,
|
|
28
|
+
)
|
|
29
29
|
from kash.xonsh_custom.xonsh_completers import load_completers
|
|
30
|
+
from kash.xonsh_custom.xonsh_keybindings import add_key_bindings
|
|
30
31
|
from kash.xonsh_custom.xonsh_modern_tools import modernize_shell
|
|
31
32
|
|
|
32
33
|
log = get_logger(__name__)
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def _kash_workspace_str() -> str:
|
|
36
|
-
return get_prompt_info().workspace_name
|
|
37
|
-
|
|
38
|
-
|
|
39
36
|
def _shell_interactive_setup():
|
|
40
|
-
from kash.xonsh_custom.xonsh_completers import add_key_bindings
|
|
41
|
-
|
|
42
37
|
# Set up a prompt field for the workspace string.
|
|
43
38
|
fields = PromptFields(XSH)
|
|
44
|
-
|
|
39
|
+
prompt_info = get_prompt_info()
|
|
40
|
+
fields["workspace_str"] = prompt_info.workspace_name
|
|
41
|
+
fields["cwd_short_str"] = prompt_info.cwd_short_str
|
|
45
42
|
set_env("PROMPT_FIELDS", fields)
|
|
46
43
|
|
|
47
44
|
# Set up the prompt and title template.
|
|
48
45
|
set_env("PROMPT", kash_xonsh_prompt)
|
|
49
|
-
set_env("TITLE", LOGO_NAME + " - {workspace_str}")
|
|
46
|
+
set_env("TITLE", LOGO_NAME + " - {workspace_str} - {cwd_short_str}")
|
|
50
47
|
|
|
51
48
|
add_key_bindings()
|
|
52
49
|
|
|
@@ -59,12 +56,12 @@ def load_into_xonsh():
|
|
|
59
56
|
"""
|
|
60
57
|
|
|
61
58
|
if is_interactive():
|
|
59
|
+
# Do welcome first since init could take a few seconds.
|
|
60
|
+
welcome()
|
|
61
|
+
|
|
62
62
|
# Do first so in case there is an error, the shell prompt etc works as expected.
|
|
63
63
|
_shell_interactive_setup()
|
|
64
64
|
|
|
65
|
-
# Then do welcome first since init could take a few seconds.
|
|
66
|
-
doc_commands.welcome()
|
|
67
|
-
|
|
68
65
|
def load():
|
|
69
66
|
load_start_time = time.time()
|
|
70
67
|
|
|
@@ -106,7 +103,7 @@ def load_into_xonsh():
|
|
|
106
103
|
|
|
107
104
|
current_ws() # Validates and logs info for user.
|
|
108
105
|
|
|
109
|
-
pkg_check().warn_if_missing()
|
|
106
|
+
pkg_check().warn_if_missing(*RECOMMENDED_PKGS)
|
|
110
107
|
|
|
111
108
|
else:
|
|
112
109
|
reload_shell_commands_and_actions()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from kash.actions import get_loaded_kits
|
|
2
|
-
from kash.config.setup import
|
|
2
|
+
from kash.config.setup import kash_setup
|
|
3
3
|
from kash.config.text_styles import COLOR_VALUE, STYLE_HINT
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
kash_setup(rich_logging=True) # Set up logging first.
|
|
6
6
|
|
|
7
7
|
from collections.abc import Callable
|
|
8
8
|
from typing import TYPE_CHECKING, TypeVar
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from typing import Any, cast
|
|
4
3
|
|
|
5
4
|
from funlog import log_calls
|
|
6
|
-
from
|
|
7
|
-
from prompt_toolkit.application import get_app
|
|
8
|
-
from prompt_toolkit.filters import Condition
|
|
9
|
-
from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
|
|
10
|
-
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
|
11
|
-
from prompt_toolkit.shortcuts import print_formatted_text
|
|
5
|
+
from strif import AtomicVar
|
|
12
6
|
from xonsh.built_ins import XSH
|
|
13
7
|
from xonsh.completers.completer import add_one_completer
|
|
14
8
|
from xonsh.completers.tools import (
|
|
@@ -19,7 +13,6 @@ from xonsh.completers.tools import (
|
|
|
19
13
|
)
|
|
20
14
|
from xonsh.parsers.completion_context import CommandContext
|
|
21
15
|
|
|
22
|
-
from kash.actions.core.assistant_chat import assistant_chat
|
|
23
16
|
from kash.config.logger import get_logger
|
|
24
17
|
from kash.exec.action_registry import get_all_actions_defaults
|
|
25
18
|
from kash.exec.command_registry import get_all_commands
|
|
@@ -34,8 +27,6 @@ from kash.shell.completions.shell_completions import (
|
|
|
34
27
|
get_std_command_completions,
|
|
35
28
|
trace_completions,
|
|
36
29
|
)
|
|
37
|
-
from kash.shell.ui.shell_syntax import assist_request_str
|
|
38
|
-
from kash.utils.common.atomic_var import AtomicVar
|
|
39
30
|
from kash.utils.errors import ApiResultError, InvalidState
|
|
40
31
|
from kash.xonsh_custom.command_nl_utils import as_nl_words, looks_like_nl
|
|
41
32
|
from kash.xonsh_custom.shell_which import is_valid_command
|
|
@@ -351,7 +342,7 @@ def _params_for_command(command_name: str) -> list[Param[Any]] | None:
|
|
|
351
342
|
if command:
|
|
352
343
|
return annotate_param_info(command)
|
|
353
344
|
elif action:
|
|
354
|
-
return
|
|
345
|
+
return action.shell_params
|
|
355
346
|
else:
|
|
356
347
|
return None
|
|
357
348
|
|
|
@@ -482,244 +473,6 @@ def options_enum_completer(context: CompletionContext) -> CompleterResult:
|
|
|
482
473
|
# - `source_code`
|
|
483
474
|
|
|
484
475
|
|
|
485
|
-
@Condition
|
|
486
|
-
def is_unquoted_assist_request():
|
|
487
|
-
app = get_app()
|
|
488
|
-
buf = app.current_buffer
|
|
489
|
-
text = buf.text.strip()
|
|
490
|
-
is_default_buffer = buf.name == "DEFAULT_BUFFER"
|
|
491
|
-
has_prefix = text.startswith("?") and not (text.startswith('? "') or text.startswith("? '"))
|
|
492
|
-
return is_default_buffer and has_prefix
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
_command_regex = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
496
|
-
|
|
497
|
-
_python_keyword_regex = re.compile(
|
|
498
|
-
r"assert|async|await|break|class|continue|def|del|elif|else|except|finally|"
|
|
499
|
-
r"for|from|global|if|import|lambda|nonlocal|pass|raise|return|try|while|with|yield"
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
def _extract_command_name(text: str) -> str | None:
|
|
504
|
-
text = text.split()[0]
|
|
505
|
-
if _python_keyword_regex.match(text):
|
|
506
|
-
return None
|
|
507
|
-
if _command_regex.match(text):
|
|
508
|
-
return text
|
|
509
|
-
return None
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
@Condition
|
|
513
|
-
def whitespace_only() -> bool:
|
|
514
|
-
app = get_app()
|
|
515
|
-
buf = app.current_buffer
|
|
516
|
-
return not buf.text.strip()
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
@Condition
|
|
520
|
-
def is_typo_command() -> bool:
|
|
521
|
-
"""
|
|
522
|
-
Is the command itself invalid? Should be conservative, so we can suppress
|
|
523
|
-
executing it if it is definitely a typo.
|
|
524
|
-
"""
|
|
525
|
-
|
|
526
|
-
app = get_app()
|
|
527
|
-
buf = app.current_buffer
|
|
528
|
-
text = buf.text.strip()
|
|
529
|
-
|
|
530
|
-
is_default_buffer = buf.name == "DEFAULT_BUFFER"
|
|
531
|
-
if not is_default_buffer:
|
|
532
|
-
return False
|
|
533
|
-
|
|
534
|
-
# Assistant NL requests always allowed.
|
|
535
|
-
has_assistant_prefix = text.startswith("?") or text.rstrip().endswith("?")
|
|
536
|
-
if has_assistant_prefix:
|
|
537
|
-
return False
|
|
538
|
-
|
|
539
|
-
# Anything more complex is probably Python.
|
|
540
|
-
# TODO: Do a better syntax parse of this as Python, or use xonsh's algorithm.
|
|
541
|
-
for s in ["\n", "(", ")"]:
|
|
542
|
-
if s in text:
|
|
543
|
-
return False
|
|
544
|
-
|
|
545
|
-
# Empty command line allowed.
|
|
546
|
-
if not text:
|
|
547
|
-
return False
|
|
548
|
-
|
|
549
|
-
# Now look at the command.
|
|
550
|
-
command_name = _extract_command_name(text)
|
|
551
|
-
|
|
552
|
-
# Python or missing command is fine.
|
|
553
|
-
if not command_name:
|
|
554
|
-
return False
|
|
555
|
-
|
|
556
|
-
# Recognized command.
|
|
557
|
-
if is_valid_command(command_name):
|
|
558
|
-
return False
|
|
559
|
-
|
|
560
|
-
# Okay it's almost certainly a command typo.
|
|
561
|
-
return True
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
@Condition
|
|
565
|
-
def is_completion_menu_active() -> bool:
|
|
566
|
-
app = get_app()
|
|
567
|
-
return app.current_buffer.complete_state is not None
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
@Condition
|
|
571
|
-
def could_show_more_tab_completions() -> bool:
|
|
572
|
-
return _MULTI_TAB_STATE.value.could_show_more()
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
# Set up prompt_toolkit key bindings.
|
|
576
|
-
def add_key_bindings() -> None:
|
|
577
|
-
custom_bindings = KeyBindings()
|
|
578
|
-
|
|
579
|
-
# Need to be careful only to bind with a filter if the state is suitable.
|
|
580
|
-
# Only add more completions if we've seen some results but user hasn't pressed
|
|
581
|
-
# tab a second time yet. Otherwise the behavior should fall back to usual ptk
|
|
582
|
-
# tab behavior (selecting each completion one by one).
|
|
583
|
-
@custom_bindings.add("tab", filter=could_show_more_tab_completions)
|
|
584
|
-
def _(event: KeyPressEvent):
|
|
585
|
-
"""
|
|
586
|
-
Add a second tab to show more completions.
|
|
587
|
-
"""
|
|
588
|
-
with _MULTI_TAB_STATE.updates() as state:
|
|
589
|
-
state.more_results_requested = True
|
|
590
|
-
|
|
591
|
-
trace_completions("More completion results requested", state)
|
|
592
|
-
|
|
593
|
-
# Restart completions.
|
|
594
|
-
buf = event.app.current_buffer
|
|
595
|
-
buf.complete_state = None
|
|
596
|
-
buf.start_completion()
|
|
597
|
-
|
|
598
|
-
@custom_bindings.add("s-tab")
|
|
599
|
-
def _(event: KeyPressEvent):
|
|
600
|
-
with _MULTI_TAB_STATE.updates() as state:
|
|
601
|
-
state.more_results_requested = True
|
|
602
|
-
|
|
603
|
-
trace_completions("More completion results requested", state)
|
|
604
|
-
|
|
605
|
-
# Restart completions.
|
|
606
|
-
buf = event.app.current_buffer
|
|
607
|
-
buf.complete_state = None
|
|
608
|
-
buf.start_completion()
|
|
609
|
-
|
|
610
|
-
@custom_bindings.add(" ", filter=whitespace_only)
|
|
611
|
-
def _(event: KeyPressEvent):
|
|
612
|
-
"""
|
|
613
|
-
Map space at the start of the line to `? ` to invoke an assistant question.
|
|
614
|
-
"""
|
|
615
|
-
buf = event.app.current_buffer
|
|
616
|
-
if buf.text == " " or buf.text == "":
|
|
617
|
-
buf.delete_before_cursor(len(buf.text))
|
|
618
|
-
buf.insert_text("? ")
|
|
619
|
-
else:
|
|
620
|
-
buf.insert_text(" ")
|
|
621
|
-
|
|
622
|
-
@custom_bindings.add(" ", filter=is_typo_command)
|
|
623
|
-
def _(event: KeyPressEvent):
|
|
624
|
-
"""
|
|
625
|
-
If the user types two words and the first word is likely an invalid
|
|
626
|
-
command, jump back to prefix the whole line with `? ` to make it clear we're
|
|
627
|
-
in natural language mode.
|
|
628
|
-
"""
|
|
629
|
-
|
|
630
|
-
buf = event.app.current_buffer
|
|
631
|
-
text = buf.text.strip()
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
buf.cursor_position == len(buf.text)
|
|
635
|
-
and len(text.split()) >= 2
|
|
636
|
-
and not text.startswith("?")
|
|
637
|
-
):
|
|
638
|
-
buf.transform_current_line(lambda line: "? " + line)
|
|
639
|
-
buf.cursor_position += 2
|
|
640
|
-
|
|
641
|
-
buf.insert_text(" ")
|
|
642
|
-
|
|
643
|
-
@custom_bindings.add("enter", filter=whitespace_only)
|
|
644
|
-
def _(_event: KeyPressEvent):
|
|
645
|
-
"""
|
|
646
|
-
Suppress enter if the command line is empty, but add a newline above the prompt.
|
|
647
|
-
"""
|
|
648
|
-
print_formatted_text("")
|
|
649
|
-
|
|
650
|
-
@custom_bindings.add("enter", filter=is_unquoted_assist_request)
|
|
651
|
-
def _(event: KeyPressEvent):
|
|
652
|
-
"""
|
|
653
|
-
Automatically add quotes around assistant questions, so there are not
|
|
654
|
-
syntax errors if the command line contains unclosed quotes etc.
|
|
655
|
-
"""
|
|
656
|
-
|
|
657
|
-
buf = event.app.current_buffer
|
|
658
|
-
text = buf.text.strip()
|
|
659
|
-
|
|
660
|
-
question_text = text[1:].strip()
|
|
661
|
-
if not question_text:
|
|
662
|
-
# If the user enters an empty assistant request, treat it as a shortcut to go to the assistant chat.
|
|
663
|
-
buf.delete_before_cursor(len(buf.text))
|
|
664
|
-
buf.insert_text(assistant_chat.__name__)
|
|
665
|
-
else:
|
|
666
|
-
# Convert it to an assistant question starting with a `?`.
|
|
667
|
-
buf.delete_before_cursor(len(buf.text))
|
|
668
|
-
buf.insert_text(assist_request_str(question_text))
|
|
669
|
-
|
|
670
|
-
buf.validate_and_handle()
|
|
671
|
-
|
|
672
|
-
@custom_bindings.add("enter", filter=is_typo_command)
|
|
673
|
-
def _(event: KeyPressEvent):
|
|
674
|
-
"""
|
|
675
|
-
Suppress enter and if possible give completions if the command is just not a valid command.
|
|
676
|
-
"""
|
|
677
|
-
|
|
678
|
-
buf = event.app.current_buffer
|
|
679
|
-
buf.start_completion()
|
|
680
|
-
|
|
681
|
-
# TODO: Also suppress enter if a command or action doesn't meet the required args,
|
|
682
|
-
# selection, or preconditions.
|
|
683
|
-
# Perhaps also have a way to get confirmation if its a rarely used or unexpected command
|
|
684
|
-
# (based on history/suggestions).
|
|
685
|
-
# TODO: Add suggested replacements, e.g. df -> duf, top -> btm, etc.
|
|
686
|
-
|
|
687
|
-
@custom_bindings.add("@")
|
|
688
|
-
def _(event: KeyPressEvent):
|
|
689
|
-
"""
|
|
690
|
-
Auto-trigger item completions after `@` sign.
|
|
691
|
-
"""
|
|
692
|
-
buf = event.app.current_buffer
|
|
693
|
-
buf.insert_text("@")
|
|
694
|
-
buf.start_completion()
|
|
695
|
-
|
|
696
|
-
@custom_bindings.add("escape", eager=True, filter=is_completion_menu_active)
|
|
697
|
-
def _(event: KeyPressEvent):
|
|
698
|
-
"""
|
|
699
|
-
Close the completion menu when escape is pressed.
|
|
700
|
-
"""
|
|
701
|
-
event.app.current_buffer.cancel_completion()
|
|
702
|
-
|
|
703
|
-
@custom_bindings.add("c-c", eager=True)
|
|
704
|
-
def _(event):
|
|
705
|
-
"""
|
|
706
|
-
Control-C to reset the current buffer. Similar to usual behavior but doesn't
|
|
707
|
-
leave ugly prompt chars.
|
|
708
|
-
"""
|
|
709
|
-
print_formatted_text("")
|
|
710
|
-
buf = event.app.current_buffer
|
|
711
|
-
# Abort reverse search/filtering, clear any selection, and reset the buffer.
|
|
712
|
-
search.stop_search()
|
|
713
|
-
buf.exit_selection()
|
|
714
|
-
buf.reset()
|
|
715
|
-
|
|
716
|
-
existing_bindings = __xonsh__.shell.shell.prompter.app.key_bindings # noqa: F821 # pyright: ignore[reportUndefinedVariable]
|
|
717
|
-
merged_bindings = merge_key_bindings([existing_bindings, custom_bindings])
|
|
718
|
-
__xonsh__.shell.shell.prompter.app.key_bindings = merged_bindings # noqa: F821 # pyright: ignore[reportUndefinedVariable]
|
|
719
|
-
|
|
720
|
-
log.info("Added custom %s key bindings.", len(merged_bindings.bindings))
|
|
721
|
-
|
|
722
|
-
|
|
723
476
|
def load_completers():
|
|
724
477
|
add_one_completer("command_completer", command_completer, "start")
|
|
725
478
|
add_one_completer("recommended_shell_completer", recommended_shell_completer, "start")
|