feat(cli): shorten file paths in tool output display
Add three-level path shortening hierarchy for cleaner CLI output:
1. Project path -> <project_name>/... (when project loaded via /project)
2. Workspace path -> ./... (relative to current working directory)
3. Home path -> ~/... (fallback for paths under home directory)
Changes:
- Add shorten_path() and shorten_paths_in_command() functions in display.rs
- Add project_path/project_name fields to ConsoleUiWriter
- Add set_workspace_path(), set_project_path(), clear_project() to UiWriter trait
- Add ui_writer() getter to Agent struct
- Wire up project path setting in /project and /unproject commands
- Set workspace path when creating agents in all CLI modes
Before: ● read_file | /Users/dhanji/icloud/butler/projects/appa_estate/status.md
After: ● read_file | appa_estate/status.md (with project loaded)
● read_file | ./src/main.rs (workspace-relative)
● read_file | ~/Documents/file.txt (home-relative)
This commit is contained in:
@@ -15,6 +15,7 @@ use crate::cli_args::Cli;
|
|||||||
use crate::interactive::run_interactive;
|
use crate::interactive::run_interactive;
|
||||||
use crate::simple_output::SimpleOutput;
|
use crate::simple_output::SimpleOutput;
|
||||||
use crate::ui_writer_impl::ConsoleUiWriter;
|
use crate::ui_writer_impl::ConsoleUiWriter;
|
||||||
|
use g3_core::ui_writer::UiWriter;
|
||||||
use crate::utils::load_config_with_cli_overrides;
|
use crate::utils::load_config_with_cli_overrides;
|
||||||
|
|
||||||
/// Run accumulative autonomous mode - accumulates requirements from user input
|
/// Run accumulative autonomous mode - accumulates requirements from user input
|
||||||
@@ -153,6 +154,7 @@ pub async fn run_accumulative_mode(
|
|||||||
|
|
||||||
// Create agent for this autonomous run
|
// Create agent for this autonomous run
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
|
ui_writer.set_workspace_path(workspace_dir.clone());
|
||||||
let agent = Agent::new_autonomous_with_readme_and_quiet(
|
let agent = Agent::new_autonomous_with_readme_and_quiet(
|
||||||
config.clone(),
|
config.clone(),
|
||||||
ui_writer,
|
ui_writer,
|
||||||
@@ -284,6 +286,7 @@ async fn handle_command(
|
|||||||
|
|
||||||
// Create agent for interactive mode with requirements context
|
// Create agent for interactive mode with requirements context
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
|
ui_writer.set_workspace_path(workspace_dir.clone());
|
||||||
let agent = Agent::new_with_readme_and_quiet(
|
let agent = Agent::new_with_readme_and_quiet(
|
||||||
config,
|
config,
|
||||||
ui_writer,
|
ui_writer,
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ pub async fn run_agent_mode(
|
|||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
// Set agent mode on UI writer for visual differentiation (light gray tool names)
|
// Set agent mode on UI writer for visual differentiation (light gray tool names)
|
||||||
ui_writer.set_agent_mode(true);
|
ui_writer.set_agent_mode(true);
|
||||||
|
ui_writer.set_workspace_path(workspace_dir.clone());
|
||||||
let mut agent =
|
let mut agent =
|
||||||
Agent::new_with_custom_prompt(config, ui_writer, system_prompt, combined_content.clone()).await?;
|
Agent::new_with_custom_prompt(config, ui_writer, system_prompt, combined_content.clone()).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::coach_feedback;
|
|||||||
use crate::metrics::{format_elapsed_time, generate_turn_histogram, TurnMetrics};
|
use crate::metrics::{format_elapsed_time, generate_turn_histogram, TurnMetrics};
|
||||||
use crate::simple_output::SimpleOutput;
|
use crate::simple_output::SimpleOutput;
|
||||||
use crate::ui_writer_impl::ConsoleUiWriter;
|
use crate::ui_writer_impl::ConsoleUiWriter;
|
||||||
|
use g3_core::ui_writer::UiWriter;
|
||||||
|
|
||||||
/// Run autonomous mode with coach-player feedback loop (console output).
|
/// Run autonomous mode with coach-player feedback loop (console output).
|
||||||
pub async fn run_autonomous(
|
pub async fn run_autonomous(
|
||||||
@@ -498,6 +499,7 @@ async fn execute_coach_turn(
|
|||||||
crate::filter_json::reset_json_tool_state();
|
crate::filter_json::reset_json_tool_state();
|
||||||
|
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
|
ui_writer.set_workspace_path(project.workspace().to_path_buf());
|
||||||
let mut coach_agent =
|
let mut coach_agent =
|
||||||
match Agent::new_autonomous_with_readme_and_quiet(coach_config, ui_writer, None, quiet)
|
match Agent::new_autonomous_with_readme_and_quiet(coach_config, ui_writer, None, quiet)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -360,6 +360,14 @@ pub async fn handle_command<W: UiWriter>(
|
|||||||
Some(project) => {
|
Some(project) => {
|
||||||
// Set project content in agent's system message
|
// Set project content in agent's system message
|
||||||
if agent.set_project_content(Some(project.content.clone())) {
|
if agent.set_project_content(Some(project.content.clone())) {
|
||||||
|
// Set project path on UI writer for path shortening
|
||||||
|
let project_name = project.path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("project")
|
||||||
|
.to_string();
|
||||||
|
agent.ui_writer().set_project_path(project.path.clone(), project_name);
|
||||||
|
|
||||||
// Print loaded status
|
// Print loaded status
|
||||||
print!(
|
print!(
|
||||||
"{}Project loaded:{} {}\n",
|
"{}Project loaded:{} {}\n",
|
||||||
@@ -388,6 +396,7 @@ pub async fn handle_command<W: UiWriter>(
|
|||||||
"/unproject" => {
|
"/unproject" => {
|
||||||
if active_project.is_some() {
|
if active_project.is_some() {
|
||||||
agent.clear_project_content();
|
agent.clear_project_content();
|
||||||
|
agent.ui_writer().clear_project();
|
||||||
*active_project = None;
|
*active_project = None;
|
||||||
output.print("✅ Project unloaded. Context reset to original system message.");
|
output.print("✅ Project unloaded. Context reset to original system message.");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,6 +17,83 @@ pub fn format_workspace_path(workspace_path: &Path) -> String {
|
|||||||
.unwrap_or(path_str)
|
.unwrap_or(path_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorten a path string for display by:
|
||||||
|
/// 1. Replacing project directory prefix with `<project_name>/` (if project is active)
|
||||||
|
/// 2. Replacing workspace directory prefix with `./`
|
||||||
|
/// 3. Replacing home directory prefix with `~`
|
||||||
|
///
|
||||||
|
/// This is useful for tool output where paths should be concise.
|
||||||
|
/// The project check happens first (most specific), then workspace, then home.
|
||||||
|
pub fn shorten_path(path: &str, workspace_path: Option<&std::path::Path>, project: Option<(&std::path::Path, &str)>) -> String {
|
||||||
|
// First, try to make it relative to project (most specific)
|
||||||
|
if let Some((project_path, project_name)) = project {
|
||||||
|
let project_str = project_path.display().to_string();
|
||||||
|
if let Some(relative) = path.strip_prefix(&project_str) {
|
||||||
|
// Handle both "/subpath" and "" (exact match) cases
|
||||||
|
if relative.is_empty() {
|
||||||
|
return format!("{}/", project_name);
|
||||||
|
} else if let Some(stripped) = relative.strip_prefix('/') {
|
||||||
|
return format!("{}/{}", project_name, stripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, try to make it relative to workspace
|
||||||
|
if let Some(workspace) = workspace_path {
|
||||||
|
let workspace_str = workspace.display().to_string();
|
||||||
|
if let Some(relative) = path.strip_prefix(&workspace_str) {
|
||||||
|
// Handle both "/subpath" and "" (exact match) cases
|
||||||
|
if relative.is_empty() {
|
||||||
|
return "./".to_string();
|
||||||
|
} else if let Some(stripped) = relative.strip_prefix('/') {
|
||||||
|
return format!("./{}", stripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to replacing home directory with ~
|
||||||
|
if let Some(home) = dirs::home_dir() {
|
||||||
|
let home_str = home.display().to_string();
|
||||||
|
if let Some(relative) = path.strip_prefix(&home_str) {
|
||||||
|
return format!("~{}", relative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shorten any paths found within a shell command string.
|
||||||
|
/// This replaces project paths with `<project_name>/`, workspace paths with `./`, and home paths with `~`.
|
||||||
|
pub fn shorten_paths_in_command(command: &str, workspace_path: Option<&std::path::Path>, project: Option<(&std::path::Path, &str)>) -> String {
|
||||||
|
let mut result = command.to_string();
|
||||||
|
|
||||||
|
// First, replace project paths (most specific)
|
||||||
|
if let Some((project_path, project_name)) = project {
|
||||||
|
let project_str = project_path.display().to_string();
|
||||||
|
// Replace project path followed by / with project_name/
|
||||||
|
result = result.replace(&format!("{}/", project_str), &format!("{}/", project_name));
|
||||||
|
// Replace exact project path
|
||||||
|
result = result.replace(&project_str, project_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, replace workspace paths
|
||||||
|
if let Some(workspace) = workspace_path {
|
||||||
|
let workspace_str = workspace.display().to_string();
|
||||||
|
// Replace workspace path followed by / with ./
|
||||||
|
result = result.replace(&format!("{}/", workspace_str), "./");
|
||||||
|
// Replace exact workspace path at word boundary
|
||||||
|
result = result.replace(&workspace_str, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then replace home directory paths
|
||||||
|
if let Some(home) = dirs::home_dir() {
|
||||||
|
let home_str = home.display().to_string();
|
||||||
|
result = result.replace(&home_str, "~");
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Print the workspace path in a consistent format.
|
/// Print the workspace path in a consistent format.
|
||||||
pub fn print_workspace_path(workspace_path: &Path) {
|
pub fn print_workspace_path(workspace_path: &Path) {
|
||||||
let display = format_workspace_path(workspace_path);
|
let display = format_workspace_path(workspace_path);
|
||||||
@@ -195,4 +272,91 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert!(with_readme.has_any());
|
assert!(with_readme.has_any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_workspace_relative() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects/myapp");
|
||||||
|
let path = "/Users/test/projects/myapp/src/main.rs";
|
||||||
|
let shortened = shorten_path(path, Some(&workspace), None);
|
||||||
|
assert_eq!(shortened, "./src/main.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_workspace_exact() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects/myapp");
|
||||||
|
let path = "/Users/test/projects/myapp";
|
||||||
|
let shortened = shorten_path(path, Some(&workspace), None);
|
||||||
|
assert_eq!(shortened, "./");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_home_relative() {
|
||||||
|
// This test depends on having a home directory
|
||||||
|
if let Some(home) = dirs::home_dir() {
|
||||||
|
let path = format!("{}/other/project/file.rs", home.display());
|
||||||
|
let shortened = shorten_path(&path, None, None);
|
||||||
|
assert_eq!(shortened, "~/other/project/file.rs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_no_match() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects/myapp");
|
||||||
|
let path = "/tmp/other/file.rs";
|
||||||
|
let shortened = shorten_path(path, Some(&workspace), None);
|
||||||
|
assert_eq!(shortened, "/tmp/other/file.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_project_relative() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects");
|
||||||
|
let project_path = PathBuf::from("/Users/test/projects/appa_estate");
|
||||||
|
let path = "/Users/test/projects/appa_estate/status.md";
|
||||||
|
let shortened = shorten_path(path, Some(&workspace), Some((&project_path, "appa_estate")));
|
||||||
|
assert_eq!(shortened, "appa_estate/status.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_path_project_takes_priority() {
|
||||||
|
// Project path is under workspace, but project shortening should take priority
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects");
|
||||||
|
let project_path = PathBuf::from("/Users/test/projects/appa_estate");
|
||||||
|
let path = "/Users/test/projects/appa_estate/src/main.rs";
|
||||||
|
let shortened = shorten_path(path, Some(&workspace), Some((&project_path, "appa_estate")));
|
||||||
|
assert_eq!(shortened, "appa_estate/src/main.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_paths_in_command_workspace() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects/myapp");
|
||||||
|
let command = "cat /Users/test/projects/myapp/src/main.rs";
|
||||||
|
let shortened = shorten_paths_in_command(command, Some(&workspace), None);
|
||||||
|
assert_eq!(shortened, "cat ./src/main.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_paths_in_command_home() {
|
||||||
|
if let Some(home) = dirs::home_dir() {
|
||||||
|
let command = format!("ls {}/Documents", home.display());
|
||||||
|
let shortened = shorten_paths_in_command(&command, None, None);
|
||||||
|
assert_eq!(shortened, "ls ~/Documents");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_paths_in_command_multiple() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects/myapp");
|
||||||
|
let command = "diff /Users/test/projects/myapp/a.rs /Users/test/projects/myapp/b.rs";
|
||||||
|
let shortened = shorten_paths_in_command(command, Some(&workspace), None);
|
||||||
|
assert_eq!(shortened, "diff ./a.rs ./b.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shorten_paths_in_command_project() {
|
||||||
|
let workspace = PathBuf::from("/Users/test/projects");
|
||||||
|
let project_path = PathBuf::from("/Users/test/projects/appa_estate");
|
||||||
|
let command = "cat /Users/test/projects/appa_estate/status.md";
|
||||||
|
let shortened = shorten_paths_in_command(command, Some(&workspace), Some((&project_path, "appa_estate")));
|
||||||
|
assert_eq!(shortened, "cat appa_estate/status.md");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ use interactive::run_interactive;
|
|||||||
use project_files::{combine_project_content, read_agents_config, read_include_prompt, read_workspace_memory, read_project_readme};
|
use project_files::{combine_project_content, read_agents_config, read_include_prompt, read_workspace_memory, read_project_readme};
|
||||||
use simple_output::SimpleOutput;
|
use simple_output::SimpleOutput;
|
||||||
use ui_writer_impl::ConsoleUiWriter;
|
use ui_writer_impl::ConsoleUiWriter;
|
||||||
|
use g3_core::ui_writer::UiWriter;
|
||||||
use utils::{initialize_logging, load_config_with_cli_overrides, setup_workspace_directory};
|
use utils::{initialize_logging, load_config_with_cli_overrides, setup_workspace_directory};
|
||||||
use template::process_template;
|
use template::process_template;
|
||||||
|
|
||||||
@@ -167,6 +168,7 @@ async fn run_console_mode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
|
ui_writer.set_workspace_path(workspace_dir.clone());
|
||||||
|
|
||||||
let mut agent = if cli.autonomous {
|
let mut agent = if cli.autonomous {
|
||||||
Agent::new_autonomous_with_readme_and_quiet(
|
Agent::new_autonomous_with_readme_and_quiet(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::filter_json::{filter_json_tool_calls, reset_json_tool_state, ToolParsingHint};
|
use crate::filter_json::{filter_json_tool_calls, reset_json_tool_state, ToolParsingHint};
|
||||||
|
use crate::display::{shorten_path, shorten_paths_in_command};
|
||||||
use crate::streaming_markdown::StreamingMarkdownFormatter;
|
use crate::streaming_markdown::StreamingMarkdownFormatter;
|
||||||
use g3_core::ui_writer::UiWriter;
|
use g3_core::ui_writer::UiWriter;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
@@ -137,6 +138,12 @@ impl ParsingHintState {
|
|||||||
pub struct ConsoleUiWriter {
|
pub struct ConsoleUiWriter {
|
||||||
current_tool_name: std::sync::Mutex<Option<String>>,
|
current_tool_name: std::sync::Mutex<Option<String>>,
|
||||||
current_tool_args: std::sync::Mutex<Vec<(String, String)>>,
|
current_tool_args: std::sync::Mutex<Vec<(String, String)>>,
|
||||||
|
/// Workspace path for shortening displayed paths
|
||||||
|
workspace_path: std::sync::Mutex<Option<std::path::PathBuf>>,
|
||||||
|
/// Project path for shortening displayed paths (takes priority over workspace)
|
||||||
|
project_path: std::sync::Mutex<Option<std::path::PathBuf>>,
|
||||||
|
/// Project name for display (e.g., "appa_estate")
|
||||||
|
project_name: std::sync::Mutex<Option<String>>,
|
||||||
current_output_line: std::sync::Mutex<Option<String>>,
|
current_output_line: std::sync::Mutex<Option<String>>,
|
||||||
output_line_printed: std::sync::Mutex<bool>,
|
output_line_printed: std::sync::Mutex<bool>,
|
||||||
/// Track if we're in shell compact mode (for appending timing to output line)
|
/// Track if we're in shell compact mode (for appending timing to output line)
|
||||||
@@ -191,6 +198,9 @@ impl ConsoleUiWriter {
|
|||||||
Self {
|
Self {
|
||||||
current_tool_name: std::sync::Mutex::new(None),
|
current_tool_name: std::sync::Mutex::new(None),
|
||||||
current_tool_args: std::sync::Mutex::new(Vec::new()),
|
current_tool_args: std::sync::Mutex::new(Vec::new()),
|
||||||
|
workspace_path: std::sync::Mutex::new(None),
|
||||||
|
project_path: std::sync::Mutex::new(None),
|
||||||
|
project_name: std::sync::Mutex::new(None),
|
||||||
current_output_line: std::sync::Mutex::new(None),
|
current_output_line: std::sync::Mutex::new(None),
|
||||||
output_line_printed: std::sync::Mutex::new(false),
|
output_line_printed: std::sync::Mutex::new(false),
|
||||||
is_shell_compact: std::sync::Mutex::new(false),
|
is_shell_compact: std::sync::Mutex::new(false),
|
||||||
@@ -201,6 +211,18 @@ impl ConsoleUiWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConsoleUiWriter {
|
||||||
|
fn get_workspace_path(&self) -> Option<std::path::PathBuf> {
|
||||||
|
self.workspace_path.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_project_info(&self) -> Option<(std::path::PathBuf, String)> {
|
||||||
|
let path = self.project_path.lock().unwrap().clone()?;
|
||||||
|
let name = self.project_name.lock().unwrap().clone()?;
|
||||||
|
Some((path, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UiWriter for ConsoleUiWriter {
|
impl UiWriter for ConsoleUiWriter {
|
||||||
fn print(&self, message: &str) {
|
fn print(&self, message: &str) {
|
||||||
print!("{}", message);
|
print!("{}", message);
|
||||||
@@ -308,17 +330,28 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
// For multi-line values, only show the first line
|
// For multi-line values, only show the first line
|
||||||
let first_line = value.lines().next().unwrap_or("");
|
let first_line = value.lines().next().unwrap_or("");
|
||||||
|
|
||||||
// Truncate long values for display
|
// Get workspace path for shortening
|
||||||
let display_value = if first_line.len() > 80 {
|
let workspace = self.get_workspace_path();
|
||||||
|
let workspace_ref = workspace.as_deref();
|
||||||
|
|
||||||
|
// Get project info for shortening
|
||||||
|
let project_info = self.get_project_info();
|
||||||
|
let project_ref = project_info.as_ref().map(|(p, n)| (p.as_path(), n.as_str()));
|
||||||
|
|
||||||
|
// Shorten paths in the value (handles both file paths and shell commands)
|
||||||
|
let shortened = shorten_paths_in_command(first_line, workspace_ref, project_ref);
|
||||||
|
|
||||||
|
// Truncate long values for display (after shortening)
|
||||||
|
let display_value = if shortened.chars().count() > 80 {
|
||||||
// Use char_indices to safely truncate at character boundary
|
// Use char_indices to safely truncate at character boundary
|
||||||
let truncate_at = first_line
|
let truncate_at = shortened
|
||||||
.char_indices()
|
.char_indices()
|
||||||
.nth(77)
|
.nth(77)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.unwrap_or(first_line.len());
|
.unwrap_or(shortened.len());
|
||||||
format!("{}...", &first_line[..truncate_at])
|
format!("{}...", &shortened[..truncate_at])
|
||||||
} else {
|
} else {
|
||||||
first_line.to_string()
|
shortened
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add range information for read_file tool calls
|
// Add range information for read_file tool calls
|
||||||
@@ -500,16 +533,21 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Truncate long paths
|
// Shorten path (project -> name/, workspace -> ./, home -> ~) then truncate if still long
|
||||||
if file_path.len() > 60 {
|
let workspace = self.get_workspace_path();
|
||||||
let truncate_at = file_path
|
let project_info = self.get_project_info();
|
||||||
|
let project_ref = project_info.as_ref().map(|(p, n)| (p.as_path(), n.as_str()));
|
||||||
|
let shortened = shorten_path(file_path, workspace.as_deref(), project_ref);
|
||||||
|
|
||||||
|
if shortened.chars().count() > 60 {
|
||||||
|
let truncate_at = shortened
|
||||||
.char_indices()
|
.char_indices()
|
||||||
.nth(57)
|
.nth(57)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.unwrap_or(file_path.len());
|
.unwrap_or(shortened.len());
|
||||||
format!("{}", &file_path[..truncate_at])
|
format!("{}...", &shortened[..truncate_at])
|
||||||
} else {
|
} else {
|
||||||
file_path.to_string()
|
shortened
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -806,4 +844,18 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
fn set_agent_mode(&self, is_agent_mode: bool) {
|
fn set_agent_mode(&self, is_agent_mode: bool) {
|
||||||
self.hint_state.is_agent_mode.store(is_agent_mode, Ordering::Relaxed);
|
self.hint_state.is_agent_mode.store(is_agent_mode, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_workspace_path(&self, path: std::path::PathBuf) {
|
||||||
|
*self.workspace_path.lock().unwrap() = Some(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_project_path(&self, path: std::path::PathBuf, name: String) {
|
||||||
|
*self.project_path.lock().unwrap() = Some(path);
|
||||||
|
*self.project_name.lock().unwrap() = Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_project(&self) {
|
||||||
|
*self.project_path.lock().unwrap() = None;
|
||||||
|
*self.project_name.lock().unwrap() = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1055,6 +1055,11 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
&self.context_window
|
&self.context_window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the UI writer.
|
||||||
|
pub fn ui_writer(&self) -> &W {
|
||||||
|
&self.ui_writer
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a message directly to the context window.
|
/// Add a message directly to the context window.
|
||||||
/// Used for injecting discovery messages before the first LLM turn.
|
/// Used for injecting discovery messages before the first LLM turn.
|
||||||
pub fn add_message_to_context(&mut self, message: Message) {
|
pub fn add_message_to_context(&mut self, message: Message) {
|
||||||
|
|||||||
@@ -126,6 +126,20 @@ pub trait UiWriter: Send + Sync {
|
|||||||
/// When in agent mode, tool names may be displayed differently (e.g., different color).
|
/// When in agent mode, tool names may be displayed differently (e.g., different color).
|
||||||
/// Default implementation does nothing.
|
/// Default implementation does nothing.
|
||||||
fn set_agent_mode(&self, _is_agent_mode: bool) {}
|
fn set_agent_mode(&self, _is_agent_mode: bool) {}
|
||||||
|
|
||||||
|
/// Set the workspace path for shortening displayed paths.
|
||||||
|
/// Paths under this directory will be shown as `./relative/path`.
|
||||||
|
/// Default implementation does nothing.
|
||||||
|
fn set_workspace_path(&self, _path: std::path::PathBuf) {}
|
||||||
|
|
||||||
|
/// Set the active project path and name for shortening displayed paths.
|
||||||
|
/// Paths under this directory will be shown as `<project_name>/relative/path`.
|
||||||
|
/// Default implementation does nothing.
|
||||||
|
fn set_project_path(&self, _path: std::path::PathBuf, _name: String) {}
|
||||||
|
|
||||||
|
/// Clear the active project (when project is unloaded).
|
||||||
|
/// Default implementation does nothing.
|
||||||
|
fn clear_project(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A no-op implementation for when UI output is not needed
|
/// A no-op implementation for when UI output is not needed
|
||||||
|
|||||||
Reference in New Issue
Block a user