diff --git a/crates/assistant/src/slash_command/active_command.rs b/crates/assistant/src/slash_command/active_command.rs index 8eea99420b..7a80cad88f 100644 --- a/crates/assistant/src/slash_command/active_command.rs +++ b/crates/assistant/src/slash_command/active_command.rs @@ -1,10 +1,13 @@ -use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput}; +use super::{ + file_command::{codeblock_fence_for_path, FilePlaceholder}, + SlashCommand, SlashCommandOutput, +}; use anyhow::{anyhow, Result}; use assistant_slash_command::SlashCommandOutputSection; use editor::Editor; use gpui::{AppContext, Task, WeakView}; use language::LspAdapterDelegate; -use std::{borrow::Cow, sync::Arc}; +use std::sync::Arc; use ui::{IntoElement, WindowContext}; use workspace::Workspace; @@ -60,14 +63,8 @@ impl SlashCommand for ActiveSlashCommand { let text = cx.background_executor().spawn({ let path = path.clone(); async move { - let path = path - .as_ref() - .map(|path| path.to_string_lossy()) - .unwrap_or_else(|| Cow::Borrowed("untitled")); - - let mut output = String::with_capacity(path.len() + snapshot.len() + 9); - output.push_str("```"); - output.push_str(&path); + let mut output = String::new(); + output.push_str(&codeblock_fence_for_path(path.as_deref(), None)); output.push('\n'); for chunk in snapshot.as_rope().chunks() { output.push_str(chunk); diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index ed65a9dc96..1508612faa 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -6,6 +6,7 @@ use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView}; use language::{LineEnding, LspAdapterDelegate}; use project::PathMatchCandidateSet; use std::{ + fmt::Write, ops::Range, path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, @@ -155,20 +156,20 @@ impl SlashCommand for FileSlashCommand { }; let fs = workspace.read(cx).app_state().fs.clone(); - let argument = argument.to_string(); - let text = cx.background_executor().spawn(async move { - let mut content = fs.load(&abs_path).await?; - LineEnding::normalize(&mut content); - let mut output = String::with_capacity(argument.len() + content.len() + 9); - output.push_str("```"); - output.push_str(&argument); - output.push('\n'); - output.push_str(&content); - if !output.ends_with('\n') { - output.push('\n'); + let text = cx.background_executor().spawn({ + let path = path.clone(); + async move { + let mut content = fs.load(&abs_path).await?; + LineEnding::normalize(&mut content); + let mut output = String::new(); + output.push_str(&codeblock_fence_for_path(Some(&path), None)); + output.push_str(&content); + if !output.ends_with('\n') { + output.push('\n'); + } + output.push_str("```"); + anyhow::Ok(output) } - output.push_str("```"); - anyhow::Ok(output) }); cx.foreground_executor().spawn(async move { let text = text.await?; @@ -224,3 +225,25 @@ impl RenderOnce for FilePlaceholder { .on_click(move |_, cx| unfold(cx)) } } + +pub fn codeblock_fence_for_path(path: Option<&Path>, 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()) { + write!(text, "{} ", extension).unwrap(); + } + + write!(text, "{}", path.display()).unwrap(); + } else { + write!(text, "untitled").unwrap(); + } + + if let Some(row_range) = row_range { + write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap(); + } + + text.push('\n'); + text +} diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 195c5b1633..f55af0a9b8 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -1,4 +1,7 @@ -use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput}; +use super::{ + file_command::{codeblock_fence_for_path, FilePlaceholder}, + SlashCommand, SlashCommandOutput, +}; use anyhow::Result; use assistant_slash_command::SlashCommandOutputSection; use gpui::{AppContext, Task, WeakView}; @@ -125,9 +128,8 @@ impl SlashCommand for SearchSlashCommand { let range_start = result.range.start.min(file_content.len()); let range_end = result.range.end.min(file_content.len()); - let start_line = - file_content[0..range_start].matches('\n').count() as u32 + 1; - let end_line = file_content[0..range_end].matches('\n').count() as u32 + 1; + let start_row = file_content[0..range_start].matches('\n').count() as u32; + let end_row = file_content[0..range_end].matches('\n').count() as u32; let start_line_byte_offset = file_content[0..range_start] .rfind('\n') .map(|pos| pos + 1) @@ -138,14 +140,11 @@ impl SlashCommand for SearchSlashCommand { .unwrap_or_else(|| file_content.len()); let section_start_ix = text.len(); - writeln!( - text, - "```{}:{}-{}", - result.path.display(), - start_line, - end_line, - ) - .unwrap(); + text.push_str(&codeblock_fence_for_path( + Some(&result.path), + Some(start_row..end_row), + )); + let mut excerpt = file_content[start_line_byte_offset..end_line_byte_offset].to_string(); LineEnding::normalize(&mut excerpt); @@ -159,7 +158,7 @@ impl SlashCommand for SearchSlashCommand { FilePlaceholder { id, path: Some(full_path.clone()), - line_range: Some(start_line..end_line), + line_range: Some(start_row..end_row), unfold, } .into_any_element() diff --git a/crates/assistant/src/slash_command/tabs_command.rs b/crates/assistant/src/slash_command/tabs_command.rs index 1426b438f6..f690411468 100644 --- a/crates/assistant/src/slash_command/tabs_command.rs +++ b/crates/assistant/src/slash_command/tabs_command.rs @@ -1,11 +1,14 @@ -use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput}; +use super::{ + file_command::{codeblock_fence_for_path, FilePlaceholder}, + SlashCommand, SlashCommandOutput, +}; use anyhow::{anyhow, Result}; use assistant_slash_command::SlashCommandOutputSection; use collections::HashMap; use editor::Editor; use gpui::{AppContext, Entity, Task, WeakView}; use language::LspAdapterDelegate; -use std::{fmt::Write, path::Path, sync::Arc}; +use std::{fmt::Write, sync::Arc}; use ui::{IntoElement, WindowContext}; use workspace::Workspace; @@ -77,15 +80,7 @@ impl SlashCommand for TabsSlashCommand { let mut text = String::new(); for (full_path, buffer, _) in open_buffers { let section_start_ix = text.len(); - writeln!( - text, - "```{}\n", - full_path - .as_deref() - .unwrap_or(Path::new("untitled")) - .display() - ) - .unwrap(); + text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None)); for chunk in buffer.as_rope().chunks() { text.push_str(chunk); }