Clicking on `Choose another encoding` and selecting an encoding should

R Aadarsh created

now open the file in the chosen encoding if it is valid or show the
invalid screen again if not.

(UTF-16 files aren't being handled correctly as of now)

Change summary

crates/agent_ui/src/acp/message_editor.rs         | 91 +++++++++++++++++
crates/agent_ui/src/context.rs                    |  2 
crates/fs/src/encodings.rs                        |  2 
crates/project/src/buffer_store.rs                | 14 +-
crates/project/src/debugger/breakpoint_store.rs   |  2 
crates/project/src/lsp_store.rs                   |  2 
crates/project/src/lsp_store/rust_analyzer_ext.rs |  6 
crates/project/src/project.rs                     |  7 +
crates/remote_server/src/headless_project.rs      | 10 +
crates/workspace/src/workspace.rs                 |  1 
crates/worktree/src/worktree.rs                   |  6 
11 files changed, 124 insertions(+), 19 deletions(-)

Detailed changes

crates/agent_ui/src/acp/message_editor.rs 🔗

@@ -463,6 +463,97 @@ impl MessageEditor {
         })
     }
 
+    fn confirm_mention_for_directory(
+        &mut self,
+        abs_path: PathBuf,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<Mention>> {
+        fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
+            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
+        }
+
+        let Some(project_path) = self
+            .project
+            .read(cx)
+            .project_path_for_absolute_path(&abs_path, cx)
+        else {
+            return Task::ready(Err(anyhow!("project path not found")));
+        };
+        let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
+            return Task::ready(Err(anyhow!("project entry not found")));
+        };
+        let directory_path = entry.path.clone();
+        let worktree_id = project_path.worktree_id;
+        let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else {
+            return Task::ready(Err(anyhow!("worktree not found")));
+        };
+        let project = self.project.clone();
+        cx.spawn(async move |_, cx| {
+            let file_paths = worktree.read_with(cx, |worktree, _cx| {
+                collect_files_in_path(worktree, &directory_path)
+            })?;
+            let descendants_future = cx.update(|cx| {
+                join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
+                    let rel_path = worktree_path
+                        .strip_prefix(&directory_path)
+                        .log_err()
+                        .map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
+
+                    let open_task = project.update(cx, |project, cx| {
+                        project.buffer_store().update(cx, |buffer_store, cx| {
+                            let project_path = ProjectPath {
+                                worktree_id,
+                                path: worktree_path,
+                            };
+                            buffer_store.open_buffer(project_path, cx)
+                        })
+                    });
+
+                    cx.spawn(async move |cx| {
+                        let buffer = open_task.await.log_err()?;
+                        let buffer_content = outline::get_buffer_content_or_outline(
+                            buffer.clone(),
+                            Some(&full_path),
+                            &cx,
+                        )
+                        .await
+                        .ok()?;
+
+                        Some((rel_path, full_path, buffer_content.text, buffer))
+                    })
+                }))
+            })?;
+
+            let contents = cx
+                .background_spawn(async move {
+                    let (contents, tracked_buffers) = descendants_future
+                        .await
+                        .into_iter()
+                        .flatten()
+                        .map(|(rel_path, full_path, rope, buffer)| {
+                            ((rel_path, full_path, rope), buffer)
+                        })
+                        .unzip();
+                    Mention::Text {
+                        content: render_directory_contents(contents),
+                        tracked_buffers,
+                    }
+                })
+                .await;
+            anyhow::Ok(contents)
+        })
+    }
+
     fn confirm_mention_for_fetch(
         &mut self,
         url: url::Url,

crates/agent_ui/src/context.rs 🔗

@@ -287,7 +287,7 @@ impl DirectoryContextHandle {
             let open_task = project.update(cx, |project, cx| {
                 project.buffer_store().update(cx, |buffer_store, cx| {
                     let project_path = ProjectPath { worktree_id, path };
-                    buffer_store.open_buffer(project_path, cx)
+                    buffer_store.open_buffer(project_path, None, cx)
                 })
             });
 

crates/fs/src/encodings.rs 🔗

@@ -82,7 +82,7 @@ impl EncodingWrapper {
         } else {
             // If there were decoding errors, return an error.
             Err(anyhow::anyhow!(
-                "The file contains invalid bytes for the specified encoding: {}. This usually menas that the file is not a regular text file, or is encoded in a different encoding. Continuing to open it may result in data loss if saved.",
+                "The file contains invalid bytes for the specified encoding: {}.\nThis usually means that the file is not a regular text file, or is encoded in a different encoding.\nContinuing to open it may result in data loss if saved.",
                 self.0.name()
             ))
         }

crates/project/src/buffer_store.rs 🔗

@@ -9,6 +9,7 @@ use client::Client;
 use collections::{HashMap, HashSet, hash_map};
 use fs::Fs;
 use futures::{Future, FutureExt as _, StreamExt, channel::oneshot, future::Shared};
+use fs::{Fs, encodings::EncodingWrapper};
 use gpui::{
     App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
 };
@@ -631,11 +632,13 @@ impl LocalBufferStore {
         &self,
         path: Arc<RelPath>,
         worktree: Entity<Worktree>,
+        encoding: Option<EncodingWrapper>,
         cx: &mut Context<BufferStore>,
     ) -> Task<Result<Entity<Buffer>>> {
         let load_buffer = worktree.update(cx, |worktree, cx| {
-            let load_file = worktree.load_file(path.as_ref(), None, cx);
+            let load_file = worktree.load_file(path.as_ref(), encoding, cx);
             let reservation = cx.reserve_entity();
+
             let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
             let path = path.clone();
             cx.spawn(async move |_, cx| {
@@ -655,11 +658,7 @@ impl LocalBufferStore {
 
         cx.spawn(async move |this, cx| {
             let buffer = match load_buffer.await {
-                Ok(buffer) => {
-                    // Reload the buffer to trigger UTF-16 detection
-                    buffer.update(cx, |buffer, cx| buffer.reload(cx))?.await?;
-                    Ok(buffer)
-                }
+                Ok(buffer) => Ok(buffer),
                 Err(error) if is_not_found_error(&error) => cx.new(|cx| {
                     let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
                     let text_buffer = text::Buffer::new(
@@ -834,6 +833,7 @@ impl BufferStore {
     pub fn open_buffer(
         &mut self,
         project_path: ProjectPath,
+        encoding: Option<EncodingWrapper>,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Buffer>>> {
         if let Some(buffer) = self.get_by_path(&project_path) {
@@ -857,7 +857,7 @@ impl BufferStore {
                     return Task::ready(Err(anyhow!("no such worktree")));
                 };
                 let load_buffer = match &self.state {
-                    BufferStoreState::Local(this) => this.open_buffer(path, worktree, cx),
+                    BufferStoreState::Local(this) => this.open_buffer(path, worktree, encoding, cx),
                     BufferStoreState::Remote(this) => this.open_buffer(path, worktree, cx),
                 };
 

crates/project/src/debugger/breakpoint_store.rs 🔗

@@ -796,7 +796,7 @@ impl BreakpointStore {
                                 worktree_id: worktree.read(cx).id(),
                                 path: relative_path,
                             };
-                            this.open_buffer(path, cx)
+                            this.open_buffer(path, None, cx)
                         })?
                         .await;
                     let Ok(buffer) = buffer else {

crates/project/src/lsp_store.rs 🔗

@@ -8336,7 +8336,7 @@ impl LspStore {
             lsp_store
                 .update(cx, |lsp_store, cx| {
                     lsp_store.buffer_store().update(cx, |buffer_store, cx| {
-                        buffer_store.open_buffer(project_path, cx)
+                        buffer_store.open_buffer(project_path, None, cx)
                     })
                 })?
                 .await

crates/project/src/lsp_store/rust_analyzer_ext.rs 🔗

@@ -91,7 +91,7 @@ pub fn cancel_flycheck(
     let buffer = buffer_path.map(|buffer_path| {
         project.update(cx, |project, cx| {
             project.buffer_store().update(cx, |buffer_store, cx| {
-                buffer_store.open_buffer(buffer_path, cx)
+                buffer_store.open_buffer(buffer_path, None, cx)
             })
         })
     });
@@ -140,7 +140,7 @@ pub fn run_flycheck(
     let buffer = buffer_path.map(|buffer_path| {
         project.update(cx, |project, cx| {
             project.buffer_store().update(cx, |buffer_store, cx| {
-                buffer_store.open_buffer(buffer_path, cx)
+                buffer_store.open_buffer(buffer_path, None, cx)
             })
         })
     });
@@ -198,7 +198,7 @@ pub fn clear_flycheck(
     let buffer = buffer_path.map(|buffer_path| {
         project.update(cx, |project, cx| {
             project.buffer_store().update(cx, |buffer_store, cx| {
-                buffer_store.open_buffer(buffer_path, cx)
+                buffer_store.open_buffer(buffer_path, None, cx)
             })
         })
     });

crates/project/src/project.rs 🔗

@@ -28,6 +28,7 @@ use buffer_diff::BufferDiff;
 use context_server_store::ContextServerStore;
 use encoding_rs::Encoding;
 pub use environment::ProjectEnvironmentEvent;
+use fs::encodings::EncodingWrapper;
 use git::repository::get_git_committer;
 use git_store::{Repository, RepositoryId};
 pub mod search_history;
@@ -2717,7 +2718,11 @@ impl Project {
         }
 
         self.buffer_store.update(cx, |buffer_store, cx| {
-            buffer_store.open_buffer(path.into(), cx)
+            buffer_store.open_buffer(
+                path.into(),
+                Some(EncodingWrapper::new(self.encoding.lock().as_ref().unwrap())),
+                cx,
+            )
         })
     }
 

crates/remote_server/src/headless_project.rs 🔗

@@ -506,7 +506,14 @@ impl HeadlessProject {
         let (buffer_store, buffer) = this.update(&mut cx, |this, cx| {
             let buffer_store = this.buffer_store.clone();
             let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
-                buffer_store.open_buffer(ProjectPath { worktree_id, path }, cx)
+                buffer_store.open_buffer(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::<Path>::from_proto(message.payload.path),
+                    },
+                    None,
+                    cx,
+                )
             });
             anyhow::Ok((buffer_store, buffer))
         })??;
@@ -597,6 +604,7 @@ impl HeadlessProject {
                         worktree_id: worktree.read(cx).id(),
                         path: path,
                     },
+                    None,
                     cx,
                 )
             });

crates/workspace/src/workspace.rs 🔗

@@ -3416,6 +3416,7 @@ impl Workspace {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+        println!("{:?}", *self.encoding.lock().unwrap());
         cx.spawn_in(window, async move |workspace, cx| {
             let open_paths_task_result = workspace
                 .update_in(cx, |workspace, window, cx| {

crates/worktree/src/worktree.rs 🔗

@@ -710,7 +710,7 @@ impl Worktree {
     pub fn load_file(
         &self,
         path: &Path,
-        encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        encoding: Option<EncodingWrapper>,
         cx: &Context<Worktree>,
     ) -> Task<Result<LoadedFile>> {
         match self {
@@ -1324,7 +1324,7 @@ impl LocalWorktree {
     fn load_file(
         &self,
         path: &Path,
-        encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        encoding: Option<EncodingWrapper>,
         cx: &Context<Worktree>,
     ) -> Task<Result<LoadedFile>> {
         let path = Arc::from(path);
@@ -1353,7 +1353,7 @@ impl LocalWorktree {
                 .load_with_encoding(
                     &abs_path,
                     if let Some(encoding) = encoding {
-                        EncodingWrapper::new(*encoding.lock().unwrap())
+                        encoding
                     } else {
                         EncodingWrapper::new(encoding_rs::UTF_8)
                     },