Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add navigation commands into the editor #5

Closed
wants to merge 43 commits into from
Closed
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
68f1c00
add gitignore for oh-viewer
ryanhoangt Nov 4, 2024
a32e357
add tree_sitter parser and some utils from locify
ryanhoangt Nov 4, 2024
f806342
add get symbol definition impl
ryanhoangt Nov 4, 2024
5857451
change agent_ide to openhands_aci
ryanhoangt Nov 5, 2024
2520c12
rename cont.
ryanhoangt Nov 5, 2024
35b94e0
add get symbol refs impl
ryanhoangt Nov 5, 2024
649abc3
add temp demo cli
ryanhoangt Nov 5, 2024
ebcdf40
Merge branch 'main' into ht/jump-commands
ryanhoangt Nov 5, 2024
d6155ab
Merge branch 'main' into ht/jump-commands
ryanhoangt Nov 17, 2024
2c81540
update editor object param
ryanhoangt Nov 17, 2024
68ec986
use lazy init
ryanhoangt Nov 18, 2024
f780897
update path arg in object
ryanhoangt Nov 18, 2024
4fc76fc
add navigation tips
ryanhoangt Nov 18, 2024
7344427
add fuzzy search
ryanhoangt Nov 20, 2024
38ec03e
fix lint
ryanhoangt Nov 20, 2024
08ca8cc
handle no git repo found
ryanhoangt Nov 21, 2024
e07a6c3
move git import under init
ryanhoangt Nov 22, 2024
cca111e
Merge branch 'main' into ht/jump-commands
ryanhoangt Nov 23, 2024
719b7e5
fix tests
ryanhoangt Nov 23, 2024
8204cbe
test: Add tests for jump_to_definition and find_references commands
openhands-agent Nov 23, 2024
a9484f2
use logger and remove print
ryanhoangt Nov 23, 2024
04c8876
rename cache dir
ryanhoangt Nov 23, 2024
6c7c964
remove tips when navigation commands are disabled
ryanhoangt Nov 23, 2024
b1ecd84
rename property
ryanhoangt Nov 23, 2024
46ddee5
remove redundant code
ryanhoangt Nov 23, 2024
3d8d25a
use tmp dir for storing cache
ryanhoangt Nov 25, 2024
6017fc9
remove traversal, using pwd set lazily
ryanhoangt Nov 29, 2024
f36c14d
remove extra newline
ryanhoangt Nov 29, 2024
b11c30e
Merge branch 'main' into ht/jump-commands
ryanhoangt Nov 30, 2024
5a678b9
use private methods
ryanhoangt Nov 30, 2024
41af934
use private methods for navigator
ryanhoangt Nov 30, 2024
8c153e3
use more exact git repo detection status
ryanhoangt Nov 30, 2024
1a26afd
fix bug lazy init
ryanhoangt Nov 30, 2024
7ff0dc8
fix bug
ryanhoangt Nov 30, 2024
4b305c2
fix symlink bug in swe-bench eval
ryanhoangt Dec 1, 2024
295820f
only use tips for viewing file
ryanhoangt Dec 4, 2024
99060fb
Merge branch 'main' into ht/jump-commands
ryanhoangt Dec 4, 2024
965cf7c
Revert "Merge branch 'main' into ht/jump-commands"
ryanhoangt Dec 4, 2024
721f2fc
Merge branch 'main' into ht/jump-commands
ryanhoangt Dec 4, 2024
1ad1ae9
rename lois
ryanhoangt Dec 6, 2024
681d079
remove symlink handling
ryanhoangt Dec 12, 2024
36b347e
add docstring
ryanhoangt Dec 12, 2024
58270d0
Merge branch 'main' into ht/jump-commands
ryanhoangt Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -162,3 +162,6 @@ cython_debug/
#.idea/
.vscode/
.DS_Store

# Cache
.cache*
4 changes: 3 additions & 1 deletion openhands_aci/editor/__init__.py
Original file line number Diff line number Diff line change
@@ -19,12 +19,13 @@ def _make_api_tool_result(tool_result: ToolResult) -> str:

def file_editor(
command: Command,
path: str,
path: str | None = None,
file_text: str | None = None,
view_range: list[int] | None = None,
old_str: str | None = None,
new_str: str | None = None,
insert_line: int | None = None,
symbol_name: str | None = None,
enable_linting: bool = False,
) -> str:
try:
@@ -36,6 +37,7 @@ def file_editor(
old_str=old_str,
new_str=new_str,
insert_line=insert_line,
symbol_name=symbol_name,
enable_linting=enable_linting,
)
except ToolError as e:
160 changes: 153 additions & 7 deletions openhands_aci/editor/editor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import argparse
import json
import tempfile
from collections import defaultdict
from pathlib import Path
@@ -12,6 +14,8 @@
EditorToolParameterMissingError,
ToolError,
)
from .navigator import SymbolNavigator
from .prompts import NAVIGATION_TIPS
from .results import CLIResult, maybe_truncate

Command = Literal[
@@ -20,8 +24,8 @@
'str_replace',
'insert',
'undo_edit',
# 'jump_to_definition', TODO:
# 'find_references' TODO:
'jump_to_definition',
'find_references',
]


@@ -42,22 +46,29 @@ class OHEditor:
def __init__(self):
self._file_history: dict[Path, list[str]] = defaultdict(list)
self._linter = DefaultLinter()
self._symbol_navigator = SymbolNavigator()

