diff --git a/crates/agent/src/context.rs b/crates/agent/src/context.rs index fdcf0316a04eb493fb03c14d2c5891fb86ab72c5..80062b1e46f01d1740359e6832c73a2aec255031 100644 --- a/crates/agent/src/context.rs +++ b/crates/agent/src/context.rs @@ -159,7 +159,7 @@ pub struct FileContextHandle { #[derive(Debug, Clone)] pub struct FileContext { pub handle: FileContextHandle, - pub full_path: Arc, + pub full_path: String, pub text: SharedString, pub is_outline: bool, } @@ -187,7 +187,7 @@ impl FileContextHandle { log::error!("file context missing path"); return Task::ready(None); }; - let full_path: Arc = file.full_path(cx).into(); + let full_path = file.full_path(cx).to_string_lossy().to_string(); let rope = buffer_ref.as_rope().clone(); let buffer = self.buffer.clone(); @@ -236,7 +236,7 @@ pub struct DirectoryContextHandle { #[derive(Debug, Clone)] pub struct DirectoryContext { pub handle: DirectoryContextHandle, - pub full_path: Arc, + pub full_path: String, pub descendants: Vec, } @@ -274,13 +274,16 @@ impl DirectoryContextHandle { } let directory_path = entry.path.clone(); - let directory_full_path = worktree_ref.full_path(&directory_path).into(); + let directory_full_path = worktree_ref + .full_path(&directory_path) + .to_string_lossy() + .to_string(); let file_paths = collect_files_in_path(worktree_ref, &directory_path); let descendants_future = future::join_all(file_paths.into_iter().map(|path| { let worktree_ref = worktree.read(cx); let worktree_id = worktree_ref.id(); - let full_path = worktree_ref.full_path(&path); + let full_path = worktree_ref.full_path(&path).to_string_lossy().to_string(); let rel_path = path .strip_prefix(&directory_path) @@ -361,7 +364,7 @@ pub struct SymbolContextHandle { #[derive(Debug, Clone)] pub struct SymbolContext { pub handle: SymbolContextHandle, - pub full_path: Arc, + pub full_path: String, pub line_range: Range, pub text: SharedString, } @@ -400,7 +403,7 @@ impl SymbolContextHandle { log::error!("symbol context's file has no path"); return Task::ready(None); }; - let full_path = file.full_path(cx).into(); + let full_path = file.full_path(cx).to_string_lossy().to_string(); let line_range = self.enclosing_range.to_point(&buffer_ref.snapshot()); let text = self.text(cx); let buffer = self.buffer.clone(); @@ -434,7 +437,7 @@ pub struct SelectionContextHandle { #[derive(Debug, Clone)] pub struct SelectionContext { pub handle: SelectionContextHandle, - pub full_path: Arc, + pub full_path: String, pub line_range: Range, pub text: SharedString, } @@ -473,7 +476,7 @@ impl SelectionContextHandle { let text = self.text(cx); let buffer = self.buffer.clone(); let context = AgentContext::Selection(SelectionContext { - full_path: full_path.into(), + full_path: full_path.to_string_lossy().to_string(), line_range: self.line_range(cx), text, handle: self, @@ -703,7 +706,7 @@ impl Display for RulesContext { #[derive(Debug, Clone)] pub struct ImageContext { pub project_path: Option, - pub full_path: Option>, + pub full_path: Option, pub original_image: Arc, // TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml // needed due to a false positive of `clippy::mutable_key_type`. @@ -983,14 +986,17 @@ fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec>) -> String { +fn codeblock_tag(full_path: &str, line_range: Option>) -> String { let mut result = String::new(); - if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) { + if let Some(extension) = Path::new(full_path) + .extension() + .and_then(|ext| ext.to_str()) + { let _ = write!(result, "{} ", extension); } - let _ = write!(result, "{}", full_path.display()); + let _ = write!(result, "{}", full_path); if let Some(range) = line_range { if range.start.row == range.end.row { diff --git a/crates/agent/src/context_store.rs b/crates/agent/src/context_store.rs index 9b9653700005306db07e38e6b74a9d1a585fd2b2..c4aa0abc25544e943e0e91c3a95c8dd8fafad8ef 100644 --- a/crates/agent/src/context_store.rs +++ b/crates/agent/src/context_store.rs @@ -312,7 +312,7 @@ impl ContextStore { let item = image_item.read(cx); this.insert_image( Some(item.project_path(cx)), - Some(item.file.full_path(cx).into()), + Some(item.file.full_path(cx).to_string_lossy().to_string()), item.image.clone(), remove_if_exists, cx, @@ -328,7 +328,7 @@ impl ContextStore { fn insert_image( &mut self, project_path: Option, - full_path: Option>, + full_path: Option, image: Arc, remove_if_exists: bool, cx: &mut Context, diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index fcbe8978583d2b17dd9dc6c31cf369749f81ef99..ce8dcba10236aa194e8b30d3fe6855d8c5fa5148 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -225,9 +225,12 @@ impl AgentTool for ReadFileTool { Ok(result.into()) } else { // No line ranges specified, so check file size to see if it's too big. - let buffer_content = - outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), cx) - .await?; + let buffer_content = outline::get_buffer_content_or_outline( + buffer.clone(), + Some(&abs_path.to_string_lossy()), + cx, + ) + .await?; action_log.update(cx, |log, cx| { log.buffer_read(buffer.clone(), cx); diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index b37e073f8ac627a79f495a72804aa75d169571ef..dec9b0beb23d9d7d49116a30404f244b5bb1f8e8 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -452,9 +452,12 @@ impl MessageEditor { .update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(async move |_, cx| { let buffer = buffer.await?; - let buffer_content = - outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), &cx) - .await?; + let buffer_content = outline::get_buffer_content_or_outline( + buffer.clone(), + Some(&abs_path.to_string_lossy()), + &cx, + ) + .await?; Ok(Mention::Text { content: buffer_content.text, @@ -1174,14 +1177,20 @@ fn full_mention_for_directory( abs_path: &Path, cx: &mut App, ) -> Task> { - fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc, PathBuf)> { + fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc, String)> { let mut files = Vec::new(); for entry in worktree.child_entries(path) { if entry.is_dir() { files.extend(collect_files_in_path(worktree, &entry.path)); } else if entry.is_file() { - files.push((entry.path.clone(), worktree.full_path(&entry.path))); + files.push(( + entry.path.clone(), + worktree + .full_path(&entry.path) + .to_string_lossy() + .to_string(), + )); } } @@ -1259,7 +1268,7 @@ fn full_mention_for_directory( }) } -fn render_directory_contents(entries: Vec<(Arc, PathBuf, String)>) -> String { +fn render_directory_contents(entries: Vec<(Arc, String, String)>) -> String { let mut output = String::new(); for (_relative_path, full_path, content) in entries { let fence = codeblock_fence_for_path(Some(&full_path), None); diff --git a/crates/agent_ui/src/ui/context_pill.rs b/crates/agent_ui/src/ui/context_pill.rs index a015d6305e388b7e16565141f8e42232ba7ce7ac..c42d33c6d346cf869e7b27dc04c63202b3502068 100644 --- a/crates/agent_ui/src/ui/context_pill.rs +++ b/crates/agent_ui/src/ui/context_pill.rs @@ -17,6 +17,7 @@ use agent::context::{ FileContextHandle, ImageContext, ImageStatus, RulesContextHandle, SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle, }; +use util::paths::PathStyle; #[derive(IntoElement)] pub enum ContextPill { @@ -303,33 +304,54 @@ impl AddedContext { cx: &App, ) -> Option { match handle { - AgentContextHandle::File(handle) => Self::pending_file(handle, cx), + AgentContextHandle::File(handle) => { + Self::pending_file(handle, project.path_style(cx), cx) + } AgentContextHandle::Directory(handle) => Self::pending_directory(handle, project, cx), - AgentContextHandle::Symbol(handle) => Self::pending_symbol(handle, cx), - AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx), + AgentContextHandle::Symbol(handle) => { + Self::pending_symbol(handle, project.path_style(cx), cx) + } + AgentContextHandle::Selection(handle) => { + Self::pending_selection(handle, project.path_style(cx), cx) + } AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)), AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)), AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)), AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx), - AgentContextHandle::Image(handle) => Some(Self::image(handle, model, cx)), + AgentContextHandle::Image(handle) => { + Some(Self::image(handle, model, project.path_style(cx), cx)) + } } } - fn pending_file(handle: FileContextHandle, cx: &App) -> Option { - let full_path = handle.buffer.read(cx).file()?.full_path(cx); - Some(Self::file(handle, &full_path, cx)) + fn pending_file( + handle: FileContextHandle, + path_style: PathStyle, + cx: &App, + ) -> Option { + let full_path = handle + .buffer + .read(cx) + .file()? + .full_path(cx) + .to_string_lossy() + .to_string(); + Some(Self::file(handle, &full_path, path_style, cx)) } - fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext { - let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into(); - let (name, parent) = - extract_file_name_and_directory_from_full_path(full_path, &full_path_string); + fn file( + handle: FileContextHandle, + full_path: &str, + path_style: PathStyle, + cx: &App, + ) -> AddedContext { + let (name, parent) = extract_file_name_and_directory_from_full_path(full_path, path_style); AddedContext { kind: ContextKind::File, name, parent, - tooltip: Some(full_path_string), - icon_path: FileIcons::get_icon(full_path, cx), + tooltip: Some(SharedString::new(full_path)), + icon_path: FileIcons::get_icon(Path::new(full_path), cx), status: ContextStatus::Ready, render_hover: None, handle: AgentContextHandle::File(handle), @@ -343,19 +365,24 @@ impl AddedContext { ) -> Option { let worktree = project.worktree_for_entry(handle.entry_id, cx)?.read(cx); let entry = worktree.entry_for_id(handle.entry_id)?; - let full_path = worktree.full_path(&entry.path); - Some(Self::directory(handle, &full_path)) + let full_path = worktree + .full_path(&entry.path) + .to_string_lossy() + .to_string(); + Some(Self::directory(handle, &full_path, project.path_style(cx))) } - fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext { - let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into(); - let (name, parent) = - extract_file_name_and_directory_from_full_path(full_path, &full_path_string); + fn directory( + handle: DirectoryContextHandle, + full_path: &str, + path_style: PathStyle, + ) -> AddedContext { + let (name, parent) = extract_file_name_and_directory_from_full_path(full_path, path_style); AddedContext { kind: ContextKind::Directory, name, parent, - tooltip: Some(full_path_string), + tooltip: Some(SharedString::new(full_path)), icon_path: None, status: ContextStatus::Ready, render_hover: None, @@ -363,9 +390,17 @@ impl AddedContext { } } - fn pending_symbol(handle: SymbolContextHandle, cx: &App) -> Option { - let excerpt = - ContextFileExcerpt::new(&handle.full_path(cx)?, handle.enclosing_line_range(cx), cx); + fn pending_symbol( + handle: SymbolContextHandle, + path_style: PathStyle, + cx: &App, + ) -> Option { + let excerpt = ContextFileExcerpt::new( + &handle.full_path(cx)?.to_string_lossy(), + handle.enclosing_line_range(cx), + path_style, + cx, + ); Some(AddedContext { kind: ContextKind::Symbol, name: handle.symbol.clone(), @@ -383,8 +418,17 @@ impl AddedContext { }) } - fn pending_selection(handle: SelectionContextHandle, cx: &App) -> Option { - let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx); + fn pending_selection( + handle: SelectionContextHandle, + path_style: PathStyle, + cx: &App, + ) -> Option { + let excerpt = ContextFileExcerpt::new( + &handle.full_path(cx)?.to_string_lossy(), + handle.line_range(cx), + path_style, + cx, + ); Some(AddedContext { kind: ContextKind::Selection, name: excerpt.file_name_and_range.clone(), @@ -485,13 +529,13 @@ impl AddedContext { fn image( context: ImageContext, model: Option<&Arc>, + path_style: PathStyle, cx: &App, ) -> AddedContext { let (name, parent, icon_path) = if let Some(full_path) = context.full_path.as_ref() { - let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into(); let (name, parent) = - extract_file_name_and_directory_from_full_path(full_path, &full_path_string); - let icon_path = FileIcons::get_icon(full_path, cx); + extract_file_name_and_directory_from_full_path(full_path, path_style); + let icon_path = FileIcons::get_icon(Path::new(full_path), cx); (name, parent, icon_path) } else { ("Image".into(), None, None) @@ -540,19 +584,20 @@ impl AddedContext { } fn extract_file_name_and_directory_from_full_path( - path: &Path, - name_fallback: &SharedString, + path: &str, + path_style: PathStyle, ) -> (SharedString, Option) { - let name = path - .file_name() - .map(|n| n.to_string_lossy().into_owned().into()) - .unwrap_or_else(|| name_fallback.clone()); - let parent = path - .parent() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().into_owned().into()); - - (name, parent) + let (parent, file_name) = path_style.split(path); + let parent = parent.and_then(|parent| { + let parent = parent.trim_end_matches(path_style.separator()); + let (_, parent) = path_style.split(parent); + if parent.is_empty() { + None + } else { + Some(SharedString::new(parent)) + } + }); + (SharedString::new(file_name), parent) } #[derive(Debug, Clone)] @@ -564,25 +609,25 @@ struct ContextFileExcerpt { } impl ContextFileExcerpt { - pub fn new(full_path: &Path, line_range: Range, cx: &App) -> Self { - let full_path_string = full_path.to_string_lossy().into_owned(); - let file_name = full_path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_else(|| full_path_string.clone()); - + pub fn new(full_path: &str, line_range: Range, path_style: PathStyle, cx: &App) -> Self { + let (parent, file_name) = path_style.split(full_path); let line_range_text = format!(" ({}-{})", line_range.start.row + 1, line_range.end.row + 1); - let mut full_path_and_range = full_path_string; + let mut full_path_and_range = full_path.to_owned(); full_path_and_range.push_str(&line_range_text); - let mut file_name_and_range = file_name; + let mut file_name_and_range = file_name.to_owned(); file_name_and_range.push_str(&line_range_text); - let parent_name = full_path - .parent() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().into_owned().into()); + let parent_name = parent.and_then(|parent| { + let parent = parent.trim_end_matches(path_style.separator()); + let (_, parent) = path_style.split(parent); + if parent.is_empty() { + None + } else { + Some(SharedString::new(parent)) + } + }); - let icon_path = FileIcons::get_icon(full_path, cx); + let icon_path = FileIcons::get_icon(Path::new(full_path), cx); ContextFileExcerpt { file_name_and_range: file_name_and_range.into(), @@ -690,6 +735,7 @@ impl Component for AddedContext { image_task: Task::ready(Some(LanguageModelImage::empty())).shared(), }, None, + PathStyle::local(), cx, ), ); @@ -710,6 +756,7 @@ impl Component for AddedContext { .shared(), }, None, + PathStyle::local(), cx, ), ); @@ -725,6 +772,7 @@ impl Component for AddedContext { image_task: Task::ready(None).shared(), }, None, + PathStyle::local(), cx, ), ); @@ -767,7 +815,8 @@ mod tests { full_path: None, }; - let added_context = AddedContext::image(image_context, Some(&model), cx); + let added_context = + AddedContext::image(image_context, Some(&model), PathStyle::local(), cx); assert!(matches!( added_context.status, @@ -790,7 +839,7 @@ mod tests { full_path: None, }; - let added_context = AddedContext::image(image_context, None, cx); + let added_context = AddedContext::image(image_context, None, PathStyle::local(), cx); assert!( matches!(added_context.status, ContextStatus::Ready), diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index 53a631e68f73b151ea830c9ba6594c95014f2e4b..0968a297b82bb0da783ec18fb1cd0301acf50f4c 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -355,9 +355,7 @@ fn collect_files( let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?; append_buffer_to_output( &snapshot, - Some(Path::new( - path_including_worktree_name.display(path_style).as_ref(), - )), + Some(path_including_worktree_name.display(path_style).as_ref()), &mut output, ) .log_err(); @@ -382,18 +380,18 @@ fn collect_files( } pub fn codeblock_fence_for_path( - path: Option<&Path>, + path: Option<&str>, row_range: Option>, ) -> String { let mut text = String::new(); write!(text, "```").unwrap(); if let Some(path) = path { - if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) { + if let Some(extension) = Path::new(path).extension().and_then(|ext| ext.to_str()) { write!(text, "{} ", extension).unwrap(); } - write!(text, "{}", path.display()).unwrap(); + write!(text, "{path}").unwrap(); } else { write!(text, "untitled").unwrap(); } @@ -413,12 +411,12 @@ pub struct FileCommandMetadata { pub fn build_entry_output_section( range: Range, - path: Option<&Path>, + path: Option<&str>, is_directory: bool, line_range: Option>, ) -> SlashCommandOutputSection { let mut label = if let Some(path) = path { - path.to_string_lossy().to_string() + path.to_string() } else { "untitled".to_string() }; @@ -441,7 +439,7 @@ pub fn build_entry_output_section( } else { path.and_then(|path| { serde_json::to_value(FileCommandMetadata { - path: path.to_string_lossy().to_string(), + path: path.to_string(), }) .ok() }) @@ -532,7 +530,7 @@ mod custom_path_matcher { pub fn append_buffer_to_output( buffer: &BufferSnapshot, - path: Option<&Path>, + path: Option<&str>, output: &mut SlashCommandOutput, ) -> Result<()> { let prev_len = output.text.len(); diff --git a/crates/assistant_slash_commands/src/selection_command.rs b/crates/assistant_slash_commands/src/selection_command.rs index c5f01ee94c670d359816d80eef3c0230fe8ad0d0..068a66339b9a3c8619e1050feb4d70b32f97f657 100644 --- a/crates/assistant_slash_commands/src/selection_command.rs +++ b/crates/assistant_slash_commands/src/selection_command.rs @@ -137,7 +137,9 @@ pub fn selections_creases( None }; let language_name = language_name.as_deref().unwrap_or(""); - let filename = snapshot.file_at(range.start).map(|file| file.full_path(cx)); + let filename = snapshot + .file_at(range.start) + .map(|file| file.full_path(cx).to_string_lossy().to_string()); let text = if language_name == "markdown" { selected_text .lines() @@ -187,9 +189,9 @@ pub fn selections_creases( let start_line = range.start.row + 1; let end_line = range.end.row + 1; if start_line == end_line { - format!("{}, Line {}", path.display(), start_line) + format!("{path}, Line {start_line}") } else { - format!("{}, Lines {} to {}", path.display(), start_line, end_line) + format!("{path}, Lines {start_line} to {end_line}") } } else { "Quoted selection".to_string() diff --git a/crates/assistant_slash_commands/src/symbols_command.rs b/crates/assistant_slash_commands/src/symbols_command.rs index c700319800769e3a6e45234355c850e746231200..c537ced2966bf0bfde912ba326890a935a20f220 100644 --- a/crates/assistant_slash_commands/src/symbols_command.rs +++ b/crates/assistant_slash_commands/src/symbols_command.rs @@ -7,8 +7,8 @@ use editor::Editor; use gpui::{AppContext as _, Task, WeakEntity}; use language::{BufferSnapshot, LspAdapterDelegate}; use std::sync::Arc; -use std::{path::Path, sync::atomic::AtomicBool}; -use ui::{App, IconName, Window}; +use std::sync::atomic::AtomicBool; +use ui::{App, IconName, SharedString, Window}; use workspace::Workspace; pub struct OutlineSlashCommand; @@ -67,13 +67,13 @@ impl SlashCommand for OutlineSlashCommand { }; let snapshot = buffer.read(cx).snapshot(); - let path = snapshot.resolve_file_path(cx, true); + let path = snapshot.resolve_file_path(true, cx); cx.background_spawn(async move { let outline = snapshot.outline(None); - let path = path.as_deref().unwrap_or(Path::new("untitled")); - let mut outline_text = format!("Symbols for {}:\n", path.display()); + let path = path.as_deref().unwrap_or("untitled"); + let mut outline_text = format!("Symbols for {path}:\n"); for item in &outline.path_candidates { outline_text.push_str("- "); outline_text.push_str(&item.string); @@ -84,7 +84,7 @@ impl SlashCommand for OutlineSlashCommand { sections: vec![SlashCommandOutputSection { range: 0..outline_text.len(), icon: IconName::ListTree, - label: path.to_string_lossy().to_string().into(), + label: SharedString::new(path), metadata: None, }], text: outline_text, diff --git a/crates/assistant_slash_commands/src/tab_command.rs b/crates/assistant_slash_commands/src/tab_command.rs index a124beed6302d6c67085ccb70f4c3aa58834d3f2..9fd38128cacd51db5bd48fc801d1238ae3f674c4 100644 --- a/crates/assistant_slash_commands/src/tab_command.rs +++ b/crates/assistant_slash_commands/src/tab_command.rs @@ -8,12 +8,9 @@ use editor::Editor; use futures::future::join_all; use gpui::{Task, WeakEntity}; use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate}; -use std::{ - path::PathBuf, - sync::{Arc, atomic::AtomicBool}, -}; +use std::sync::{Arc, atomic::AtomicBool}; use ui::{ActiveTheme, App, Window, prelude::*}; -use util::ResultExt; +use util::{ResultExt, paths::PathStyle}; use workspace::Workspace; use crate::file_command::append_buffer_to_output; @@ -72,35 +69,42 @@ impl SlashCommand for TabSlashCommand { return Task::ready(Ok(Vec::new())); } - let active_item_path = workspace.as_ref().and_then(|workspace| { - workspace - .update(cx, |workspace, cx| { - let snapshot = active_item_buffer(workspace, cx).ok()?; - snapshot.resolve_file_path(cx, true) - }) - .ok() - .flatten() + let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { + return Task::ready(Err(anyhow::anyhow!("no workspace"))); + }; + + let active_item_path = workspace.update(cx, |workspace, cx| { + let snapshot = active_item_buffer(workspace, cx).ok()?; + snapshot.resolve_file_path(true, cx) }); + let path_style = workspace.read(cx).path_style(cx); + let current_query = arguments.last().cloned().unwrap_or_default(); - let tab_items_search = - tab_items_for_queries(workspace, &[current_query], cancel, false, window, cx); + let tab_items_search = tab_items_for_queries( + Some(workspace.downgrade()), + &[current_query], + cancel, + false, + window, + cx, + ); let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); window.spawn(cx, async move |_| { let tab_items = tab_items_search.await?; let run_command = tab_items.len() == 1; let tab_completion_items = tab_items.into_iter().filter_map(|(path, ..)| { - let path_string = path.as_deref()?.to_string_lossy().to_string(); - if argument_set.contains(&path_string) { + let path = path?; + if argument_set.contains(&path) { return None; } - if active_item_path.is_some() && active_item_path == path { + if active_item_path.as_ref() == Some(&path) { return None; } - let label = create_tab_completion_label(path.as_ref()?, comment_id); + let label = create_tab_completion_label(&path, path_style, comment_id); Some(ArgumentCompletion { label, - new_text: path_string, + new_text: path, replace_previous_arguments: false, after_completion: run_command.into(), }) @@ -109,8 +113,9 @@ impl SlashCommand for TabSlashCommand { let active_item_completion = active_item_path .as_deref() .map(|active_item_path| { - let path_string = active_item_path.to_string_lossy().to_string(); - let label = create_tab_completion_label(active_item_path, comment_id); + let path_string = active_item_path.to_string(); + let label = + create_tab_completion_label(active_item_path, path_style, comment_id); ArgumentCompletion { label, new_text: path_string, @@ -169,7 +174,7 @@ fn tab_items_for_queries( strict_match: bool, window: &mut Window, cx: &mut App, -) -> Task, BufferSnapshot, usize)>>> { +) -> Task, BufferSnapshot, usize)>>> { let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty()); let queries = queries.to_owned(); window.spawn(cx, async move |cx| { @@ -179,7 +184,7 @@ fn tab_items_for_queries( .update(cx, |workspace, cx| { if strict_match && empty_query { let snapshot = active_item_buffer(workspace, cx)?; - let full_path = snapshot.resolve_file_path(cx, true); + let full_path = snapshot.resolve_file_path(true, cx); return anyhow::Ok(vec![(full_path, snapshot, 0)]); } @@ -201,7 +206,7 @@ fn tab_items_for_queries( && visited_buffers.insert(buffer.read(cx).remote_id()) { let snapshot = buffer.read(cx).snapshot(); - let full_path = snapshot.resolve_file_path(cx, true); + let full_path = snapshot.resolve_file_path(true, cx); open_buffers.push((full_path, snapshot, *timestamp)); } } @@ -224,10 +229,7 @@ fn tab_items_for_queries( let match_candidates = open_buffers .iter() .enumerate() - .filter_map(|(id, (full_path, ..))| { - let path_string = full_path.as_deref()?.to_string_lossy().to_string(); - Some((id, path_string)) - }) + .filter_map(|(id, (full_path, ..))| Some((id, full_path.clone()?))) .fold(HashMap::default(), |mut candidates, (id, path_string)| { candidates .entry(path_string) @@ -249,8 +251,7 @@ fn tab_items_for_queries( .iter() .enumerate() .filter_map(|(id, (full_path, ..))| { - let path_string = full_path.as_deref()?.to_string_lossy().to_string(); - Some(fuzzy::StringMatchCandidate::new(id, &path_string)) + Some(fuzzy::StringMatchCandidate::new(id, full_path.as_ref()?)) }) .collect::>(); let mut processed_matches = HashSet::default(); @@ -302,21 +303,15 @@ fn active_item_buffer( } fn create_tab_completion_label( - path: &std::path::Path, + path: &str, + path_style: PathStyle, comment_id: Option, ) -> CodeLabel { - let file_name = path - .file_name() - .map(|f| f.to_string_lossy()) - .unwrap_or_default(); - let parent_path = path - .parent() - .map(|p| p.to_string_lossy()) - .unwrap_or_default(); + let (parent_path, file_name) = path_style.split(path); let mut label = CodeLabel::default(); - label.push_str(&file_name, None); + label.push_str(file_name, None); label.push_str(" ", None); - label.push_str(&parent_path, comment_id); + label.push_str(parent_path.unwrap_or_default(), comment_id); label.filter_range = 0..file_name.len(); label } diff --git a/crates/assistant_tool/src/outline.rs b/crates/assistant_tool/src/outline.rs index fa5ad561eea94d92568ee36ab8835f8a87337314..4c8e2efefd67e25c630d38e16bda8a8dff34fb16 100644 --- a/crates/assistant_tool/src/outline.rs +++ b/crates/assistant_tool/src/outline.rs @@ -5,7 +5,6 @@ use language::{Buffer, OutlineItem, ParseStatus}; use project::Project; use regex::Regex; use std::fmt::Write; -use std::path::Path; use text::Point; /// For files over this size, instead of reading them (or including them in context), @@ -143,7 +142,7 @@ pub struct BufferContent { /// For smaller files, returns the full content. pub async fn get_buffer_content_or_outline( buffer: Entity, - path: Option<&Path>, + path: Option<&str>, cx: &AsyncApp, ) -> Result { let file_size = buffer.read_with(cx, |buffer, _| buffer.text().len())?; @@ -170,15 +169,10 @@ pub async fn get_buffer_content_or_outline( let text = if let Some(path) = path { format!( - "# File outline for {} (file too large to show full content)\n\n{}", - path.display(), - outline_text + "# File outline for {path} (file too large to show full content)\n\n{outline_text}", ) } else { - format!( - "# File outline (file too large to show full content)\n\n{}", - outline_text - ) + format!("# File outline (file too large to show full content)\n\n{outline_text}",) }; Ok(BufferContent { text, diff --git a/crates/assistant_tools/src/edit_agent.rs b/crates/assistant_tools/src/edit_agent.rs index 29ac53e2a606d63873f515aff25326debf0486f1..829287f65478d56d793ed506c44a8331580cc4c5 100644 --- a/crates/assistant_tools/src/edit_agent.rs +++ b/crates/assistant_tools/src/edit_agent.rs @@ -26,13 +26,13 @@ use language_model::{ use project::{AgentLocation, Project}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::{cmp, iter, mem, ops::Range, path::PathBuf, pin::Pin, sync::Arc, task::Poll}; +use std::{cmp, iter, mem, ops::Range, pin::Pin, sync::Arc, task::Poll}; use streaming_diff::{CharOperation, StreamingDiff}; use streaming_fuzzy_matcher::StreamingFuzzyMatcher; #[derive(Serialize)] struct CreateFilePromptTemplate { - path: Option, + path: Option, edit_description: String, } @@ -42,7 +42,7 @@ impl Template for CreateFilePromptTemplate { #[derive(Serialize)] struct EditFileXmlPromptTemplate { - path: Option, + path: Option, edit_description: String, } @@ -52,7 +52,7 @@ impl Template for EditFileXmlPromptTemplate { #[derive(Serialize)] struct EditFileDiffFencedPromptTemplate { - path: Option, + path: Option, edit_description: String, } @@ -115,7 +115,7 @@ impl EditAgent { let conversation = conversation.clone(); let output = cx.spawn(async move |cx| { let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?; - let path = cx.update(|cx| snapshot.resolve_file_path(cx, true))?; + let path = cx.update(|cx| snapshot.resolve_file_path(true, cx))?; let prompt = CreateFilePromptTemplate { path, edit_description, @@ -229,7 +229,7 @@ impl EditAgent { let edit_format = self.edit_format; let output = cx.spawn(async move |cx| { let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?; - let path = cx.update(|cx| snapshot.resolve_file_path(cx, true))?; + let path = cx.update(|cx| snapshot.resolve_file_path(true, cx))?; let prompt = match edit_format { EditFormat::XmlTags => EditFileXmlPromptTemplate { path, diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 7006cc690375b904be2128e16d254cc6acbaac01..f9f68491e5846fa1ead09d6976d1f9a9bc99b501 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -261,9 +261,8 @@ impl Tool for ReadFileTool { Ok(result) } else { // No line ranges specified, so check file size to see if it's too big. - let path_buf = std::path::PathBuf::from(&file_path); let buffer_content = - outline::get_buffer_content_or_outline(buffer.clone(), Some(&path_buf), cx) + outline::get_buffer_content_or_outline(buffer.clone(), Some(&file_path), cx) .await?; action_log.update(cx, |log, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 04a479a776ec7bdd6a29cfe998eafab9e6ccd7c0..73ee2ae01e11e13d215cd33ac2d31cd295741134 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3782,13 +3782,17 @@ impl EditorElement { let file = for_excerpt.buffer.file(); let can_open_excerpts = Editor::can_open_excerpts_in_file(file); let path_style = file.map(|file| file.path_style(cx)); - let relative_path = for_excerpt.buffer.resolve_file_path(cx, include_root); - let filename = relative_path - .as_ref() - .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string())); - let parent_path = relative_path.as_ref().and_then(|path| { - Some(path.parent()?.to_string_lossy().to_string() + path_style?.separator()) - }); + let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx); + let (parent_path, filename) = if let Some(path) = &relative_path { + if let Some(path_style) = path_style { + let (dir, file_name) = path_style.split(path); + (dir.map(|dir| dir.to_owned()), Some(file_name.to_owned())) + } else { + (None, Some(path.clone())) + } + } else { + (None, None) + }; let focus_handle = editor.focus_handle(cx); let colors = cx.theme().colors(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f882db7c198bcabe22333cbbc620359695bb5f03..e8ce3b81f3e2fa958dd7839edbbdde9d5fc00667 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -963,13 +963,12 @@ impl Item for Editor { buffer .snapshot() .resolve_file_path( - cx, self.project .as_ref() .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) .unwrap_or_default(), + cx, ) - .map(|path| path.to_string_lossy().to_string()) .unwrap_or_else(|| { if multibuffer.is_singleton() { multibuffer.title(cx).to_string() diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b76bb7521b403d7c8900d8ac9963f7e908aa6ea3..e6f11d38d21f87a57b109152b4a8567e28f86428 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -4628,12 +4628,12 @@ impl BufferSnapshot { self.file.as_ref() } - pub fn resolve_file_path(&self, cx: &App, include_root: bool) -> Option { + pub fn resolve_file_path(&self, include_root: bool, cx: &App) -> Option { if let Some(file) = self.file() { if file.path().file_name().is_none() || include_root { - Some(file.full_path(cx)) + Some(file.full_path(cx).to_string_lossy().to_string()) } else { - Some(file.path().as_std_path().to_owned()) + Some(file.path().display(file.path_style(cx)).to_string()) } } else { None diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index e9dd3dd817a8584cdf999b87dad5d3b949f37092..18b8a9ccb0628834b664dfaf2aec622e476cb4ff 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -280,6 +280,17 @@ impl PathStyle { )) } } + + pub fn split(self, path_like: &str) -> (Option<&str>, &str) { + let Some(pos) = path_like.rfind(self.separator()) else { + return (None, path_like); + }; + let filename_start = pos + self.separator().len(); + ( + Some(&path_like[..filename_start]), + &path_like[filename_start..], + ) + } } #[derive(Debug, Clone)]