Implement chat and markdown views, enhance status bar, and add workflow panel
- Added `chat.rs` for rendering chat messages with timestamps and roles. - Introduced `markdown.rs` for rendering markdown content with styling. - Created `status.rs` to display the application status bar with session and message counts. - Developed `workflow.rs` to show the current workflow status, including tool calls and active jobs. - Established a `theme.rs` for centralized color management across the UI. - Updated `mod.rs` to include new modules and manage rendering logic.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use serde_json::{json, Value};
|
||||
use anyhow::{Result, anyhow};
|
||||
use super::super::Tool;
|
||||
use super::super::ToolCtx;
|
||||
use super::super::resolve_path;
|
||||
use super::helpers::arg_str;
|
||||
|
||||
pub struct Edit;
|
||||
|
||||
impl Tool for Edit {
|
||||
fn name(&self) -> &'static str {
|
||||
"edit"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Replace a string in a file with a new string. The old string must be unique unless replace_all is true."
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the file to edit (relative to workspace root)"
|
||||
},
|
||||
"old": {
|
||||
"type": "string",
|
||||
"description": "The exact text to replace"
|
||||
},
|
||||
"new": {
|
||||
"type": "string",
|
||||
"description": "The replacement text"
|
||||
},
|
||||
"replace_all": {
|
||||
"type": "boolean",
|
||||
"description": "Replace all occurrences instead of requiring uniqueness"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Reason for the change (must be non-empty)"
|
||||
}
|
||||
},
|
||||
"required": ["path", "old", "new", "reason"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let rel = arg_str(args, "path")?;
|
||||
let old = arg_str(args, "old")?;
|
||||
let new_str = arg_str(args, "new")?;
|
||||
let reason = arg_str(args, "reason")?;
|
||||
if reason.trim().is_empty() {
|
||||
anyhow::bail!("reason must be a non-empty string");
|
||||
}
|
||||
let replace_all = args.get("replace_all").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let path: PathBuf = resolve_path(&ctx.workspaces, &rel)?;
|
||||
if !path.exists() {
|
||||
anyhow::bail!("file '{}' does not exist at resolved path {}", rel, path.display());
|
||||
}
|
||||
if path.is_dir() {
|
||||
anyhow::bail!("'{}' is a directory, not a file", rel);
|
||||
}
|
||||
let content = fs::read_to_string(&path)
|
||||
.map_err(|e| anyhow!("failed to read '{}': {}", rel, e))?;
|
||||
if !content.contains(&old) {
|
||||
anyhow::bail!("old string not found in '{}'", rel);
|
||||
}
|
||||
if !replace_all {
|
||||
let count = content.matches(&old).count();
|
||||
if count > 1 {
|
||||
anyhow::bail!(
|
||||
"old string appears {} times in '{}'. Set replace_all=true to replace all occurrences, or provide a more specific match.",
|
||||
count, rel
|
||||
);
|
||||
}
|
||||
}
|
||||
let new_content = if replace_all {
|
||||
content.replace(&old, &new_str)
|
||||
} else {
|
||||
content.replacen(&old, &new_str, 1)
|
||||
};
|
||||
fs::write(&path, &new_content)
|
||||
.map_err(|e| anyhow!("failed to write '{}': {}", rel, e))?;
|
||||
let bytes_diff = if new_content.len() > content.len() {
|
||||
new_content.len() - content.len()
|
||||
} else {
|
||||
content.len() - new_content.len()
|
||||
};
|
||||
Ok(format!("edited {} ({} byte delta)", rel, bytes_diff as isize))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user