Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/code_assistant/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ async-trait = "0.1"
dotenv = "0.15"
dirs = "5.0"
md5 = "0.7.0"
shell-words = "1.1"

# Date and time handling
chrono = { version = "0.4", features = ["serde"] }
Expand Down
1 change: 1 addition & 0 deletions crates/code_assistant/assets/icons/panel_left_close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions crates/code_assistant/assets/icons/panel_left_open.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 7 additions & 46 deletions crates/code_assistant/src/acp/terminal_executor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use agent_client_protocol::{self as acp, Client};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use shell_words::split as split_command_line;
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
Expand Down Expand Up @@ -58,18 +57,6 @@ impl ACPTerminalCommandExecutor {
default_timeout: DEFAULT_TIMEOUT,
}
}

fn parse_command_line(command_line: &str) -> Result<(String, Vec<String>)> {
let mut parts = split_command_line(command_line)
.map_err(|e| anyhow!("Failed to parse command line '{command_line}': {e}"))?
.into_iter();

let command = parts
.next()
.ok_or_else(|| anyhow!("Command line is empty"))?;
let args = parts.collect();
Ok((command, args))
}
}

#[async_trait]
Expand All @@ -91,11 +78,6 @@ impl CommandExecutor for ACPTerminalCommandExecutor {
callback: Option<&dyn StreamingCallback>,
sandbox_request: Option<&SandboxCommandRequest>,
) -> Result<CommandOutput> {
let (command, args) = match Self::parse_command_line(command_line) {
Ok(parsed) => parsed,
Err(err) => return Err(err),
};

let sender = match terminal_worker_sender() {
Some(sender) => sender,
None => {
Expand All @@ -109,8 +91,7 @@ impl CommandExecutor for ACPTerminalCommandExecutor {
let (event_tx, mut event_rx) = mpsc::unbounded_channel();
let request = TerminalExecuteRequest {
session_id: self.session_id.clone(),
command,
args,
command_line: command_line.to_string(),
cwd: working_dir.cloned(),
timeout: self.default_timeout,
streaming: callback.is_some(),
Expand Down Expand Up @@ -157,8 +138,7 @@ impl CommandExecutor for ACPTerminalCommandExecutor {
#[derive(Debug)]
struct TerminalExecuteRequest {
session_id: acp::SessionId,
command: String,
args: Vec<String>,
command_line: String,
cwd: Option<PathBuf>,
timeout: Duration,
streaming: bool,
Expand Down Expand Up @@ -204,18 +184,19 @@ async fn run_command(
) -> Result<CommandOutput> {
let TerminalExecuteRequest {
session_id,
command,
args,
command_line,
cwd,
timeout,
streaming,
..
} = request;

// Pass the complete command line as the command parameter with empty args.
// This avoids escaping issues on the Zed side when args are passed separately.
let create_request = acp::CreateTerminalRequest {
session_id: session_id.clone(),
command,
args,
command: command_line,
args: Vec::new(),
env: Vec::new(),
cwd,
output_byte_limit: Some(OUTPUT_BYTE_LIMIT),
Expand Down Expand Up @@ -369,23 +350,3 @@ async fn wait_for_terminal_completion(
output: output_response.output,
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_command_line_handles_quotes() {
let (command, args) =
ACPTerminalCommandExecutor::parse_command_line("cargo test --package \"my crate\"")
.unwrap();
assert_eq!(command, "cargo");
assert_eq!(args, vec!["test", "--package", "my crate"]);
}

#[test]
fn parse_command_line_errors_on_empty_input() {
let err = ACPTerminalCommandExecutor::parse_command_line("").unwrap_err();
assert!(err.to_string().contains("empty"));
}
}
34 changes: 32 additions & 2 deletions crates/code_assistant/src/acp/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,9 +668,39 @@ impl UserInterface for ACPUserUI {
);
}

// Resource events - could be used for "follow mode" in ACP
UiEvent::ResourceLoaded { project, path } => {
tracing::trace!(
"ACPUserUI: ResourceLoaded - project: {}, path: {}",
project,
path.display()
);
// TODO: Could emit follow mode updates here
}
UiEvent::ResourceWritten { project, path } => {
tracing::trace!(
"ACPUserUI: ResourceWritten - project: {}, path: {}",
project,
path.display()
);
}
UiEvent::DirectoryListed { project, path } => {
tracing::trace!(
"ACPUserUI: DirectoryListed - project: {}, path: {}",
project,
path.display()
);
}
UiEvent::ResourceDeleted { project, path } => {
tracing::trace!(
"ACPUserUI: ResourceDeleted - project: {}, path: {}",
project,
path.display()
);
}

// Events that don't translate to ACP
UiEvent::UpdateMemory { .. }
| UiEvent::SetMessages { .. }
UiEvent::SetMessages { .. }
| UiEvent::DisplayCompactionSummary { .. }
| UiEvent::StreamingStarted(_)
| UiEvent::StreamingStopped { .. }
Expand Down
Loading