Add worktrees, per-hunk review, effort per message, regenerate, voice input
- Isolated git worktree per session (create, status, merge, discard) - Permission card: accept file-edit hunks individually; agent learns what was rejected - Reasoning effort per message; regenerate the last answer with another model/effort - Voice input via the Web Speech API - Folder-up button in the file browser; ssh, rsync and mc in the image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df1924b0a1
commit
fca1c41087
22 files changed
+821
-32
No files matched your search
+38
-1
@@ -22,6 +22,27 @@ fn image_type(path: &Path) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply only the selected hunks (indices of the 3-line-context groups, as in
|
||||
/// `unified_diff`) of the change old -> new. Returns (result, total hunks).
|
||||
pub fn apply_hunks(old: &str, new: &str, selected: &[usize]) -> (String, usize) {
|
||||
let diff = similar::TextDiff::from_lines(old, new);
|
||||
let groups = diff.grouped_ops(3);
|
||||
let total = groups.len();
|
||||
let mut out = String::new();
|
||||
for op in diff.ops() {
|
||||
let in_selected = op.tag() != similar::DiffTag::Equal && groups.iter().enumerate().any(|(i, g)| selected.contains(&i) && g.contains(op));
|
||||
let lines: Vec<&str> = if in_selected {
|
||||
diff.new_slices()[op.new_range()].to_vec()
|
||||
} else {
|
||||
diff.old_slices()[op.old_range()].to_vec()
|
||||
};
|
||||
for l in lines {
|
||||
out.push_str(l);
|
||||
}
|
||||
}
|
||||
(out, total)
|
||||
}
|
||||
|
||||
pub fn unified_diff(path: &str, old: &str, new: &str) -> String {
|
||||
similar::TextDiff::from_lines(old, new)
|
||||
.unified_diff()
|
||||
@@ -235,7 +256,7 @@ impl Tool for Edit {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_edit(old: &str, input: &Value) -> Result<String> {
|
||||
pub fn apply_edit(old: &str, input: &Value) -> Result<String> {
|
||||
let from = arg_str(input, "old_string")?;
|
||||
let to = arg_str(input, "new_string")?;
|
||||
if from.is_empty() {
|
||||
@@ -484,3 +505,19 @@ mod tests {
|
||||
assert!(apply_edit("xyz", &input).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod hunk_tests {
|
||||
#[test]
|
||||
fn partial_apply() {
|
||||
let old: String = (1..=30).map(|i| format!("line {i}\n")).collect();
|
||||
let new = old.replace("line 2\n", "line TWO\n").replace("line 25\n", "line 25\nextra\n");
|
||||
let (only_first, total) = super::apply_hunks(&old, &new, &[0]);
|
||||
assert_eq!(total, 2);
|
||||
assert!(only_first.contains("line TWO") && !only_first.contains("extra"));
|
||||
let (only_second, _) = super::apply_hunks(&old, &new, &[1]);
|
||||
assert!(!only_second.contains("line TWO") && only_second.contains("extra"));
|
||||
assert_eq!(super::apply_hunks(&old, &new, &[0, 1]).0, new);
|
||||
assert_eq!(super::apply_hunks(&old, &new, &[]).0, old);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user