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,147 @@
|
||||
use std::fs;
|
||||
use serde_json::{json, Value};
|
||||
use anyhow::{Result, anyhow};
|
||||
use ignore::Walk;
|
||||
use globset::{GlobBuilder, GlobSetBuilder};
|
||||
use super::Tool;
|
||||
use super::ToolCtx;
|
||||
use super::resolve_path;
|
||||
|
||||
pub struct Grep;
|
||||
|
||||
impl Tool for Grep {
|
||||
fn name(&self) -> &'static str {
|
||||
"grep"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Search for a pattern in files using recursive text search"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"description": "Text pattern to search for"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to search in (relative to workspace root)"
|
||||
}
|
||||
},
|
||||
"required": ["pattern", "path"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let pattern = args.get("pattern")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: pattern"))?
|
||||
.to_string();
|
||||
let rel = args.get("path")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: path"))?
|
||||
.to_string();
|
||||
let path = resolve_path(&ctx.workspaces, &rel)?;
|
||||
if !path.exists() {
|
||||
anyhow::bail!("path '{}' does not exist", rel);
|
||||
}
|
||||
if !path.is_dir() {
|
||||
anyhow::bail!("path '{}' is not a directory", rel);
|
||||
}
|
||||
let mut results: Vec<(String, usize, String)> = Vec::new();
|
||||
for entry in Walk::new(&path).flatten() {
|
||||
let file_path = entry.path();
|
||||
if !file_path.is_file() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(content) = fs::read_to_string(file_path) {
|
||||
for (i, line) in content.lines().enumerate() {
|
||||
if line.contains(&pattern) {
|
||||
let rel_path = file_path.strip_prefix(&path)
|
||||
.unwrap_or(file_path)
|
||||
.display()
|
||||
.to_string();
|
||||
results.push((rel_path, i + 1, line.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if results.is_empty() {
|
||||
return Ok(format!("no matches found for '{}' in {}", pattern, rel));
|
||||
}
|
||||
let output = results.iter()
|
||||
.map(|(f, line, text)| format!("{}:{}:{}", f, line, text))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Ok(format!("found {} matches:\n{}", results.len(), output))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Glob;
|
||||
|
||||
impl Tool for Glob {
|
||||
fn name(&self) -> &'static str {
|
||||
"glob"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"List files matching a glob pattern"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"description": "Glob pattern to match files (e.g. '**/*.rs')"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Root path to search from (relative to workspace root)"
|
||||
}
|
||||
},
|
||||
"required": ["pattern", "path"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let pat_str = args.get("pattern")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: pattern"))?
|
||||
.to_string();
|
||||
let rel = args.get("path")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: path"))?
|
||||
.to_string();
|
||||
let root = resolve_path(&ctx.workspaces, &rel)?;
|
||||
if !root.exists() || !root.is_dir() {
|
||||
anyhow::bail!("path '{}' is not a valid directory", rel);
|
||||
}
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
let full_pattern = root.join(&pat_str).display().to_string();
|
||||
builder.add(GlobBuilder::new(&full_pattern).build()
|
||||
.map_err(|e| anyhow!("invalid glob pattern '{}': {}", pat_str, e))?);
|
||||
let glob_set = builder.build()
|
||||
.map_err(|e| anyhow!("failed to build glob set: {}", e))?;
|
||||
let mut matches: Vec<String> = Vec::new();
|
||||
for entry in Walk::new(&root).flatten() {
|
||||
let p = entry.path();
|
||||
if glob_set.is_match(p) {
|
||||
let rel_path = p.strip_prefix(&root)
|
||||
.unwrap_or(p)
|
||||
.display()
|
||||
.to_string();
|
||||
matches.push(format!("{}{}", rel_path, if p.is_dir() { "/" } else { "" }));
|
||||
}
|
||||
}
|
||||
matches.sort();
|
||||
if matches.is_empty() {
|
||||
return Ok(format!("no files match '{}' in {}", pat_str, rel));
|
||||
}
|
||||
Ok(matches.join("\n"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user