Detailed changes
@@ -159,7 +159,7 @@ pub struct FileContextHandle {
#[derive(Debug, Clone)]
pub struct FileContext {
pub handle: FileContextHandle,
- pub full_path: Arc<Path>,
+ 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<Path> = 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<Path>,
+ pub full_path: String,
pub descendants: Vec<DirectoryContextDescendant>,
}
@@ -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<Path>,
+ pub full_path: String,
pub line_range: Range<Point>,
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<Path>,
+ pub full_path: String,
pub line_range: Range<Point>,
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<ProjectPath>,
- pub full_path: Option<Arc<Path>>,
+ pub full_path: Option<String>,
pub original_image: Arc<gpui::Image>,
// 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<Arc<RelPath
files
}
-fn codeblock_tag(full_path: &Path, line_range: Option<Range<Point>>) -> String {
+fn codeblock_tag(full_path: &str, line_range: Option<Range<Point>>) -> 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 {
@@ -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<ProjectPath>,
- full_path: Option<Arc<Path>>,
+ full_path: Option<String>,
image: Arc<Image>,
remove_if_exists: bool,
cx: &mut Context<ContextStore>,
@@ -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);
@@ -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<Result<Mention>> {
- fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc<RelPath>, PathBuf)> {
+ fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc<RelPath>, 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<RelPath>, PathBuf, String)>) -> String {
+fn render_directory_contents(entries: Vec<(Arc<RelPath>, 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);
@@ -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<AddedContext> {
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<AddedContext> {
- 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<AddedContext> {
+ 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<AddedContext> {
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<AddedContext> {
- 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<AddedContext> {
+ 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<AddedContext> {
- let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx);
+ fn pending_selection(
+ handle: SelectionContextHandle,
+ path_style: PathStyle,
+ cx: &App,
+ ) -> Option<AddedContext> {
+ 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<dyn language_model::LanguageModel>>,
+ 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<SharedString>) {
- 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<Point>, 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<Point>, 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),
@@ -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<RangeInclusive<u32>>,
) -> 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<usize>,
- path: Option<&Path>,
+ path: Option<&str>,
is_directory: bool,
line_range: Option<Range<u32>>,
) -> SlashCommandOutputSection<usize> {
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();
@@ -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()
@@ -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,
@@ -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<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> {
+) -> Task<anyhow::Result<Vec<(Option<String>, 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::<Vec<_>>();
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<HighlightId>,
) -> 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
}
@@ -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<Buffer>,
- path: Option<&Path>,
+ path: Option<&str>,
cx: &AsyncApp,
) -> Result<BufferContent> {
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,
@@ -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<PathBuf>,
+ path: Option<String>,
edit_description: String,
}
@@ -42,7 +42,7 @@ impl Template for CreateFilePromptTemplate {
#[derive(Serialize)]
struct EditFileXmlPromptTemplate {
- path: Option<PathBuf>,
+ path: Option<String>,
edit_description: String,
}
@@ -52,7 +52,7 @@ impl Template for EditFileXmlPromptTemplate {
#[derive(Serialize)]
struct EditFileDiffFencedPromptTemplate {
- path: Option<PathBuf>,
+ path: Option<String>,
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,
@@ -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| {
@@ -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();
@@ -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()
@@ -4628,12 +4628,12 @@ impl BufferSnapshot {
self.file.as_ref()
}
- pub fn resolve_file_path(&self, cx: &App, include_root: bool) -> Option<PathBuf> {
+ pub fn resolve_file_path(&self, include_root: bool, cx: &App) -> Option<String> {
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
@@ -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)]