feat: Enhance command execution handling in TerminalCog with subprocess improvements
This commit is contained in:
parent
b598df1f82
commit
07cd5d9acf
@ -284,6 +284,12 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
|
||||
self.last_command = command # Store command for display after execution
|
||||
|
||||
# For other commands, use subprocess
|
||||
if self.active_process and self.active_process.poll() is None:
|
||||
self.output_history.append("A command is already running. Please wait or refresh.")
|
||||
await self._update_terminal_message(interaction)
|
||||
return
|
||||
|
||||
# For other commands, use subprocess
|
||||
if self.active_process and self.active_process.poll() is None:
|
||||
self.output_history.append("A command is already running. Please wait or refresh.")
|
||||
@ -291,25 +297,35 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
return
|
||||
|
||||
try:
|
||||
# Use shlex.split for safer command parsing
|
||||
command_parts = shlex.split(command)
|
||||
if not command_parts:
|
||||
self.output_history.append("No command provided.")
|
||||
self.output_history.append(f"{self.current_cwd}> ")
|
||||
self.scroll_offset = max(0, len(self.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self._update_terminal_message(interaction)
|
||||
return
|
||||
|
||||
self.active_process = subprocess.Popen(
|
||||
command,
|
||||
shell=True, # Security risk: Be absolutely sure this is owner-only.
|
||||
command_parts,
|
||||
stdin=subprocess.PIPE, # Enable interactive input
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
text=True, # Use text mode for easier handling of output
|
||||
cwd=self.current_cwd,
|
||||
bufsize=1, # Line-buffered
|
||||
universal_newlines=True # For text mode
|
||||
# bufsize=1, # Removed line-buffering for better interactive handling
|
||||
# universal_newlines=True # text=True handles this
|
||||
)
|
||||
if not self.auto_update_task.is_running():
|
||||
self.auto_update_task.start()
|
||||
|
||||
|
||||
# Initial update to show command is running
|
||||
self.output_history.append(f"{self.current_cwd}> {command}") # Add command to history immediately
|
||||
self.scroll_offset = max(0, len(self.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self._update_terminal_message(interaction)
|
||||
|
||||
except FileNotFoundError:
|
||||
self.output_history.append(f"Error: Command not found: {command.split()[0]}")
|
||||
self.output_history.append(f"Error: Command not found: {command_parts[0]}")
|
||||
self.output_history.append(f"{self.current_cwd}> ")
|
||||
self.scroll_offset = max(0, len(self.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self._update_terminal_message(interaction)
|
||||
@ -330,23 +346,32 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
updated = False
|
||||
if self.active_process:
|
||||
new_output_lines = []
|
||||
interactive_prompt_detected = False
|
||||
try:
|
||||
# Read non-blockingly (or as much as possible without full block)
|
||||
# stdout
|
||||
while True: # Read all available lines from stdout
|
||||
line = self.active_process.stdout.readline()
|
||||
if not line: # No more output currently, or EOF
|
||||
break
|
||||
new_output_lines.append(line.strip()) # Strip newlines
|
||||
updated = True
|
||||
|
||||
# stderr
|
||||
while True: # Read all available lines from stderr
|
||||
line = self.active_process.stderr.readline()
|
||||
if not line:
|
||||
break
|
||||
new_output_lines.append(f"STDERR: {line.strip()}")
|
||||
updated = True
|
||||
# Read non-blockingly from stdout and stderr
|
||||
while True:
|
||||
stdout_line = self.active_process.stdout.readline()
|
||||
stderr_line = self.active_process.stderr.readline()
|
||||
|
||||
if not stdout_line and not stderr_line:
|
||||
break # No more output currently
|
||||
|
||||
if stdout_line:
|
||||
line = stdout_line.strip()
|
||||
new_output_lines.append(line)
|
||||
updated = True
|
||||
# Basic check for interactive prompts (can be improved)
|
||||
if line.strip().endswith((':', '#', '$', '>')):
|
||||
interactive_prompt_detected = True
|
||||
|
||||
if stderr_line:
|
||||
line = f"STDERR: {stderr_line.strip()}"
|
||||
new_output_lines.append(line)
|
||||
updated = True
|
||||
# Check stderr for prompts too, though less common
|
||||
if stderr_line.strip().endswith((':', '#', '$', '>')):
|
||||
interactive_prompt_detected = True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
new_output_lines.append(f"Error reading process output: {e}")
|
||||
@ -366,7 +391,7 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
|
||||
if final_stdout: self.output_history.extend(final_stdout.strip().splitlines())
|
||||
if final_stderr: self.output_history.extend([f"STDERR: {l}" for l in final_stderr.strip().splitlines()])
|
||||
|
||||
|
||||
self.output_history.append(f"Process finished with exit code {return_code}.")
|
||||
self.output_history.append(f"{self.current_cwd}> ") # New prompt
|
||||
self.active_process = None
|
||||
@ -378,6 +403,11 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
self.scroll_offset = max(0, len(self.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self._update_terminal_message(interaction)
|
||||
|
||||
# If an interactive prompt was detected and the process is still running,
|
||||
# we might need to signal the user or change button states.
|
||||
# This part will be handled in the TerminalView update_button_states
|
||||
# and potentially a new mechanism for sending input.
|
||||
|
||||
|
||||
class TerminalInputModal(ui.Modal, title="Send Command to Terminal"):
|
||||
command_input = ui.TextInput(
|
||||
@ -392,14 +422,31 @@ class TerminalInputModal(ui.Modal, title="Send Command to Terminal"):
|
||||
self.cog = cog
|
||||
|
||||
async def on_submit(self, interaction: Interaction):
|
||||
command = self.command_input.value
|
||||
if command:
|
||||
# Defer here as execute_shell_command can take time and will edit later
|
||||
await interaction.response.defer()
|
||||
await self.cog.execute_shell_command(command, interaction)
|
||||
user_input = self.command_input.value
|
||||
if not user_input:
|
||||
await interaction.response.send_message("No input entered.", ephemeral=True)
|
||||
return
|
||||
|
||||
# Defer the interaction as we will update the message later
|
||||
await interaction.response.defer()
|
||||
|
||||
if self.cog.active_process and self.cog.active_process.poll() is None:
|
||||
# There is an active process, assume the input is for it
|
||||
try:
|
||||
self.cog.active_process.stdin.write(user_input + '\n')
|
||||
self.cog.active_process.stdin.flush()
|
||||
# Add the input to history for display
|
||||
self.cog.output_history.append(user_input)
|
||||
self.cog.scroll_offset = max(0, len(self.cog.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self.cog._update_terminal_message(interaction) # Update message with the input
|
||||
except Exception as e:
|
||||
self.cog.output_history.append(f"Error sending input to process: {e}")
|
||||
self.cog.scroll_offset = max(0, len(self.cog.output_history) - MAX_OUTPUT_LINES_PER_IMAGE)
|
||||
await self.cog._update_terminal_message(interaction)
|
||||
else:
|
||||
await interaction.response.send_message("No command entered.", ephemeral=True)
|
||||
|
||||
# No active process, execute as a new command
|
||||
await self.cog.execute_shell_command(user_input, interaction)
|
||||
|
||||
async def on_error(self, interaction: Interaction, error: Exception):
|
||||
await interaction.response.send_message(f"Modal error: {error}", ephemeral=True)
|
||||
print(f"TerminalInputModal error: {error}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user