mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
307 lines
10 KiB
Python
307 lines
10 KiB
Python
"""
|
|
Edit Tool - Semantic refactoring via LSP
|
|
========================================
|
|
|
|
Purpose: semantic refactors via LSP across languages.
|
|
|
|
Operations:
|
|
- rename: Rename symbol across workspace
|
|
- code_action: Apply LSP code actions
|
|
- organize_imports: Organize imports
|
|
- apply_workspace_edit: Apply arbitrary WorkspaceEdit
|
|
|
|
Supports gopls, tsserver, pyright, rust-analyzer, clangd
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from .dev_tools import DevResult, DevToolBase, create_dev_result
|
|
|
|
|
|
class EditTool(DevToolBase):
|
|
"""LSP-powered semantic editing tool"""
|
|
|
|
def __init__(self, target: str, op: str, **kwargs):
|
|
super().__init__(target, **kwargs)
|
|
self.op = op
|
|
self.file = kwargs.get("file")
|
|
self.pos = kwargs.get("pos") # {line: int, character: int}
|
|
self.range = kwargs.get("range") # {start: {line, ch}, end: {line, ch}}
|
|
self.new_name = kwargs.get("new_name")
|
|
self.only = kwargs.get("only", []) # code action kinds
|
|
self.apply = kwargs.get("apply", True)
|
|
|
|
async def execute(self) -> DevResult:
|
|
"""Execute edit operation"""
|
|
try:
|
|
if self.op == "rename":
|
|
return await self._rename()
|
|
elif self.op == "code_action":
|
|
return await self._code_action()
|
|
elif self.op == "organize_imports":
|
|
return await self._organize_imports()
|
|
elif self.op == "apply_workspace_edit":
|
|
return await self._apply_workspace_edit()
|
|
else:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[f"Unknown operation: {self.op}"],
|
|
)
|
|
except Exception as e:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[str(e)],
|
|
)
|
|
|
|
async def _rename(self) -> DevResult:
|
|
"""Rename symbol using LSP"""
|
|
if not self.file or not self.pos or not self.new_name:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=["rename requires file, pos, and new_name"],
|
|
)
|
|
|
|
# Use appropriate LSP client based on language
|
|
if self.language == "go":
|
|
return await self._gopls_rename()
|
|
elif self.language == "ts":
|
|
return await self._typescript_rename()
|
|
elif self.language == "py":
|
|
return await self._pyright_rename()
|
|
elif self.language == "rs":
|
|
return await self._rust_analyzer_rename()
|
|
else:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[f"Rename not supported for language: {self.language}"],
|
|
)
|
|
|
|
async def _code_action(self) -> DevResult:
|
|
"""Apply code actions"""
|
|
if self.language == "go":
|
|
return await self._gopls_code_action()
|
|
elif self.language == "ts":
|
|
return await self._typescript_code_action()
|
|
elif self.language == "py":
|
|
return await self._pyright_code_action()
|
|
else:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[f"Code actions not supported for language: {self.language}"],
|
|
)
|
|
|
|
async def _organize_imports(self) -> DevResult:
|
|
"""Organize imports"""
|
|
if self.language == "go":
|
|
# Use goimports directly
|
|
result = self._run_command(["goimports", "-w"] + self.resolved["files"])
|
|
elif self.language == "ts":
|
|
# Use TypeScript organize imports
|
|
return await self._typescript_organize_imports()
|
|
elif self.language == "py":
|
|
# Use ruff or isort
|
|
result = self._run_command(
|
|
["ruff", "check", "--select", "I", "--fix"] + self.resolved["files"]
|
|
)
|
|
else:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[
|
|
f"Organize imports not supported for language: {self.language}"
|
|
],
|
|
)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=self.resolved["files"] if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _gopls_rename(self) -> DevResult:
|
|
"""Rename using gopls"""
|
|
# Use gopls command line
|
|
cmd = [
|
|
"gopls",
|
|
"rename",
|
|
f"-w={self.workspace['root']}",
|
|
f"{self.file}:{self.pos['line'] + 1}:{self.pos['character'] + 1}",
|
|
self.new_name,
|
|
]
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
# Parse touched files from output if available
|
|
touched_files = []
|
|
if result.returncode == 0:
|
|
# gopls might output changed files - parse if available
|
|
touched_files = self.resolved["files"]
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="gopls",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=touched_files,
|
|
)
|
|
|
|
async def _typescript_rename(self) -> DevResult:
|
|
"""Rename using TypeScript Language Server"""
|
|
# For now, use a simple approach - could integrate with actual TS LSP
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="tsserver",
|
|
scope_resolved=self.target,
|
|
errors=["TypeScript LSP rename not implemented yet - use IDE"],
|
|
)
|
|
|
|
async def _pyright_rename(self) -> DevResult:
|
|
"""Rename using Pyright"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="pyright",
|
|
scope_resolved=self.target,
|
|
errors=["Pyright rename not implemented yet - use IDE"],
|
|
)
|
|
|
|
async def _rust_analyzer_rename(self) -> DevResult:
|
|
"""Rename using rust-analyzer"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="rust-analyzer",
|
|
scope_resolved=self.target,
|
|
errors=["rust-analyzer rename not implemented yet - use IDE"],
|
|
)
|
|
|
|
async def _gopls_code_action(self) -> DevResult:
|
|
"""Apply code actions using gopls"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="gopls",
|
|
scope_resolved=self.target,
|
|
errors=["gopls code actions not implemented yet"],
|
|
)
|
|
|
|
async def _typescript_code_action(self) -> DevResult:
|
|
"""Apply code actions using TypeScript"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="tsserver",
|
|
scope_resolved=self.target,
|
|
errors=["TypeScript code actions not implemented yet"],
|
|
)
|
|
|
|
async def _pyright_code_action(self) -> DevResult:
|
|
"""Apply code actions using Pyright"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="pyright",
|
|
scope_resolved=self.target,
|
|
errors=["Pyright code actions not implemented yet"],
|
|
)
|
|
|
|
async def _typescript_organize_imports(self) -> DevResult:
|
|
"""Organize TypeScript imports"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="tsserver",
|
|
scope_resolved=self.target,
|
|
errors=["TypeScript organize imports not implemented yet"],
|
|
)
|
|
|
|
async def _apply_workspace_edit(self) -> DevResult:
|
|
"""Apply arbitrary workspace edit"""
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=["apply_workspace_edit not implemented yet"],
|
|
)
|
|
|
|
|
|
# MCP tool integration
|
|
async def edit_tool_handler(
|
|
target: str,
|
|
op: str,
|
|
file: Optional[str] = None,
|
|
pos: Optional[Dict[str, int]] = None,
|
|
range: Optional[Dict[str, Any]] = None,
|
|
new_name: Optional[str] = None,
|
|
only: Optional[List[str]] = None,
|
|
apply: bool = True,
|
|
language: str = "auto",
|
|
backend: str = "auto",
|
|
root: Optional[str] = None,
|
|
env: Optional[Dict[str, str]] = None,
|
|
dry_run: bool = False,
|
|
) -> Dict[str, Any]:
|
|
"""MCP handler for edit tool"""
|
|
|
|
tool = EditTool(
|
|
target=target,
|
|
op=op,
|
|
file=file,
|
|
pos=pos,
|
|
range=range,
|
|
new_name=new_name,
|
|
only=only or [],
|
|
apply=apply,
|
|
language=language,
|
|
backend=backend,
|
|
root=root,
|
|
env=env,
|
|
dry_run=dry_run,
|
|
)
|
|
|
|
result = await tool.execute()
|
|
return result.dict()
|