Display edit diffs (#469)
This commit is contained in:
@@ -7,13 +7,16 @@ use json_patch::Patch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use ts_rs::TS;
|
||||
use utils::{msg_store::MsgStore, path::make_path_relative, shell::get_shell_command};
|
||||
use utils::{
|
||||
diff::create_unified_diff, msg_store::MsgStore, path::make_path_relative,
|
||||
shell::get_shell_command,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::CommandBuilder,
|
||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
||||
logs::{
|
||||
ActionType, EditDiff, NormalizedEntry, NormalizedEntryType, TodoItem as LogsTodoItem,
|
||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem as LogsTodoItem,
|
||||
stderr_processor::normalize_stderr_logs,
|
||||
utils::{EntryIndexProvider, patch::ConversationPatch},
|
||||
},
|
||||
@@ -450,17 +453,16 @@ impl AmpContentItem {
|
||||
path: make_path_relative(path, worktree_path),
|
||||
},
|
||||
AmpToolData::CreateFile { path, content, .. } => {
|
||||
let diffs = content
|
||||
let changes = content
|
||||
.as_ref()
|
||||
.map(|content| EditDiff::Replace {
|
||||
old: String::new(),
|
||||
new: content.clone(),
|
||||
.map(|content| FileChange::Write {
|
||||
content: content.clone(),
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(path, worktree_path),
|
||||
diffs,
|
||||
changes,
|
||||
}
|
||||
}
|
||||
AmpToolData::EditFile {
|
||||
@@ -469,17 +471,21 @@ impl AmpContentItem {
|
||||
new_str,
|
||||
..
|
||||
} => {
|
||||
let diffs = if old_str.is_some() || new_str.is_some() {
|
||||
vec![EditDiff::Replace {
|
||||
old: old_str.clone().unwrap_or_default(),
|
||||
new: new_str.clone().unwrap_or_default(),
|
||||
let changes = if old_str.is_some() || new_str.is_some() {
|
||||
vec![FileChange::Edit {
|
||||
unified_diff: create_unified_diff(
|
||||
path,
|
||||
old_str.as_deref().unwrap_or(""),
|
||||
new_str.as_deref().unwrap_or(""),
|
||||
),
|
||||
has_line_numbers: false,
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(path, worktree_path),
|
||||
diffs,
|
||||
changes,
|
||||
}
|
||||
}
|
||||
AmpToolData::Bash { command, .. } => ActionType::CommandRun {
|
||||
|
||||
@@ -7,14 +7,18 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use ts_rs::TS;
|
||||
use utils::{
|
||||
log_msg::LogMsg, msg_store::MsgStore, path::make_path_relative, shell::get_shell_command,
|
||||
diff::{concatenate_diff_hunks, create_unified_diff, create_unified_diff_hunk},
|
||||
log_msg::LogMsg,
|
||||
msg_store::MsgStore,
|
||||
path::make_path_relative,
|
||||
shell::get_shell_command,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::CommandBuilder,
|
||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
||||
logs::{
|
||||
ActionType, EditDiff, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
stderr_processor::normalize_stderr_logs,
|
||||
utils::{EntryIndexProvider, patch::ConversationPatch},
|
||||
},
|
||||
@@ -434,28 +438,32 @@ impl ClaudeLogProcessor {
|
||||
old_string,
|
||||
new_string,
|
||||
} => {
|
||||
let diffs = if old_string.is_some() || new_string.is_some() {
|
||||
vec![EditDiff::Replace {
|
||||
old: old_string.clone().unwrap_or_default(),
|
||||
new: new_string.clone().unwrap_or_default(),
|
||||
let changes = if old_string.is_some() || new_string.is_some() {
|
||||
vec![FileChange::Edit {
|
||||
unified_diff: create_unified_diff(
|
||||
file_path,
|
||||
&old_string.clone().unwrap_or_default(),
|
||||
&new_string.clone().unwrap_or_default(),
|
||||
),
|
||||
has_line_numbers: false,
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(file_path, worktree_path),
|
||||
diffs,
|
||||
changes,
|
||||
}
|
||||
}
|
||||
ClaudeToolData::MultiEdit { file_path, edits } => {
|
||||
let diffs = edits
|
||||
let hunks: Vec<String> = edits
|
||||
.iter()
|
||||
.filter_map(|edit| {
|
||||
if edit.old_string.is_some() || edit.new_string.is_some() {
|
||||
Some(EditDiff::Replace {
|
||||
old: edit.old_string.clone().unwrap_or_default(),
|
||||
new: edit.new_string.clone().unwrap_or_default(),
|
||||
})
|
||||
Some(create_unified_diff_hunk(
|
||||
&edit.old_string.clone().unwrap_or_default(),
|
||||
&edit.new_string.clone().unwrap_or_default(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -463,17 +471,19 @@ impl ClaudeLogProcessor {
|
||||
.collect();
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(file_path, worktree_path),
|
||||
diffs,
|
||||
changes: vec![FileChange::Edit {
|
||||
unified_diff: concatenate_diff_hunks(file_path, &hunks),
|
||||
has_line_numbers: false,
|
||||
}],
|
||||
}
|
||||
}
|
||||
ClaudeToolData::Write { file_path, content } => {
|
||||
let diffs = vec![EditDiff::Replace {
|
||||
old: String::new(),
|
||||
new: content.clone(),
|
||||
let diffs = vec![FileChange::Write {
|
||||
content: content.clone(),
|
||||
}];
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(file_path, worktree_path),
|
||||
diffs,
|
||||
changes: diffs,
|
||||
}
|
||||
}
|
||||
ClaudeToolData::Bash { command, .. } => ActionType::CommandRun {
|
||||
@@ -503,7 +513,7 @@ impl ClaudeLogProcessor {
|
||||
}
|
||||
ClaudeToolData::NotebookEdit { notebook_path, .. } => ActionType::FileEdit {
|
||||
path: make_path_relative(notebook_path, worktree_path),
|
||||
diffs: vec![],
|
||||
changes: vec![],
|
||||
},
|
||||
ClaudeToolData::TodoWrite { todos } => ActionType::TodoManagement {
|
||||
todos: todos
|
||||
|
||||
@@ -7,13 +7,18 @@ use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use ts_rs::TS;
|
||||
use utils::{msg_store::MsgStore, path::make_path_relative, shell::get_shell_command};
|
||||
use utils::{
|
||||
diff::{concatenate_diff_hunks, extract_unified_diff_hunks},
|
||||
msg_store::MsgStore,
|
||||
path::make_path_relative,
|
||||
shell::get_shell_command,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::CommandBuilder,
|
||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
||||
logs::{
|
||||
ActionType, EditDiff, NormalizedEntry, NormalizedEntryType,
|
||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType,
|
||||
utils::{EntryIndexProvider, patch::ConversationPatch},
|
||||
},
|
||||
};
|
||||
@@ -342,7 +347,7 @@ pub enum CodexMsgContent {
|
||||
PatchApplyBegin {
|
||||
call_id: Option<String>,
|
||||
auto_approved: Option<bool>,
|
||||
changes: std::collections::HashMap<String, FileChange>,
|
||||
changes: std::collections::HashMap<String, CodexFileChange>,
|
||||
},
|
||||
|
||||
#[serde(rename = "patch_apply_end")]
|
||||
@@ -389,7 +394,7 @@ pub enum CodexMsgContent {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FileChange {
|
||||
pub enum CodexFileChange {
|
||||
Add {
|
||||
content: String,
|
||||
},
|
||||
@@ -471,25 +476,39 @@ impl CodexJson {
|
||||
make_path_relative(file_path, ¤t_dir.to_string_lossy());
|
||||
|
||||
// Try to extract unified diff from change data
|
||||
let mut diffs = vec![];
|
||||
let mut changes = vec![];
|
||||
|
||||
match change_data {
|
||||
FileChange::Update { unified_diff, .. } => {
|
||||
CodexFileChange::Update {
|
||||
unified_diff,
|
||||
move_path,
|
||||
} => {
|
||||
let mut new_path = relative_path.clone();
|
||||
|
||||
if let Some(move_path) = move_path {
|
||||
new_path = make_path_relative(
|
||||
&move_path.to_string_lossy(),
|
||||
¤t_dir.to_string_lossy(),
|
||||
);
|
||||
changes.push(FileChange::Rename {
|
||||
new_path: new_path.clone(),
|
||||
});
|
||||
}
|
||||
if !unified_diff.is_empty() {
|
||||
diffs.push(EditDiff::Unified {
|
||||
unified_diff: unified_diff.clone(),
|
||||
let hunks = extract_unified_diff_hunks(unified_diff);
|
||||
changes.push(FileChange::Edit {
|
||||
unified_diff: concatenate_diff_hunks(&new_path, &hunks),
|
||||
has_line_numbers: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
FileChange::Add { content } => {
|
||||
// For new files, we could show the content as a diff
|
||||
diffs.push(EditDiff::Replace {
|
||||
old: String::new(),
|
||||
new: content.clone(),
|
||||
CodexFileChange::Add { content } => {
|
||||
changes.push(FileChange::Write {
|
||||
content: content.clone(),
|
||||
});
|
||||
}
|
||||
FileChange::Delete => {
|
||||
// For deletions, we don't have old content to show
|
||||
CodexFileChange::Delete => {
|
||||
changes.push(FileChange::Delete);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -499,7 +518,7 @@ impl CodexJson {
|
||||
tool_name: "edit".to_string(),
|
||||
action_type: ActionType::FileEdit {
|
||||
path: relative_path.clone(),
|
||||
diffs,
|
||||
changes,
|
||||
},
|
||||
},
|
||||
content: relative_path,
|
||||
|
||||
@@ -7,13 +7,21 @@ use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use ts_rs::TS;
|
||||
use utils::{msg_store::MsgStore, path::make_path_relative, shell::get_shell_command};
|
||||
use utils::{
|
||||
diff::{
|
||||
concatenate_diff_hunks, create_unified_diff, create_unified_diff_hunk,
|
||||
extract_unified_diff_hunks,
|
||||
},
|
||||
msg_store::MsgStore,
|
||||
path::make_path_relative,
|
||||
shell::get_shell_command,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::CommandBuilder,
|
||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
||||
logs::{
|
||||
ActionType, EditDiff, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
plain_text_processor::PlainTextLogProcessor,
|
||||
utils::{ConversationPatch, EntryIndexProvider},
|
||||
},
|
||||
@@ -480,39 +488,50 @@ impl CursorToolCall {
|
||||
(
|
||||
ActionType::FileEdit {
|
||||
path: path.clone(),
|
||||
diffs: vec![],
|
||||
changes: vec![],
|
||||
},
|
||||
format!("`{path}`"),
|
||||
)
|
||||
}
|
||||
CursorToolCall::Edit { args, .. } => {
|
||||
let path = make_path_relative(&args.path, worktree_path);
|
||||
let mut diffs = vec![];
|
||||
let mut changes = vec![];
|
||||
|
||||
if let Some(_apply_patch) = &args.apply_patch {
|
||||
// todo: handle v4a
|
||||
if let Some(apply_patch) = &args.apply_patch {
|
||||
let hunks = extract_unified_diff_hunks(&apply_patch.patch_content);
|
||||
changes.push(FileChange::Edit {
|
||||
unified_diff: concatenate_diff_hunks(&path, &hunks),
|
||||
has_line_numbers: false,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(str_replace) = &args.str_replace {
|
||||
diffs.push(EditDiff::Replace {
|
||||
old: str_replace.old_text.clone(),
|
||||
new: str_replace.new_text.clone(),
|
||||
changes.push(FileChange::Edit {
|
||||
unified_diff: create_unified_diff(
|
||||
&path,
|
||||
&str_replace.old_text,
|
||||
&str_replace.new_text,
|
||||
),
|
||||
has_line_numbers: false,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(multi_str_replace) = &args.multi_str_replace {
|
||||
for edit in multi_str_replace.edits.iter() {
|
||||
diffs.push(EditDiff::Replace {
|
||||
old: edit.old_text.clone(),
|
||||
new: edit.new_text.clone(),
|
||||
});
|
||||
}
|
||||
let hunks: Vec<String> = multi_str_replace
|
||||
.edits
|
||||
.iter()
|
||||
.map(|edit| create_unified_diff_hunk(&edit.old_text, &edit.new_text))
|
||||
.collect();
|
||||
changes.push(FileChange::Edit {
|
||||
unified_diff: concatenate_diff_hunks(&path, &hunks),
|
||||
has_line_numbers: false,
|
||||
});
|
||||
}
|
||||
|
||||
(
|
||||
ActionType::FileEdit {
|
||||
path: path.clone(),
|
||||
diffs,
|
||||
changes,
|
||||
},
|
||||
format!("`{path}`"),
|
||||
)
|
||||
@@ -522,7 +541,7 @@ impl CursorToolCall {
|
||||
(
|
||||
ActionType::FileEdit {
|
||||
path: path.clone(),
|
||||
diffs: vec![],
|
||||
changes: vec![],
|
||||
},
|
||||
format!("`{path}`"),
|
||||
)
|
||||
|
||||
@@ -9,13 +9,16 @@ use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use ts_rs::TS;
|
||||
use utils::{msg_store::MsgStore, path::make_path_relative, shell::get_shell_command};
|
||||
use utils::{
|
||||
diff::create_unified_diff, msg_store::MsgStore, path::make_path_relative,
|
||||
shell::get_shell_command,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::CommandBuilder,
|
||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
||||
logs::{
|
||||
ActionType, EditDiff, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||
plain_text_processor::{MessageBoundary, PlainTextLogProcessor},
|
||||
utils::EntryIndexProvider,
|
||||
},
|
||||
@@ -679,16 +682,14 @@ impl ToolUtils {
|
||||
Tool::Write {
|
||||
file_path, content, ..
|
||||
} => {
|
||||
let diffs = match content {
|
||||
Some(content) => vec![EditDiff::Replace {
|
||||
old: String::new(),
|
||||
new: content.clone(),
|
||||
}],
|
||||
None => Vec::new(),
|
||||
let changes = if let Some(content) = content.clone() {
|
||||
vec![FileChange::Write { content }]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(file_path, worktree_path),
|
||||
diffs,
|
||||
changes,
|
||||
}
|
||||
}
|
||||
Tool::Edit {
|
||||
@@ -697,16 +698,16 @@ impl ToolUtils {
|
||||
new_string,
|
||||
..
|
||||
} => {
|
||||
let diffs = match (old_string, new_string) {
|
||||
(Some(old), Some(new)) => vec![EditDiff::Replace {
|
||||
old: old.clone(),
|
||||
new: new.clone(),
|
||||
let changes = match (old_string, new_string) {
|
||||
(Some(old), Some(new)) => vec![FileChange::Edit {
|
||||
unified_diff: create_unified_diff(file_path, old, new),
|
||||
has_line_numbers: false,
|
||||
}],
|
||||
_ => Vec::new(),
|
||||
};
|
||||
ActionType::FileEdit {
|
||||
path: make_path_relative(file_path, worktree_path),
|
||||
diffs,
|
||||
changes,
|
||||
}
|
||||
}
|
||||
Tool::Bash { command, .. } => ActionType::CommandRun {
|
||||
|
||||
@@ -5,14 +5,6 @@ pub mod plain_text_processor;
|
||||
pub mod stderr_processor;
|
||||
pub mod utils;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "format", rename_all = "snake_case")]
|
||||
pub enum EditDiff {
|
||||
Unified { unified_diff: String },
|
||||
Replace { old: String, new: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
pub struct NormalizedConversation {
|
||||
pub entries: Vec<NormalizedEntry>,
|
||||
@@ -64,7 +56,7 @@ pub enum ActionType {
|
||||
},
|
||||
FileEdit {
|
||||
path: String,
|
||||
diffs: Vec<EditDiff>,
|
||||
changes: Vec<FileChange>,
|
||||
},
|
||||
CommandRun {
|
||||
command: String,
|
||||
@@ -89,3 +81,21 @@ pub enum ActionType {
|
||||
description: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(tag = "action", rename_all = "snake_case")]
|
||||
pub enum FileChange {
|
||||
/// Create a file if it doesn't exist, and overwrite its content.
|
||||
Write { content: String },
|
||||
/// Delete a file.
|
||||
Delete,
|
||||
/// Rename a file.
|
||||
Rename { new_path: String },
|
||||
/// Edit a file with a unified diff.
|
||||
Edit {
|
||||
/// Unified diff containing file header and hunks.
|
||||
unified_diff: String,
|
||||
/// Whether line number in the hunks are reliable.
|
||||
has_line_numbers: bool,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user