Skip to content

Commit

Permalink
feat: allow editing of code blocks before execution
Browse files Browse the repository at this point in the history
  • Loading branch information
ericrallen committed Oct 10, 2023
1 parent 86a6430 commit 0be3623
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 46 deletions.
15 changes: 4 additions & 11 deletions interpreter/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import platform
import pkg_resources
import appdirs
from ..utils.display_markdown_message import display_markdown_message
from ..utils.open_file import open_file
from ..terminal_interface.conversation_navigator import conversation_navigator

arguments = [
Expand Down Expand Up @@ -114,16 +114,9 @@ def cli(interpreter):
config_dir = appdirs.user_config_dir("Open Interpreter")
config_path = os.path.join(config_dir, 'config.yaml')
print(f"Opening `{config_path}`...")
# Use the default system editor to open the file
if platform.system() == 'Windows':
os.startfile(config_path) # This will open the file with the default application, e.g., Notepad
else:
try:
# Try using xdg-open on non-Windows platforms
subprocess.call(['xdg-open', config_path])
except FileNotFoundError:
# Fallback to using 'open' on macOS if 'xdg-open' is not available
subprocess.call(['open', config_path])

open_file(file_path=config_path)

return

# TODO Implement model explorer
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions interpreter/code_interpreters/languages/utils/language_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from ...language_map import language_map


def get_language_file_extension(language_name):
"""
Get the file extension for a given language
"""
language = language_map[language_name.lower()]

if language.file_extension:
return language.file_extension
else:
return language


def get_language_proper_name(language_name):
"""
Get the proper name for a given language
"""
language = language_map[language_name.lower()]

if language.proper_name:
return language.proper_name
else:
return language
15 changes: 8 additions & 7 deletions interpreter/core/respond.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ def respond(interpreter):
print("Running code:", interpreter.messages[-1])

try:
# Yield a message, such that the user can stop code execution if they want to
try:
yield {"executing": {"code": interpreter.messages[-1]["code"], "language": interpreter.messages[-1]["language"]}}
except GeneratorExit:
# The user might exit here.
# We need to tell python what we (the generator) should do if they exit
break

# What code do you want to run?
code = interpreter.messages[-1]["code"]

Expand All @@ -105,13 +113,6 @@ def respond(interpreter):
interpreter._code_interpreters[language] = create_code_interpreter(language)
code_interpreter = interpreter._code_interpreters[language]

# Yield a message, such that the user can stop code execution if they want to
try:
yield {"executing": {"code": code, "language": language}}
except GeneratorExit:
# The user might exit here.
# We need to tell python what we (the generator) should do if they exit
break

# Yield each line, also append it to last messages' output
interpreter.messages[-1]["output"] = ""
Expand Down
32 changes: 30 additions & 2 deletions interpreter/terminal_interface/terminal_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..utils.display_markdown_message import display_markdown_message
from ..utils.truncate_output import truncate_output
from ..utils.scan_code import scan_code
from ..utils.edit_code import edit_code


def terminal_interface(interpreter, message):
Expand Down Expand Up @@ -119,10 +120,37 @@ def terminal_interface(interpreter, message):

scan_code(code, language, interpreter)

response = input(" Would you like to run this code? (y/n)\n\n ")
response = input(" Would you like to run this code? (y/n/%edit)\n\n ")
print("") # <- Aesthetic choice

if response.strip().lower() == "y":
if response.strip().lower() == "%edit":
# open a code editor with this code in a temporary file
# when the user saves and exits, run the code
# Get code language and actual code from the chunk
# We need to give these to our editor to open the file
language = chunk["executing"]["language"]
code = chunk["executing"]["code"]

edited_code = edit_code(code, language, interpreter)

# Get the last message, so we can extend it with the edited code
old_message = interpreter.messages[-1]

# Remove the last message, which is the code we're about to edit
interpreter.messages = interpreter.messages[:-1]

ran_code_block = True
render_cursor = False

active_block.end()

# Add the edited code to the messages
interpreter.messages.append({
**old_message,
"code": edited_code,
})

elif response.strip().lower() == "y":
# Create a new, identical block where the code will actually be run
# Conveniently, the chunk includes everything we need to do this:
active_block = CodeBlock()
Expand Down
64 changes: 64 additions & 0 deletions interpreter/utils/edit_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
from yaspin import yaspin

from .temporary_file import create_temporary_file, cleanup_temporary_file
from .open_file import open_file
from ..code_interpreters.languages.utils.language_tools import get_language_file_extension, get_language_proper_name


def edit_code(code, language, interpreter):
"""
Edit the code and listen for changes with watchdog
"""

temp_code = code

temp_file = create_temporary_file(
temp_code, get_language_file_extension(language), verbose=interpreter.debug_mode
)

language_name = get_language_proper_name(language)

file_name = os.path.basename(temp_file)

if interpreter.debug_mode:
print(f"Editing {language_name} code in {file_name}")
print("---")

# Run semgrep
try:
print(" Press `ENTER` after you've saved your edits.")

open_file(temp_file)

with yaspin(text=f" Editing {language_name} code...").green.right.dots as loading:
# HACK: we're just listening for the user to come back and hit Enter
# but we aren't actually doing anything with it and since we're inside
# of a yaspin handler, the input prompt doesn't actually render
done = input(" Press `ENTER` when you're ready to continue:")

loading.stop()
loading.hide()

if done == "":
print(f" {language_name} code updated.")
print("") # <- Aesthetic choice

temp_code = open(temp_file).read()

if interpreter.debug_mode:
print(f"Getting updated {language_name} code from {file_name}")
print("---")

cleanup_temporary_file(temp_file, verbose=interpreter.debug_mode)

if interpreter.debug_mode:
print(f"Deleting {file_name}")
print("---")

except Exception as e:
print(f"Could not edit {language} code.")
print(e)
print("") # <- Aesthetic choice

return temp_code
14 changes: 14 additions & 0 deletions interpreter/utils/open_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os
import platform
import subprocess

def open_file(file_path):
if platform.system() == 'Windows':
os.startfile(file_path) # This will open the file with the default application, e.g., Notepad
else:
try:
# Try using xdg-open on non-Windows platforms
subprocess.call(['xdg-open', file_path])
except FileNotFoundError:
# Fallback to using 'open' on macOS if 'xdg-open' is not available
subprocess.call(['open', file_path])
27 changes: 1 addition & 26 deletions interpreter/utils/scan_code.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,9 @@
import os
import subprocess
from yaspin import yaspin
from yaspin.spinners import Spinners

from .temporary_file import create_temporary_file, cleanup_temporary_file
from ..code_interpreters.language_map import language_map


def get_language_file_extension(language_name):
"""
Get the file extension for a given language
"""
language = language_map[language_name.lower()]

if language.file_extension:
return language.file_extension
else:
return language


def get_language_proper_name(language_name):
"""
Get the proper name for a given language
"""
language = language_map[language_name.lower()]

if language.proper_name:
return language.proper_name
else:
return language
from ..code_interpreters.languages.utils.language_tools import get_language_file_extension, get_language_proper_name


def scan_code(code, language, interpreter):
Expand Down

0 comments on commit 0be3623

Please sign in to comment.