siada-cli 0.0.1__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.
- agent_config.yaml +34 -0
- siada/__init__.py +13 -0
- siada/agent_hub/__init__.py +10 -0
- siada/agent_hub/coder/bug_fix_agent.py +398 -0
- siada/agent_hub/coder/code_gen_agent.py +235 -0
- siada/agent_hub/coder/fe_gen_agent.py +37 -0
- siada/agent_hub/coder/issue_review_agent.py +68 -0
- siada/agent_hub/coder/prompt/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
- siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
- siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
- siada/agent_hub/coder/prompt/base/rules.py +40 -0
- siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
- siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
- siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
- siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
- siada/agent_hub/coder/tracing/__init__.py +22 -0
- siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
- siada/agent_hub/hooks/__init__.py +0 -0
- siada/agent_hub/hooks/io_hooks.py +30 -0
- siada/agent_hub/siada_agent.py +214 -0
- siada/config/__init__.py +0 -0
- siada/config/config_loader.py +67 -0
- siada/entrypoint/__init__.py +3 -0
- siada/entrypoint/args_parser/__init__.py +0 -0
- siada/entrypoint/args_parser/args.py +195 -0
- siada/entrypoint/interaction/__init__.py +0 -0
- siada/entrypoint/interaction/base_turn.py +107 -0
- siada/entrypoint/interaction/config.py +30 -0
- siada/entrypoint/interaction/controller.py +122 -0
- siada/entrypoint/interaction/nointeractive_controller.py +27 -0
- siada/entrypoint/interaction/turn/interface.py +116 -0
- siada/entrypoint/interaction/turn/models.py +34 -0
- siada/entrypoint/interaction/turn/run_turn.py +641 -0
- siada/entrypoint/interaction/turn_factory.py +35 -0
- siada/entrypoint/interaction/turn_models.py +28 -0
- siada/entrypoint/siadahub.py +413 -0
- siada/foundation/__init__.py +0 -0
- siada/foundation/code_agent_context.py +48 -0
- siada/foundation/config.py +59 -0
- siada/foundation/constants.py +2 -0
- siada/foundation/context.py +76 -0
- siada/foundation/logging.py +341 -0
- siada/foundation/tools/__init__.py +0 -0
- siada/foundation/tools/get_git_diff.py +105 -0
- siada/io/__init__.py +0 -0
- siada/io/banner.py +148 -0
- siada/io/color_settings.py +207 -0
- siada/io/color_utils.py +16 -0
- siada/io/components/__init__.py +0 -0
- siada/io/components/mdstream.py +204 -0
- siada/io/console_printer.py +68 -0
- siada/io/io.py +661 -0
- siada/io/key_bindings.py +129 -0
- siada/io/notification_command.py +40 -0
- siada/io/test_banner.py +64 -0
- siada/models/__init__.py +0 -0
- siada/models/agent.py +47 -0
- siada/models/coder/__init__.py +0 -0
- siada/models/converter.py +27 -0
- siada/models/model_base_config.py +121 -0
- siada/models/model_run_config.py +153 -0
- siada/provider/__init__.py +0 -0
- siada/provider/client_factory.py +112 -0
- siada/provider/llm_client.py +11 -0
- siada/provider/openrouter/__init__.py +0 -0
- siada/provider/openrouter/coverter.py +17 -0
- siada/provider/openrouter/openroute_provider.py +33 -0
- siada/provider/provider_factory.py +53 -0
- siada/queries/tree-sitter-language-pack/README.md +7 -0
- siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- siada/queries/tree-sitter-languages/README.md +23 -0
- siada/queries/tree-sitter-languages/c-tags.scm +9 -0
- siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
- siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
- siada/queries/tree-sitter-languages/go-tags.scm +30 -0
- siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- siada/queries/tree-sitter-languages/java-tags.scm +20 -0
- siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-languages/php-tags.scm +26 -0
- siada/queries/tree-sitter-languages/python-tags.scm +12 -0
- siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
- siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
- siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
- siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- siada/services/__init__.py +30 -0
- siada/services/at_file_recommendation_design_document.md +1213 -0
- siada/services/code_context_manager.py +136 -0
- siada/services/enhanced_fix_result_check.py +546 -0
- siada/services/execution_trace_collector.py +207 -0
- siada/services/file_recommendation/__init__.py +136 -0
- siada/services/file_recommendation/core/__init__.py +17 -0
- siada/services/file_recommendation/core/completion_engine.py +351 -0
- siada/services/file_recommendation/core/config.py +62 -0
- siada/services/file_recommendation/core/file_discovery.py +353 -0
- siada/services/file_recommendation/core/suggestion.py +110 -0
- siada/services/file_recommendation/utils/__init__.py +22 -0
- siada/services/file_recommendation/utils/path_utils.py +138 -0
- siada/services/file_recommendation/utils/text_utils.py +253 -0
- siada/services/fix_result_check.py +377 -0
- siada/services/handle_at_command/README.md +247 -0
- siada/services/handle_at_command/__init__.py +115 -0
- siada/services/handle_at_command/exceptions.py +46 -0
- siada/services/handle_at_command/models.py +92 -0
- siada/services/handle_at_command/parser.py +189 -0
- siada/services/handle_at_command/processor.py +321 -0
- siada/services/handle_at_command/resolver.py +318 -0
- siada/services/handle_at_command/utils.py +345 -0
- siada/services/model_info_service.py +103 -0
- siada/services/siada_runner.py +170 -0
- siada/session/__init__.py +29 -0
- siada/session/session_manager.py +66 -0
- siada/session/session_models.py +43 -0
- siada/support/__init__.py +0 -0
- siada/support/completer.py +204 -0
- siada/support/editor.py +146 -0
- siada/support/envprocessor.py +54 -0
- siada/support/repo.py +51 -0
- siada/support/slash_commands.py +410 -0
- siada/support/spinner.py +320 -0
- siada/tools/__init__.py +0 -0
- siada/tools/ast/README.md +345 -0
- siada/tools/ast/ast_tool.py +341 -0
- siada/tools/ast/models.py +11 -0
- siada/tools/coder/__init__.py +0 -0
- siada/tools/coder/ask_followup_question.py +70 -0
- siada/tools/coder/cmd_runner.py +132 -0
- siada/tools/coder/file_operator.py +284 -0
- siada/tools/coder/file_search/README.md +223 -0
- siada/tools/coder/file_search/__init__.py +10 -0
- siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
- siada/tools/coder/file_search/bin/rg.exe +0 -0
- siada/tools/coder/file_search/search.py +437 -0
- siada/tools/coder/file_search/setup_package.py +150 -0
- siada/tools/coder/file_search/test_search.py +142 -0
- siada/tools/coder/files.py +147 -0
- siada/tools/coder/fix_attempt_completion.py +58 -0
- siada/tools/coder/issue_review_completion.py +29 -0
- siada/tools/coder/observation/__init__.py +0 -0
- siada/tools/coder/observation/error.py +22 -0
- siada/tools/coder/observation/file_observation.py +216 -0
- siada/tools/coder/observation/observation.py +71 -0
- siada/tools/coder/repo_map/__init__.py +157 -0
- siada/tools/coder/repo_map/dump.py +29 -0
- siada/tools/coder/repo_map/io.py +226 -0
- siada/tools/coder/repo_map/repo_map.py +881 -0
- siada/tools/coder/repo_map/special.py +203 -0
- siada/tools/coder/repo_map/token_counter.py +154 -0
- siada/tools/coder/repo_map/waiting.py +221 -0
- siada/tools/coder/reproduce_completion.py +39 -0
- siada/tools/coder/run_cmd.py +60 -0
- siada/tools/coder/test_completion.py +25 -0
- siada/tools/coder/tool_docs.py +44 -0
- siada/tools/compression_tool.py +200 -0
- siada/tools/read_many_files/README.md +220 -0
- siada/tools/read_many_files/__init__.py +25 -0
- siada/tools/read_many_files/file_processor.py +486 -0
- siada/tools/read_many_files/filters.py +326 -0
- siada/tools/read_many_files/formatters.py +204 -0
- siada/tools/read_many_files/models.py +189 -0
- siada/tools/read_many_files_tool.py +233 -0
- siada/tools/tool_call_format/__init__.py +55 -0
- siada/tools/tool_call_format/file_to_language.py +231 -0
- siada/tools/tool_call_format/formatter_factory.py +80 -0
- siada/tools/tool_call_format/formatters.py +282 -0
- siada/tools/tool_call_format/tool_call_formatter.py +54 -0
- siada/tools/web/__init__.py +0 -0
- siada/tools/web/web_crawl.py +208 -0
- siada/utils.py +194 -0
- siada_cli-0.0.1.dist-info/LICENSE +201 -0
- siada_cli-0.0.1.dist-info/METADATA +457 -0
- siada_cli-0.0.1.dist-info/RECORD +215 -0
- siada_cli-0.0.1.dist-info/WHEEL +4 -0
- siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from agents import SQLiteSession
|
|
6
|
+
|
|
7
|
+
from siada.entrypoint.interaction.config import RunningConfig
|
|
8
|
+
from siada.io.io import InputOutput
|
|
9
|
+
from siada.models.model_run_config import ModelRunConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SessionState:
|
|
14
|
+
"""
|
|
15
|
+
Interaction session state data model
|
|
16
|
+
|
|
17
|
+
Stores state information during user interactions, complementing OpenAI Agents' SQLiteSession:
|
|
18
|
+
- SQLiteSession: Stores large language model conversation history
|
|
19
|
+
- SessionState: Stores interaction state and context information
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Core state fields
|
|
23
|
+
context_vars: Dict[str, Any] = field(default_factory=dict)
|
|
24
|
+
"""Context variables, works with foundation.context module"""
|
|
25
|
+
|
|
26
|
+
# Agent-related state
|
|
27
|
+
current_agent: Optional[str] = None
|
|
28
|
+
"""Currently active Agent name"""
|
|
29
|
+
|
|
30
|
+
openai_session: Optional[SQLiteSession] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class RunningSession:
|
|
35
|
+
|
|
36
|
+
siada_config: RunningConfig
|
|
37
|
+
|
|
38
|
+
session_id: str = field(default_factory=lambda: str(uuid4()))
|
|
39
|
+
|
|
40
|
+
state: SessionState = field(default_factory=SessionState)
|
|
41
|
+
|
|
42
|
+
def get_input(self) -> str:
|
|
43
|
+
return self.siada_config.io.get_input()
|
|
File without changes
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from siada.services.file_recommendation import FileRecommendationEngine, CompletionConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CommandCompletionException(Exception):
|
|
8
|
+
"""Raised when a command should use the normal autocompleter instead of
|
|
9
|
+
command-specific completion."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AutoCompleter(Completer):
|
|
15
|
+
def __init__(
|
|
16
|
+
self, root, commands, encoding
|
|
17
|
+
):
|
|
18
|
+
self.encoding = encoding
|
|
19
|
+
self.root = root
|
|
20
|
+
|
|
21
|
+
self.words = set()
|
|
22
|
+
|
|
23
|
+
self.commands = commands
|
|
24
|
+
self.command_completions = dict()
|
|
25
|
+
if commands:
|
|
26
|
+
self.command_names = self.commands.get_commands()
|
|
27
|
+
|
|
28
|
+
# Initialize file recommendation engine
|
|
29
|
+
current_dir = root if root else os.getcwd()
|
|
30
|
+
config = CompletionConfig(
|
|
31
|
+
max_results=20,
|
|
32
|
+
enable_recursive_search=True,
|
|
33
|
+
max_search_depth=5,
|
|
34
|
+
respect_git_ignore=True
|
|
35
|
+
)
|
|
36
|
+
self.file_recommendation_engine = FileRecommendationEngine(
|
|
37
|
+
current_directory=current_dir,
|
|
38
|
+
config=config
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Add completion state management
|
|
42
|
+
self.completion_state = {
|
|
43
|
+
'just_completed': False,
|
|
44
|
+
'last_completion_pos': 0,
|
|
45
|
+
'last_at_command': None,
|
|
46
|
+
'last_completion_text': None
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def get_command_completions(self, document, complete_event, text, words):
|
|
50
|
+
if len(words) == 1 and not text[-1].isspace():
|
|
51
|
+
partial = words[0].lower()
|
|
52
|
+
candidates = [cmd for cmd in self.command_names if cmd.startswith(partial)]
|
|
53
|
+
for candidate in sorted(candidates):
|
|
54
|
+
yield Completion(candidate, start_position=-len(words[-1]))
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if len(words) <= 1 or text[-1].isspace():
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
cmd = words[0]
|
|
61
|
+
partial = words[-1].lower()
|
|
62
|
+
|
|
63
|
+
matches, _, _ = self.commands.matching_commands(cmd)
|
|
64
|
+
if len(matches) == 1:
|
|
65
|
+
cmd = matches[0]
|
|
66
|
+
elif cmd not in matches:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
raw_completer = self.commands.get_raw_completions(cmd)
|
|
70
|
+
if raw_completer:
|
|
71
|
+
yield from raw_completer(document, complete_event)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if cmd not in self.command_completions:
|
|
75
|
+
candidates = self.commands.get_completions(cmd)
|
|
76
|
+
self.command_completions[cmd] = candidates
|
|
77
|
+
else:
|
|
78
|
+
candidates = self.command_completions[cmd]
|
|
79
|
+
|
|
80
|
+
if candidates is None:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
candidates = [word for word in candidates if partial in word.lower()]
|
|
84
|
+
for candidate in sorted(candidates):
|
|
85
|
+
yield Completion(candidate, start_position=-len(words[-1]))
|
|
86
|
+
|
|
87
|
+
def get_completions(self, document, complete_event):
|
|
88
|
+
|
|
89
|
+
text = document.text_before_cursor
|
|
90
|
+
words = text.split()
|
|
91
|
+
if not words:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if text and text[-1].isspace():
|
|
95
|
+
# don't keep completing after a space
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if text[0] == "/":
|
|
99
|
+
try:
|
|
100
|
+
yield from self.get_command_completions(document, complete_event, text, words)
|
|
101
|
+
return
|
|
102
|
+
except CommandCompletionException:
|
|
103
|
+
# Fall through to normal completion
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# Check for @ symbol anywhere in the text before cursor
|
|
107
|
+
text_before_cursor = document.text_before_cursor
|
|
108
|
+
at_pos = text_before_cursor.rfind("@")
|
|
109
|
+
|
|
110
|
+
if at_pos != -1:
|
|
111
|
+
try:
|
|
112
|
+
# Extract query text from @ symbol to cursor
|
|
113
|
+
query_text = text_before_cursor[at_pos:]
|
|
114
|
+
current_pos = len(text_before_cursor)
|
|
115
|
+
|
|
116
|
+
# Check if user just completed a selection and cursor is at end
|
|
117
|
+
if (self.completion_state['just_completed'] and
|
|
118
|
+
current_pos == self.completion_state['last_completion_pos'] and
|
|
119
|
+
query_text == self.completion_state['last_at_command']):
|
|
120
|
+
# Reset state and don't show suggestions immediately after completion
|
|
121
|
+
self.completion_state['just_completed'] = False
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Reset completion state if user moved cursor or changed text
|
|
125
|
+
if (current_pos != self.completion_state['last_completion_pos'] or
|
|
126
|
+
query_text != self.completion_state['last_at_command']):
|
|
127
|
+
self.completion_state['just_completed'] = False
|
|
128
|
+
|
|
129
|
+
if self.file_recommendation_engine.should_show_suggestions(query_text):
|
|
130
|
+
suggestions = self.file_recommendation_engine.get_suggestions_sync(query_text)
|
|
131
|
+
|
|
132
|
+
# Calculate start_position to replace from @ symbol
|
|
133
|
+
start_position = at_pos - len(text_before_cursor)
|
|
134
|
+
|
|
135
|
+
for suggestion in suggestions:
|
|
136
|
+
# Create completion that will trigger state update when selected
|
|
137
|
+
# Auto-add space after completion to indicate completion end
|
|
138
|
+
completion_text = "@" + suggestion['value'] + " "
|
|
139
|
+
completion = Completion(
|
|
140
|
+
completion_text,
|
|
141
|
+
start_position=start_position,
|
|
142
|
+
display=suggestion['label']
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Store state for after completion (include space in position calculation)
|
|
146
|
+
self._prepare_completion_state(
|
|
147
|
+
completion_text,
|
|
148
|
+
at_pos + len(completion_text)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
yield completion
|
|
152
|
+
else:
|
|
153
|
+
if query_text == "@":
|
|
154
|
+
suggestions = self.file_recommendation_engine.get_suggestions_sync("@")
|
|
155
|
+
for suggestion in suggestions:
|
|
156
|
+
# Auto-add space after completion to indicate completion end
|
|
157
|
+
completion_text = "@" + suggestion['value'] + " "
|
|
158
|
+
completion = Completion(
|
|
159
|
+
completion_text,
|
|
160
|
+
start_position=at_pos - len(text_before_cursor), # Replace from @ symbol
|
|
161
|
+
display=suggestion['label']
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Store state for after completion (include space in position calculation)
|
|
165
|
+
self._prepare_completion_state(
|
|
166
|
+
completion_text,
|
|
167
|
+
at_pos + len(completion_text)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
yield completion
|
|
171
|
+
except Exception as e:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
candidates = self.words
|
|
176
|
+
candidates = [word if type(word) is tuple else (word, word) for word in candidates]
|
|
177
|
+
|
|
178
|
+
last_word = words[-1]
|
|
179
|
+
|
|
180
|
+
def _prepare_completion_state(self, completion_text: str, completion_pos: int):
|
|
181
|
+
"""
|
|
182
|
+
Prepare completion state for tracking when user selects a completion
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
completion_text: The text that will be inserted
|
|
186
|
+
completion_pos: The cursor position after completion
|
|
187
|
+
"""
|
|
188
|
+
self.completion_state.update({
|
|
189
|
+
'just_completed': True,
|
|
190
|
+
'last_completion_pos': completion_pos,
|
|
191
|
+
'last_at_command': completion_text,
|
|
192
|
+
'last_completion_text': completion_text
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
def reset_completion_state(self):
|
|
196
|
+
"""
|
|
197
|
+
Reset completion state
|
|
198
|
+
"""
|
|
199
|
+
self.completion_state.update({
|
|
200
|
+
'just_completed': False,
|
|
201
|
+
'last_completion_pos': 0,
|
|
202
|
+
'last_at_command': None,
|
|
203
|
+
'last_completion_text': None
|
|
204
|
+
})
|
siada/support/editor.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Editor module for handling system text editor interactions.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to:
|
|
5
|
+
- Discover and launch the system's configured text editor
|
|
6
|
+
- Create and manage temporary files for editing
|
|
7
|
+
- Handle editor preferences from environment variables
|
|
8
|
+
- Support cross-platform editor operations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import subprocess
|
|
14
|
+
import tempfile
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DEFAULT_EDITOR_NIX = "vi"
|
|
20
|
+
DEFAULT_EDITOR_OS_X = "vim"
|
|
21
|
+
DEFAULT_EDITOR_WINDOWS = "notepad"
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_status_message(success, message, style=None):
|
|
27
|
+
"""
|
|
28
|
+
Print a status message with appropriate styling.
|
|
29
|
+
|
|
30
|
+
:param success: Whether the operation was successful
|
|
31
|
+
:param message: The message to display
|
|
32
|
+
:param style: Optional style override. If None, uses green for success and red for failure
|
|
33
|
+
"""
|
|
34
|
+
if style is None:
|
|
35
|
+
style = "bold green" if success else "bold red"
|
|
36
|
+
console.print(message, style=style)
|
|
37
|
+
print("")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def write_temp_file(
|
|
41
|
+
input_data="",
|
|
42
|
+
suffix=None,
|
|
43
|
+
prefix=None,
|
|
44
|
+
dir=None,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Create a temporary file with the given input data.
|
|
48
|
+
|
|
49
|
+
:param input_data: Content to write to the temporary file
|
|
50
|
+
:param suffix: Optional file extension (without the dot)
|
|
51
|
+
:param prefix: Optional prefix for the temporary filename
|
|
52
|
+
:param dir: Optional directory to create the file in
|
|
53
|
+
:return: Path to the created temporary file
|
|
54
|
+
:raises: OSError if file creation or writing fails
|
|
55
|
+
"""
|
|
56
|
+
kwargs = {"prefix": prefix, "dir": dir}
|
|
57
|
+
if suffix:
|
|
58
|
+
kwargs["suffix"] = f".{suffix}"
|
|
59
|
+
fd, filepath = tempfile.mkstemp(**kwargs)
|
|
60
|
+
try:
|
|
61
|
+
with os.fdopen(fd, "w") as f:
|
|
62
|
+
f.write(input_data)
|
|
63
|
+
except Exception:
|
|
64
|
+
os.close(fd)
|
|
65
|
+
raise
|
|
66
|
+
return filepath
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_environment_editor(default=None):
|
|
70
|
+
"""
|
|
71
|
+
Fetches the preferred editor from the environment variables.
|
|
72
|
+
|
|
73
|
+
This function checks the following environment variables in order to
|
|
74
|
+
determine the user's preferred editor:
|
|
75
|
+
|
|
76
|
+
- VISUAL
|
|
77
|
+
- EDITOR
|
|
78
|
+
|
|
79
|
+
:param default: The default editor to return if no environment variable is set.
|
|
80
|
+
:type default: str or None
|
|
81
|
+
:return: The preferred editor as specified by environment variables or the default value.
|
|
82
|
+
:rtype: str or None
|
|
83
|
+
"""
|
|
84
|
+
editor = os.environ.get("VISUAL", os.environ.get("EDITOR", default))
|
|
85
|
+
return editor
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def discover_editor(editor_override=None):
|
|
89
|
+
"""
|
|
90
|
+
Discovers and returns the appropriate editor command.
|
|
91
|
+
|
|
92
|
+
Handles cases where the editor command includes arguments, including quoted arguments
|
|
93
|
+
with spaces (e.g. 'vim -c "set noswapfile"').
|
|
94
|
+
|
|
95
|
+
:return: The editor command as a string
|
|
96
|
+
:rtype: str
|
|
97
|
+
"""
|
|
98
|
+
system = platform.system()
|
|
99
|
+
if system == "Windows":
|
|
100
|
+
default_editor = DEFAULT_EDITOR_WINDOWS
|
|
101
|
+
elif system == "Darwin":
|
|
102
|
+
default_editor = DEFAULT_EDITOR_OS_X
|
|
103
|
+
else:
|
|
104
|
+
default_editor = DEFAULT_EDITOR_NIX
|
|
105
|
+
|
|
106
|
+
if editor_override:
|
|
107
|
+
editor = editor_override
|
|
108
|
+
else:
|
|
109
|
+
editor = get_environment_editor(default_editor)
|
|
110
|
+
|
|
111
|
+
return editor
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def pipe_editor(input_data="", suffix=None, editor=None):
|
|
115
|
+
"""
|
|
116
|
+
Opens the system editor with optional input data and returns the edited content.
|
|
117
|
+
|
|
118
|
+
This function creates a temporary file with the provided input data, opens it in
|
|
119
|
+
the system editor, waits for the user to make changes and close the editor, then
|
|
120
|
+
reads and returns the modified content. The temporary file is deleted afterwards.
|
|
121
|
+
|
|
122
|
+
:param input_data: Initial content to populate the editor with
|
|
123
|
+
:type input_data: str
|
|
124
|
+
:param suffix: Optional file extension for the temporary file (e.g. '.txt', '.md')
|
|
125
|
+
:type suffix: str or None
|
|
126
|
+
:return: The edited content after the editor is closed
|
|
127
|
+
:rtype: str
|
|
128
|
+
"""
|
|
129
|
+
filepath = write_temp_file(input_data, suffix)
|
|
130
|
+
command_str = discover_editor(editor)
|
|
131
|
+
command_str += " " + filepath
|
|
132
|
+
|
|
133
|
+
subprocess.call(command_str, shell=True)
|
|
134
|
+
with open(filepath, "r") as f:
|
|
135
|
+
output_data = f.read()
|
|
136
|
+
try:
|
|
137
|
+
os.remove(filepath)
|
|
138
|
+
except PermissionError:
|
|
139
|
+
print_status_message(
|
|
140
|
+
False,
|
|
141
|
+
(
|
|
142
|
+
f"WARNING: Unable to delete temporary file {filepath!r}. You may need to delete it"
|
|
143
|
+
" manually."
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
return output_data
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_search_path_list(default_file, git_root, command_line_file):
|
|
7
|
+
files = []
|
|
8
|
+
files.append(Path.home() / default_file) # homedir
|
|
9
|
+
if git_root:
|
|
10
|
+
files.append(Path(git_root) / default_file) # git root
|
|
11
|
+
files.append(default_file)
|
|
12
|
+
if command_line_file:
|
|
13
|
+
files.append(command_line_file)
|
|
14
|
+
|
|
15
|
+
resolved_files = []
|
|
16
|
+
for fn in files:
|
|
17
|
+
try:
|
|
18
|
+
resolved_files.append(Path(fn).resolve())
|
|
19
|
+
except OSError:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
files = resolved_files
|
|
23
|
+
files.reverse()
|
|
24
|
+
uniq = []
|
|
25
|
+
for fn in files:
|
|
26
|
+
if fn not in uniq:
|
|
27
|
+
uniq.append(fn)
|
|
28
|
+
uniq.reverse()
|
|
29
|
+
files = uniq
|
|
30
|
+
files = list(map(str, files))
|
|
31
|
+
files = list(dict.fromkeys(files))
|
|
32
|
+
|
|
33
|
+
return files
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"):
|
|
37
|
+
# Standard .env file search path
|
|
38
|
+
dotenv_files = generate_search_path_list(
|
|
39
|
+
".env",
|
|
40
|
+
git_root,
|
|
41
|
+
dotenv_fname,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
loaded = []
|
|
45
|
+
for fname in dotenv_files:
|
|
46
|
+
try:
|
|
47
|
+
if Path(fname).exists():
|
|
48
|
+
load_dotenv(fname, override=True, encoding=encoding)
|
|
49
|
+
loaded.append(fname)
|
|
50
|
+
except OSError as e:
|
|
51
|
+
print(f"OSError loading {fname}: {e}")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Error loading {fname}: {e}")
|
|
54
|
+
return loaded
|
siada/support/repo.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import git
|
|
8
|
+
|
|
9
|
+
ANY_GIT_ERROR = [
|
|
10
|
+
git.exc.ODBError,
|
|
11
|
+
git.exc.GitError,
|
|
12
|
+
git.exc.InvalidGitRepositoryError,
|
|
13
|
+
git.exc.GitCommandNotFound,
|
|
14
|
+
]
|
|
15
|
+
except ImportError:
|
|
16
|
+
git = None
|
|
17
|
+
ANY_GIT_ERROR = []
|
|
18
|
+
|
|
19
|
+
ANY_GIT_ERROR += [
|
|
20
|
+
OSError,
|
|
21
|
+
IndexError,
|
|
22
|
+
BufferError,
|
|
23
|
+
TypeError,
|
|
24
|
+
ValueError,
|
|
25
|
+
AttributeError,
|
|
26
|
+
AssertionError,
|
|
27
|
+
TimeoutError,
|
|
28
|
+
]
|
|
29
|
+
ANY_GIT_ERROR = tuple(ANY_GIT_ERROR)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_git_root(path=None):
|
|
34
|
+
"""Try and guess the git repo, since the conf.yml can be at the repo root
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path: Optional path to start searching from. If None, uses current directory.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str or None: Path to git repository root, or None if not found
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# If path is provided, start search from that directory
|
|
44
|
+
if path:
|
|
45
|
+
repo = git.Repo(path, search_parent_directories=True)
|
|
46
|
+
else:
|
|
47
|
+
# Default behavior: search from current directory
|
|
48
|
+
repo = git.Repo(search_parent_directories=True)
|
|
49
|
+
return repo.working_tree_dir
|
|
50
|
+
except (git.InvalidGitRepositoryError, FileNotFoundError):
|
|
51
|
+
return None
|