Avoid redundant newline insertion after file command (#16419)

Max Brunsfeld created

Release Notes:

- Fixed an issue where an extra newline was inserted after running a
`/file` command in the assistant.

Change summary

crates/assistant/src/assistant_panel.rs |  4 ++--
crates/assistant/src/context.rs         | 25 ++++++++++++++++++++++---
2 files changed, 24 insertions(+), 5 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -2088,14 +2088,14 @@ impl ContextEditor {
         command_range: Range<language::Anchor>,
         name: &str,
         arguments: &[String],
-        insert_trailing_newline: bool,
+        ensure_trailing_newline: bool,
         workspace: WeakView<Workspace>,
         cx: &mut ViewContext<Self>,
     ) {
         if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
             let output = command.run(arguments, workspace, self.lsp_adapter_delegate.clone(), cx);
             self.context.update(cx, |context, cx| {
-                context.insert_command_output(command_range, output, insert_trailing_newline, cx)
+                context.insert_command_output(command_range, output, ensure_trailing_newline, cx)
             });
         }
     }

crates/assistant/src/context.rs 🔗

@@ -1395,7 +1395,7 @@ impl Context {
         &mut self,
         command_range: Range<language::Anchor>,
         output: Task<Result<SlashCommandOutput>>,
-        insert_trailing_newline: bool,
+        ensure_trailing_newline: bool,
         cx: &mut ModelContext<Self>,
     ) {
         self.reparse_slash_commands(cx);
@@ -1406,8 +1406,27 @@ impl Context {
                 let output = output.await;
                 this.update(&mut cx, |this, cx| match output {
                     Ok(mut output) => {
-                        if insert_trailing_newline {
-                            output.text.push('\n');
+                        // Ensure section ranges are valid.
+                        for section in &mut output.sections {
+                            section.range.start = section.range.start.min(output.text.len());
+                            section.range.end = section.range.end.min(output.text.len());
+                            while !output.text.is_char_boundary(section.range.start) {
+                                section.range.start -= 1;
+                            }
+                            while !output.text.is_char_boundary(section.range.end) {
+                                section.range.end += 1;
+                            }
+                        }
+
+                        // Ensure there is a newline after the last section.
+                        if ensure_trailing_newline {
+                            let has_newline_after_last_section =
+                                output.sections.last().map_or(false, |last_section| {
+                                    output.text[last_section.range.end..].ends_with('\n')
+                                });
+                            if !has_newline_after_last_section {
+                                output.text.push('\n');
+                            }
                         }
 
                         let version = this.version.clone();