def __call__(
self,
*,
command: Command,
path: str,
path: str | None = None,
file_text: str | None = None,
view_range: list[int] | None = None,
old_str: str | None = None,
new_str: str | None = None,
insert_line: int | None = None,
symbol_name: str | None = None,
enable_linting: bool = False,
**kwargs,
) -> CLIResult:
_path = Path(path)
self.validate_path(command, _path)
if path is None and command not in ['jump_to_definition', 'find_references']:
raise EditorToolParameterMissingError(command, 'path')

if path is not None:
_path = Path(path)
self.validate_path(command, _path)

if command == 'view':
return self.view(_path, view_range)
elif command == 'create':
@@ -88,6 +99,14 @@ def __call__(
return self.insert(_path, insert_line, new_str, enable_linting)
elif command == 'undo_edit':
return self.undo_edit(_path)
elif command == 'jump_to_definition':
if not symbol_name:
raise EditorToolParameterMissingError(command, 'symbol_name')
return self.jump_to_definition(_path if path else None, symbol_name)
elif command == 'find_references':
if not symbol_name:
raise EditorToolParameterMissingError(command, 'symbol_name')
return self.find_references(symbol_name)

raise ToolError(
f'Unrecognized command {command}. The allowed commands for the {self.TOOL_NAME} tool are: {", ".join(get_args(Command))}'
@@ -188,7 +207,8 @@ def view(self, path: Path, view_range: list[int] | None = None) -> CLIResult:
start_line = 1
if not view_range:
return CLIResult(
output=self._make_output(file_content, str(path), start_line),
output=self._make_output(file_content, str(path), start_line)
+ (NAVIGATION_TIPS if self._symbol_navigator.is_enabled else ''),
path=str(path),
prev_exist=True,
)
@@ -230,7 +250,8 @@ def view(self, path: Path, view_range: list[int] | None = None) -> CLIResult:
file_content = '\n'.join(file_content_lines[start_line - 1 : end_line])
return CLIResult(
path=str(path),
output=self._make_output(file_content, str(path), start_line),
output=self._make_output(file_content, str(path), start_line)
+ (NAVIGATION_TIPS if self._symbol_navigator.is_enabled else ''),
prev_exist=True,
)

@@ -307,6 +328,21 @@ def insert(
new_content=new_file_text,
)

def jump_to_definition(self, path: Path | None, symbol_name: str) -> CLIResult:
"""
Implement the jump_to_definition command.
"""
# FIXME: remove the path parameter from the method signature
return CLIResult(
output=self._symbol_navigator.get_definitions_tree(symbol_name)
)

def find_references(self, symbol_name: str) -> CLIResult:
"""
Implement the find_references command.
"""
return CLIResult(output=self._symbol_navigator.get_references_tree(symbol_name))

def validate_path(self, command: Command, path: Path) -> None:
"""
Check that the path/command combination is valid.
@@ -420,3 +456,113 @@ def _run_linting(self, old_content: str, new_content: str, path: Path) -> str:
f'- Line {result.line}, Column {result.column}: {result.message}'
)
return '\n'.join(output) + '\n'


def parse_command_input(cmd_input: str) -> tuple[Command, dict]:
"""Parse the command input into command name and parameters."""
try:
cmd_dict = json.loads(cmd_input)
command = cmd_dict.pop('command')
assert command in get_args(Command)
return command, cmd_dict # type: ignore
except json.JSONDecodeError:
raise ValueError('Invalid command format. Please provide a valid JSON object.')
except KeyError:
raise ValueError("Command must include a 'command' field.")


def print_help():
"""Print available commands and their usage."""
help_text = """
Available commands (provide as JSON objects):
------------------------------------------
1. View file/directory:
{"command": "view", "path": "/absolute/path", "view_range": [start_line, end_line]}

2. Create file:
{"command": "create", "path": "/absolute/path", "file_text": "content"}

3. Replace string:
{"command": "str_replace", "path": "/absolute/path", "old_str": "old", "new_str": "new"}

4. Insert at line:
{"command": "insert", "path": "/absolute/path", "insert_line": number, "new_str": "content"}

5. Undo last edit:
{"command": "undo_edit", "path": "/absolute/path"}

6. Jump to definition:
{"command": "jump_to_definition", "path": "/absolute/path", "symbol_name": "symbol"}

7. Find references:
{"command": "find_references", "symbol_name": "symbol"}

Type 'exit' to quit or 'help' to see this message again.
"""
print(help_text)


def main():
# Set up argument parser
parser = argparse.ArgumentParser(description='OHEditor - File System Editor Tool')
parser.add_argument(
'--workspace',
type=str,
default='./',
help='Workspace directory path (default: current directory)',
)

# Parse command line arguments
args = parser.parse_args()

# Initialize the editor
editor = OHEditor()

# Print welcome message and help
print(f'OHEditor initialized with workspace: {args.workspace}')
print_help()

# Main command loop
while True:
try:
# Get user input
user_input = input("\nEnter command (or 'help'/'exit'): ").strip()

# Check for exit command
if user_input.lower() == 'exit':
print('Exiting OHEditor...')
break

# Check for help command
if user_input.lower() == 'help':
print_help()
continue

# Parse and execute command
try:
command, params = parse_command_input(user_input)
result = editor(command=command, **params)
print('\nResult:')
print(result.output)
if result.error:
print('\nErrors:')
print(result.error)

except (
ValueError,
ToolError,
EditorToolParameterInvalidError,
EditorToolParameterMissingError,
) as e:
print(f'\nError: {str(e)}')

except KeyboardInterrupt:
print("\nReceived interrupt signal. Type 'exit' to quit.")
continue
except Exception as e:
print(f'\nUnexpected error: {str(e)}')
continue


if __name__ == '__main__':
main()
Loading