assistant: Normalize line endings for prompts loaded from the prompt library (#15708)

Marshall Bowers and Max created

This PR makes it so we normalize the line endings for prompts to LFs
(`\n`) when we load a prompt from the library.

In some cases, prompts could end up with CRLF (`\r\n`) line endings.
When these prompts were used with the `/prompt` slash command and
summarily run, the prompt text would be converted into a rope, causing
the line endings to be normalized to LFs.

However, this would happen _after_ the ranges for the
`SlashCommandOutputSection`s were computed based on the text that still
contained the CRLFs. This would then cause these ranges to be invalid
for the text with the normalized endings, resulting in a panic when
converting them to anchors.

Fixes https://github.com/zed-industries/zed/issues/15652.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>

Change summary

crates/assistant/Cargo.toml                                   | 1 
crates/assistant/src/prompt_library.rs                        | 7 +++-
crates/assistant_slash_command/src/assistant_slash_command.rs | 2 
3 files changed, 7 insertions(+), 3 deletions(-)

Detailed changes

crates/assistant/Cargo.toml 🔗

@@ -71,6 +71,7 @@ strsim.workspace = true
 telemetry_events.workspace = true
 terminal.workspace = true
 terminal_view.workspace = true
+text.workspace = true
 theme.workspace = true
 toml.workspace = true
 ui.workspace = true

crates/assistant/src/prompt_library.rs 🔗

@@ -36,6 +36,7 @@ use std::{
     sync::{atomic::AtomicBool, Arc},
     time::Duration,
 };
+use text::LineEnding;
 use theme::ThemeSettings;
 use ui::{
     div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
@@ -1302,10 +1303,12 @@ impl PromptStore {
         let bodies = self.bodies;
         self.executor.spawn(async move {
             let txn = env.read_txn()?;
-            Ok(bodies
+            let mut prompt = bodies
                 .get(&txn, &id)?
                 .ok_or_else(|| anyhow!("prompt not found"))?
-                .into())
+                .into();
+            LineEnding::normalize(&mut prompt);
+            Ok(prompt)
         })
     }
 

crates/assistant_slash_command/src/assistant_slash_command.rs 🔗

@@ -60,7 +60,7 @@ pub type RenderFoldPlaceholder = Arc<
         + Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
 >;
 
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct SlashCommandOutput {
     pub text: String,
     pub sections: Vec<SlashCommandOutputSection<usize>>,