Cargo.lock π
@@ -5601,7 +5601,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
- "encoding_rs",
+ "encodings",
"fs",
"futures 0.3.31",
"fuzzy",
Conrad Irwin created
* Remove the mutexes and have methods return the detected
encoding.
* Try to handle the BOM safely...
* Clean up a bunch of code to make it more Zeddy
Cargo.lock | 2
crates/copilot/src/copilot.rs | 9
crates/edit_prediction_context/src/syntax_index.rs | 2
crates/encodings/Cargo.toml | 5
crates/encodings/src/encodings.rs | 214 +++
crates/encodings/src/lib.rs | 188 ---
crates/encodings_ui/Cargo.toml | 5
crates/encodings_ui/src/encodings_ui.rs | 108 +
crates/encodings_ui/src/selectors.rs | 945 ++++++---------
crates/fs/src/fs.rs | 32
crates/language/src/buffer.rs | 47
crates/languages/src/json.rs | 2
crates/multi_buffer/src/multi_buffer.rs | 4
crates/project/src/buffer_store.rs | 22
crates/project/src/image_store.rs | 2
crates/project/src/invalid_item_view.rs | 122 --
crates/project/src/project.rs | 10
crates/project/src/project_tests.rs | 46
crates/workspace/src/invalid_item_view.rs | 55
crates/workspace/src/workspace.rs | 48
crates/worktree/src/worktree.rs | 84 -
crates/worktree/src/worktree_tests.rs | 4
crates/zed/src/zed.rs | 5
crates/zed_actions/src/lib.rs | 2
crates/zeta/src/zeta.rs | 2
25 files changed, 799 insertions(+), 1,166 deletions(-)
@@ -5601,7 +5601,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
- "encoding_rs",
+ "encodings",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -1241,7 +1241,7 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
#[cfg(test)]
mod tests {
use super::*;
- use encodings::{Encoding, EncodingOptions};
+ use encodings::Encoding;
use gpui::TestAppContext;
use util::{path, paths::PathStyle, rel_path::rel_path};
@@ -1452,12 +1452,7 @@ mod tests {
self.abs_path.clone()
}
- fn load(
- &self,
- _: &App,
- _: &EncodingOptions,
- _: Option<Arc<Encoding>>,
- ) -> Task<Result<String>> {
+ fn load(&self, _: &App, _: Encoding) -> Task<Result<String>> {
unimplemented!()
}
@@ -523,7 +523,7 @@ impl SyntaxIndex {
};
let snapshot_task = worktree.update(cx, |worktree, cx| {
- let load_task = worktree.load_file(&project_path.path, &Default::default(), None, cx);
+ let load_task = worktree.load_file(&project_path.path, &Default::default(), cx);
let worktree_abs_path = worktree.abs_path();
cx.spawn(async move |_this, cx| {
@@ -4,6 +4,11 @@ version = "0.1.0"
publish.workspace = true
edition.workspace = true
+
+[lib]
+path = "src/encodings.rs"
+doctest = false
+
[dependencies]
anyhow.workspace = true
encoding_rs.workspace = true
@@ -0,0 +1,214 @@
+use encoding_rs;
+use std::{borrow::Cow, fmt::Debug};
+
+pub use encoding_rs::{
+ BIG5, EUC_JP, EUC_KR, GB18030, GBK, IBM866, ISO_2022_JP, ISO_8859_2, ISO_8859_3, ISO_8859_4,
+ ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, ISO_8859_8_I, ISO_8859_10, ISO_8859_13,
+ ISO_8859_14, ISO_8859_15, ISO_8859_16, KOI8_R, KOI8_U, MACINTOSH, SHIFT_JIS, UTF_8, UTF_16BE,
+ UTF_16LE, WINDOWS_874, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, WINDOWS_1254,
+ WINDOWS_1255, WINDOWS_1256, WINDOWS_1257, WINDOWS_1258, X_MAC_CYRILLIC,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Encoding {
+ pub encoding: &'static encoding_rs::Encoding,
+ pub with_bom: bool,
+}
+
+impl Default for Encoding {
+ fn default() -> Self {
+ Encoding {
+ encoding: UTF_8,
+ with_bom: false,
+ }
+ }
+}
+
+impl Encoding {
+ pub fn decode(&self, input: Vec<u8>) -> anyhow::Result<String> {
+ if self.encoding == UTF_8 && !self.with_bom {
+ return Ok(String::from_utf8(input)?);
+ }
+ let Some(result) = self
+ .encoding
+ .decode_without_bom_handling_and_without_replacement(&input)
+ else {
+ return Err(anyhow::anyhow!(
+ "input is not valid {}",
+ self.encoding.name()
+ ));
+ };
+
+ if self.with_bom && result.starts_with("\u{FEFF}") {
+ Ok(result[3..].to_string())
+ } else {
+ Ok(result.into_owned())
+ }
+ }
+
+ pub fn bom(&self) -> Option<&'static [u8]> {
+ if !self.with_bom {
+ return None;
+ }
+ if self.encoding == UTF_8 {
+ Some(&[0xEF, 0xBB, 0xBF])
+ } else if self.encoding == UTF_16BE {
+ Some(&[0xFE, 0xFF])
+ } else if self.encoding == UTF_16LE {
+ Some(&[0xFF, 0xFE])
+ } else {
+ None
+ }
+ }
+
+ pub fn encode_chunk<'a>(&self, input: &'a str) -> anyhow::Result<Cow<'a, [u8]>> {
+ if self.encoding == UTF_8 {
+ Ok(Cow::Borrowed(input.as_bytes()))
+ } else if self.encoding == UTF_16BE {
+ let mut data = Vec::<u8>::with_capacity(input.len() * 2);
+
+ // Convert the input string to UTF-16BE bytes
+ let utf16be_bytes = input.encode_utf16().flat_map(|u| u.to_be_bytes());
+
+ data.extend(utf16be_bytes);
+ Ok(Cow::Owned(data))
+ } else if self.encoding == UTF_16LE {
+ let mut data = Vec::<u8>::with_capacity(input.len() * 2);
+
+ // Convert the input string to UTF-16LE bytes
+ let utf16le_bytes = input.encode_utf16().flat_map(|u| u.to_le_bytes());
+
+ data.extend(utf16le_bytes);
+ Ok(Cow::Owned(data))
+ } else {
+ // todo: should we error on invalid content when encoding?
+ let (cow, _encoding_used, _had_errors) = self.encoding.encode(&input);
+
+ Ok(cow)
+ }
+ }
+
+ pub fn name(&self) -> &'static str {
+ let name = self.encoding.name();
+
+ match name {
+ "UTF-8" => "UTF-8",
+ "UTF-16LE" => "UTF-16 LE",
+ "UTF-16BE" => "UTF-16 BE",
+ "windows-1252" => "Windows-1252",
+ "windows-1251" => "Windows-1251",
+ "windows-1250" => "Windows-1250",
+ "ISO-8859-2" => "ISO 8859-2",
+ "ISO-8859-3" => "ISO 8859-3",
+ "ISO-8859-4" => "ISO 8859-4",
+ "ISO-8859-5" => "ISO 8859-5",
+ "ISO-8859-6" => "ISO 8859-6",
+ "ISO-8859-7" => "ISO 8859-7",
+ "ISO-8859-8" => "ISO 8859-8",
+ "ISO-8859-13" => "ISO 8859-13",
+ "ISO-8859-15" => "ISO 8859-15",
+ "KOI8-R" => "KOI8-R",
+ "KOI8-U" => "KOI8-U",
+ "macintosh" => "MacRoman",
+ "x-mac-cyrillic" => "Mac Cyrillic",
+ "windows-874" => "Windows-874",
+ "windows-1253" => "Windows-1253",
+ "windows-1254" => "Windows-1254",
+ "windows-1255" => "Windows-1255",
+ "windows-1256" => "Windows-1256",
+ "windows-1257" => "Windows-1257",
+ "windows-1258" => "Windows-1258",
+ "EUC-KR" => "Windows-949",
+ "EUC-JP" => "EUC-JP",
+ "ISO-2022-JP" => "ISO 2022-JP",
+ "GBK" => "GBK",
+ "gb18030" => "GB18030",
+ "Big5" => "Big5",
+ _ => name,
+ }
+ }
+
+ pub fn from_name(name: &str) -> Self {
+ let encoding = match name {
+ "UTF-8" => encoding_rs::UTF_8,
+ "UTF-16 LE" => encoding_rs::UTF_16LE,
+ "UTF-16 BE" => encoding_rs::UTF_16BE,
+ "Windows-1252" => encoding_rs::WINDOWS_1252,
+ "Windows-1251" => encoding_rs::WINDOWS_1251,
+ "Windows-1250" => encoding_rs::WINDOWS_1250,
+ "ISO 8859-2" => encoding_rs::ISO_8859_2,
+ "ISO 8859-3" => encoding_rs::ISO_8859_3,
+ "ISO 8859-4" => encoding_rs::ISO_8859_4,
+ "ISO 8859-5" => encoding_rs::ISO_8859_5,
+ "ISO 8859-6" => encoding_rs::ISO_8859_6,
+ "ISO 8859-7" => encoding_rs::ISO_8859_7,
+ "ISO 8859-8" => encoding_rs::ISO_8859_8,
+ "ISO 8859-13" => encoding_rs::ISO_8859_13,
+ "ISO 8859-15" => encoding_rs::ISO_8859_15,
+ "KOI8-R" => encoding_rs::KOI8_R,
+ "KOI8-U" => encoding_rs::KOI8_U,
+ "MacRoman" => encoding_rs::MACINTOSH,
+ "Mac Cyrillic" => encoding_rs::X_MAC_CYRILLIC,
+ "Windows-874" => encoding_rs::WINDOWS_874,
+ "Windows-1253" => encoding_rs::WINDOWS_1253,
+ "Windows-1254" => encoding_rs::WINDOWS_1254,
+ "Windows-1255" => encoding_rs::WINDOWS_1255,
+ "Windows-1256" => encoding_rs::WINDOWS_1256,
+ "Windows-1257" => encoding_rs::WINDOWS_1257,
+ "Windows-1258" => encoding_rs::WINDOWS_1258,
+ "Windows-949" => encoding_rs::EUC_KR,
+ "EUC-JP" => encoding_rs::EUC_JP,
+ "ISO 2022-JP" => encoding_rs::ISO_2022_JP,
+ "GBK" => encoding_rs::GBK,
+ "GB18030" => encoding_rs::GB18030,
+ "Big5" => encoding_rs::BIG5,
+ _ => encoding_rs::UTF_8, // Default to UTF-8 for unknown names
+ };
+
+ Encoding {
+ encoding,
+ with_bom: false,
+ }
+ }
+}
+
+#[derive(Default, Clone)]
+pub struct EncodingOptions {
+ pub expected: Encoding,
+ pub auto_detect: bool,
+}
+
+impl EncodingOptions {
+ pub fn process(&self, bytes: Vec<u8>) -> anyhow::Result<(Encoding, String)> {
+ let encoding = if self.auto_detect
+ && let Some(encoding) = Self::detect(&bytes)
+ {
+ encoding
+ } else {
+ self.expected
+ };
+
+ Ok((encoding, encoding.decode(bytes)?))
+ }
+
+ fn detect(bytes: &[u8]) -> Option<Encoding> {
+ if bytes.starts_with(&[0xFE, 0xFF]) {
+ Some(Encoding {
+ encoding: UTF_8,
+ with_bom: true,
+ })
+ } else if bytes.starts_with(&[0xFF, 0xFE]) {
+ Some(Encoding {
+ encoding: UTF_16LE,
+ with_bom: true,
+ })
+ } else if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
+ Some(Encoding {
+ encoding: UTF_8,
+ with_bom: true,
+ })
+ } else {
+ None
+ }
+ }
+}
@@ -1,188 +0,0 @@
-use encoding_rs;
-use std::{
- fmt::Debug,
- sync::{Arc, Mutex, atomic::AtomicBool},
-};
-
-pub use encoding_rs::{
- BIG5, EUC_JP, EUC_KR, GB18030, GBK, IBM866, ISO_2022_JP, ISO_8859_2, ISO_8859_3, ISO_8859_4,
- ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, ISO_8859_8_I, ISO_8859_10, ISO_8859_13,
- ISO_8859_14, ISO_8859_15, ISO_8859_16, KOI8_R, KOI8_U, MACINTOSH, SHIFT_JIS, UTF_8, UTF_16BE,
- UTF_16LE, WINDOWS_874, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, WINDOWS_1254,
- WINDOWS_1255, WINDOWS_1256, WINDOWS_1257, WINDOWS_1258, X_MAC_CYRILLIC,
-};
-
-pub struct Encoding(Mutex<&'static encoding_rs::Encoding>);
-
-impl Debug for Encoding {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_tuple(&format!("Encoding{:?}", self.0))
- .field(&self.get().name())
- .finish()
- }
-}
-
-impl Clone for Encoding {
- fn clone(&self) -> Self {
- Encoding(Mutex::new(self.get()))
- }
-}
-
-impl Default for Encoding {
- fn default() -> Self {
- Encoding(Mutex::new(UTF_8))
- }
-}
-
-impl From<&'static encoding_rs::Encoding> for Encoding {
- fn from(encoding: &'static encoding_rs::Encoding) -> Self {
- Encoding::new(encoding)
- }
-}
-
-unsafe impl Send for Encoding {}
-unsafe impl Sync for Encoding {}
-
-impl Encoding {
- pub fn new(encoding: &'static encoding_rs::Encoding) -> Self {
- Self(Mutex::new(encoding))
- }
-
- pub fn set(&self, encoding: &'static encoding_rs::Encoding) {
- *self.0.lock().unwrap() = encoding;
- }
-
- pub fn get(&self) -> &'static encoding_rs::Encoding {
- *self.0.lock().unwrap()
- }
-
- pub async fn decode(
- &self,
- input: Vec<u8>,
- force: bool,
- detect_utf16: bool,
- buffer_encoding: Option<Arc<Encoding>>,
- ) -> anyhow::Result<String> {
- // Check if the input starts with a BOM for UTF-16 encodings only if detect_utf16 is true.
- if detect_utf16 {
- if let Some(encoding) = match input.get(..2) {
- Some([0xFF, 0xFE]) => Some(UTF_16LE),
- Some([0xFE, 0xFF]) => Some(UTF_16BE),
- _ => None,
- } {
- self.set(encoding);
-
- if let Some(v) = buffer_encoding {
- v.set(encoding)
- }
- }
- }
-
- let (cow, had_errors) = self.get().decode_with_bom_removal(&input);
-
- if force {
- return Ok(cow.to_string());
- }
-
- if !had_errors {
- Ok(cow.to_string())
- } else {
- Err(anyhow::anyhow!(
- "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.get().name()
- ))
- }
- }
-
- pub async fn encode(&self, input: String) -> anyhow::Result<Vec<u8>> {
- if self.get() == UTF_16BE {
- let mut data = Vec::<u8>::with_capacity(input.len() * 2);
-
- // Convert the input string to UTF-16BE bytes
- let utf16be_bytes = input.encode_utf16().flat_map(|u| u.to_be_bytes());
-
- data.extend(utf16be_bytes);
- return Ok(data);
- } else if self.get() == UTF_16LE {
- let mut data = Vec::<u8>::with_capacity(input.len() * 2);
-
- // Convert the input string to UTF-16LE bytes
- let utf16le_bytes = input.encode_utf16().flat_map(|u| u.to_le_bytes());
-
- data.extend(utf16le_bytes);
- return Ok(data);
- } else {
- let (cow, _encoding_used, _had_errors) = self.get().encode(&input);
-
- Ok(cow.into_owned())
- }
- }
-
- pub fn reset(&self) {
- self.set(UTF_8);
- }
-}
-
-/// Convert a byte vector from a specified encoding to a UTF-8 string.
-pub async fn to_utf8(
- input: Vec<u8>,
- options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
-) -> anyhow::Result<String> {
- options
- .encoding
- .decode(
- input,
- options.force.load(std::sync::atomic::Ordering::Acquire),
- options
- .detect_utf16
- .load(std::sync::atomic::Ordering::Acquire),
- buffer_encoding,
- )
- .await
-}
-
-/// Convert a UTF-8 string to a byte vector in a specified encoding.
-pub async fn from_utf8(input: String, target: Encoding) -> anyhow::Result<Vec<u8>> {
- target.encode(input).await
-}
-
-pub struct EncodingOptions {
- pub encoding: Arc<Encoding>,
- pub force: AtomicBool,
- pub detect_utf16: AtomicBool,
-}
-
-impl EncodingOptions {
- pub fn reset(&self) {
- self.encoding.reset();
-
- self.force
- .store(false, std::sync::atomic::Ordering::Release);
-
- self.detect_utf16
- .store(true, std::sync::atomic::Ordering::Release);
- }
-}
-
-impl Default for EncodingOptions {
- fn default() -> Self {
- EncodingOptions {
- encoding: Arc::new(Encoding::default()),
- force: AtomicBool::new(false),
- detect_utf16: AtomicBool::new(true),
- }
- }
-}
-
-impl Clone for EncodingOptions {
- fn clone(&self) -> Self {
- EncodingOptions {
- encoding: Arc::new(self.encoding.get().into()),
- force: AtomicBool::new(self.force.load(std::sync::atomic::Ordering::Acquire)),
- detect_utf16: AtomicBool::new(
- self.detect_utf16.load(std::sync::atomic::Ordering::Acquire),
- ),
- }
- }
-}
@@ -7,7 +7,7 @@ edition.workspace = true
[dependencies]
anyhow.workspace = true
editor.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
@@ -20,6 +20,9 @@ util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
+[lib]
+path = "src/encodings_ui.rs"
+doctest = false
[lints]
workspace = true
@@ -0,0 +1,108 @@
+//! A crate for handling file encodings in the text editor.
+
+use editor::Editor;
+use gpui::{Entity, Subscription, WeakEntity};
+use language::{Buffer, BufferEvent};
+use ui::{
+ App, Button, ButtonCommon, Context, IntoElement, LabelSize, Render, Tooltip, Window, div,
+};
+use ui::{Clickable, ParentElement};
+use workspace::notifications::NotifyTaskExt;
+use workspace::{ItemHandle, StatusItemView, Workspace};
+use zed_actions::encodings_ui::OpenWithEncoding;
+// use zed_actions::encodings_ui::Toggle;
+
+/// A status bar item that shows the current file encoding and allows changing it.
+pub struct EncodingIndicator {
+ pub buffer: Option<WeakEntity<Buffer>>,
+ pub workspace: WeakEntity<Workspace>,
+ observe_buffer: Option<Subscription>,
+}
+
+pub mod selectors;
+
+impl Render for EncodingIndicator {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
+ let Some(buffer) = self.buffer() else {
+ return gpui::Empty.into_any_element();
+ };
+
+ div()
+ .child(
+ Button::new("encoding", buffer.read(cx).encoding().name())
+ .label_size(LabelSize::Small)
+ .tooltip(Tooltip::text("Select Encoding"))
+ .on_click(cx.listener(move |this, _, window, cx| {
+ let Some(buffer) = this.buffer() else {
+ return;
+ };
+ this.workspace
+ .update(cx, move |workspace, cx| {
+ if buffer.read(cx).file().is_some() {
+ selectors::save_or_reopen(buffer, workspace, window, cx)
+ } else {
+ // todo!()
+ }
+ })
+ .ok();
+ })),
+ )
+ .into_any_element()
+ }
+}
+
+impl EncodingIndicator {
+ pub fn new(workspace: WeakEntity<Workspace>) -> EncodingIndicator {
+ EncodingIndicator {
+ workspace,
+ buffer: None,
+ observe_buffer: None,
+ }
+ }
+
+ fn buffer(&self) -> Option<Entity<Buffer>> {
+ self.buffer.as_ref().and_then(|b| b.upgrade())
+ }
+
+ /// Update the encoding when the `encoding` field of the `Buffer` struct changes.
+ pub fn on_buffer_event(
+ &mut self,
+ _: Entity<Buffer>,
+ e: &BufferEvent,
+ cx: &mut Context<EncodingIndicator>,
+ ) {
+ if matches!(e, BufferEvent::EncodingChanged) {
+ cx.notify();
+ }
+ }
+}
+
+impl StatusItemView for EncodingIndicator {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ _window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx))
+ && let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton()
+ {
+ self.buffer = Some(buffer.downgrade());
+ self.observe_buffer = Some(cx.subscribe(&buffer, Self::on_buffer_event));
+ } else {
+ self.buffer = None;
+ self.observe_buffer = None;
+ }
+ cx.notify();
+ }
+}
+
+pub fn init(cx: &mut App) {
+ cx.observe_new(|workspace: &mut Workspace, _, _| {
+ workspace.register_action(|workspace, action: &OpenWithEncoding, window, cx| {
+ selectors::open_with_encoding(action.0.clone(), workspace, window, cx)
+ .detach_and_notify_err(window, cx);
+ });
+ })
+ .detach();
+}
@@ -1,632 +1,409 @@
-/// This module contains the encoding selectors for saving or reopening files with a different encoding.
-/// It provides a modal view that allows the user to choose between saving with a different encoding
-/// or reopening with a different encoding.
-pub mod save_or_reopen {
- use editor::Editor;
- use gpui::Styled;
- use gpui::{AppContext, ParentElement};
- use picker::Picker;
- use picker::PickerDelegate;
- use std::sync::atomic::AtomicBool;
- use util::ResultExt;
-
- use fuzzy::{StringMatch, StringMatchCandidate};
- use gpui::{DismissEvent, Entity, EventEmitter, Focusable, WeakEntity};
-
- use ui::{Context, HighlightedLabel, ListItem, Render, Window, rems, v_flex};
- use workspace::{ModalView, Workspace};
-
- use crate::selectors::encoding::{Action, EncodingSelector};
-
- /// A modal view that allows the user to select between saving with a different encoding or
- /// reopening with a different encoding.
- pub struct EncodingSaveOrReopenSelector {
- picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
- pub current_selection: usize,
- }
-
- impl EncodingSaveOrReopenSelector {
- pub fn new(
- window: &mut Window,
- cx: &mut Context<EncodingSaveOrReopenSelector>,
- workspace: WeakEntity<Workspace>,
- ) -> Self {
- let delegate = EncodingSaveOrReopenDelegate::new(cx.entity().downgrade(), workspace);
-
- let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
+use anyhow::Result;
+use editor::Editor;
+use encodings::Encoding;
+use encodings::EncodingOptions;
+use futures::channel::oneshot;
+use gpui::ParentElement;
+use gpui::Task;
+use language::Buffer;
+use picker::Picker;
+use picker::PickerDelegate;
+use std::path::Path;
+use std::sync::Arc;
+use std::sync::atomic::AtomicBool;
+use ui::Label;
+use ui::ListItemSpacing;
+use ui::rems;
+use util::ResultExt;
+
+use fuzzy::{StringMatch, StringMatchCandidate};
+use gpui::{DismissEvent, Entity, WeakEntity};
+
+use ui::{Context, HighlightedLabel, ListItem, Window};
+use workspace::Workspace;
+
+pub fn save_or_reopen(
+ buffer: Entity<Buffer>,
+ workspace: &mut Workspace,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+) {
+ let weak_workspace = cx.weak_entity();
+ workspace.toggle_modal(window, cx, |window, cx| {
+ let delegate = EncodingSaveOrReopenDelegate::new(buffer, weak_workspace);
+ Picker::nonsearchable_uniform_list(delegate, window, cx)
+ .modal(true)
+ .width(rems(34.0))
+ })
+}
- Self {
- picker,
- current_selection: 0,
- }
- }
+pub fn open_with_encoding(
+ path: Arc<Path>,
+ workspace: &mut Workspace,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+) -> Task<Result<()>> {
+ let (tx, rx) = oneshot::channel();
+ workspace.toggle_modal(window, cx, |window, cx| {
+ let delegate = EncodingSelectorDelegate::new(None, tx);
+ Picker::uniform_list(delegate, window, cx)
+ });
+ let project = workspace.project().clone();
+ cx.spawn_in(window, async move |workspace, cx| {
+ let encoding = rx.await.unwrap();
+
+ let (worktree, rel_path) = project
+ .update(cx, |project, cx| {
+ project.find_or_create_worktree(path, false, cx)
+ })?
+ .await?;
+
+ let project_path = (worktree.update(cx, |worktree, _| worktree.id())?, rel_path).into();
+
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.buffer_store().update(cx, |buffer_store, cx| {
+ buffer_store.open_buffer(
+ project_path,
+ &EncodingOptions {
+ expected: encoding,
+ auto_detect: true,
+ },
+ cx,
+ )
+ })
+ })?
+ .await?;
+ workspace.update_in(cx, |workspace, window, cx| {
+ workspace.open_project_item::<Editor>(
+ workspace.active_pane().clone(),
+ buffer,
+ true,
+ true,
+ window,
+ cx,
+ )
+ })?;
- /// Toggle the modal view for selecting between saving with a different encoding or
- /// reopening with a different encoding.
- pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
- let weak_workspace = workspace.weak_handle();
- workspace.toggle_modal(window, cx, |window, cx| {
- EncodingSaveOrReopenSelector::new(window, cx, weak_workspace)
- });
- }
- }
+ Ok(())
+ })
+}
- impl Focusable for EncodingSaveOrReopenSelector {
- fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
- self.picker.focus_handle(cx)
+pub fn reopen_with_encoding(
+ buffer: Entity<Buffer>,
+ workspace: &mut Workspace,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+) {
+ let encoding = buffer.read(cx).encoding();
+ let (tx, rx) = oneshot::channel();
+ workspace.toggle_modal(window, cx, |window, cx| {
+ let delegate = EncodingSelectorDelegate::new(Some(encoding), tx);
+ Picker::uniform_list(delegate, window, cx)
+ });
+ cx.spawn(async move |_, cx| {
+ let encoding = rx.await.unwrap();
+
+ let (task, prev) = buffer.update(cx, |buffer, cx| {
+ let prev = buffer.encoding();
+ buffer.set_encoding(encoding, cx);
+ (buffer.reload(cx), prev)
+ })?;
+
+ if task.await.is_err() {
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_encoding(prev, cx);
+ })?;
}
- }
- impl Render for EncodingSaveOrReopenSelector {
- fn render(
- &mut self,
- _window: &mut Window,
- _cx: &mut Context<Self>,
- ) -> impl ui::IntoElement {
- v_flex().w(rems(34.0)).child(self.picker.clone())
- }
- }
+ anyhow::Ok(())
+ })
+ .detach();
+}
- impl ModalView for EncodingSaveOrReopenSelector {}
+pub fn save_with_encoding(
+ buffer: Entity<Buffer>,
+ workspace: &mut Workspace,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+) {
+ let encoding = buffer.read(cx).encoding();
+ let (tx, rx) = oneshot::channel();
+ workspace.toggle_modal(window, cx, |window, cx| {
+ let delegate = EncodingSelectorDelegate::new(Some(encoding), tx);
+ Picker::uniform_list(delegate, window, cx)
+ });
+ cx.spawn(async move |workspace, cx| {
+ let encoding = rx.await.unwrap();
+ workspace
+ .update(cx, |workspace, cx| {
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_encoding(encoding, cx);
+ });
+ workspace
+ .project()
+ .update(cx, |project, cx| project.save_buffer(buffer, cx))
+ })
+ .ok();
+ })
+ .detach();
+}
- impl EventEmitter<DismissEvent> for EncodingSaveOrReopenSelector {}
+pub enum SaveOrReopen {
+ Save,
+ Reopen,
+}
- pub struct EncodingSaveOrReopenDelegate {
- selector: WeakEntity<EncodingSaveOrReopenSelector>,
- current_selection: usize,
- matches: Vec<StringMatch>,
- pub actions: Vec<StringMatchCandidate>,
- workspace: WeakEntity<Workspace>,
- }
+pub struct EncodingSaveOrReopenDelegate {
+ current_selection: usize,
+ actions: Vec<SaveOrReopen>,
+ workspace: WeakEntity<Workspace>,
+ buffer: Entity<Buffer>,
+}
- impl EncodingSaveOrReopenDelegate {
- pub fn new(
- selector: WeakEntity<EncodingSaveOrReopenSelector>,
- workspace: WeakEntity<Workspace>,
- ) -> Self {
- Self {
- selector,
- current_selection: 0,
- matches: Vec::new(),
- actions: vec![
- StringMatchCandidate::new(0, "Save with encoding"),
- StringMatchCandidate::new(1, "Reopen with encoding"),
- ],
- workspace,
- }
+impl EncodingSaveOrReopenDelegate {
+ pub fn new(buffer: Entity<Buffer>, workspace: WeakEntity<Workspace>) -> Self {
+ Self {
+ current_selection: 0,
+ actions: vec![SaveOrReopen::Save, SaveOrReopen::Reopen],
+ workspace,
+ buffer,
}
+ }
+}
- pub fn get_actions(&self) -> (&str, &str) {
- (&self.actions[0].string, &self.actions[1].string)
- }
+impl PickerDelegate for EncodingSaveOrReopenDelegate {
+ type ListItem = ListItem;
- /// Handle the action selected by the user.
- pub fn post_selection(
- &self,
- cx: &mut Context<Picker<EncodingSaveOrReopenDelegate>>,
- window: &mut Window,
- ) -> Option<()> {
- if self.current_selection == 0 {
- if let Some(workspace) = self.workspace.upgrade() {
- let (_, buffer, _) = workspace
- .read(cx)
- .active_item(cx)?
- .act_as::<Editor>(cx)?
- .read(cx)
- .active_excerpt(cx)?;
-
- let weak_workspace = workspace.read(cx).weak_handle();
-
- if let Some(file) = buffer.read(cx).file() {
- let path = file.as_local()?.abs_path(cx);
-
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(window, cx, |window, cx| {
- let selector = EncodingSelector::new(
- window,
- cx,
- Action::Save,
- Some(buffer.downgrade()),
- weak_workspace,
- Some(path),
- );
- selector
- })
- });
- }
- }
- } else if self.current_selection == 1 {
- if let Some(workspace) = self.workspace.upgrade() {
- let (_, buffer, _) = workspace
- .read(cx)
- .active_item(cx)?
- .act_as::<Editor>(cx)?
- .read(cx)
- .active_excerpt(cx)?;
-
- let weak_workspace = workspace.read(cx).weak_handle();
-
- if let Some(file) = buffer.read(cx).file() {
- let path = file.as_local()?.abs_path(cx);
-
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(window, cx, |window, cx| {
- let selector = EncodingSelector::new(
- window,
- cx,
- Action::Reopen,
- Some(buffer.downgrade()),
- weak_workspace,
- Some(path),
- );
- selector
- });
- });
- }
- }
- }
+ fn match_count(&self) -> usize {
+ self.actions.len()
+ }
- Some(())
- }
+ fn selected_index(&self) -> usize {
+ self.current_selection
}
- impl PickerDelegate for EncodingSaveOrReopenDelegate {
- type ListItem = ListItem;
+ fn set_selected_index(
+ &mut self,
+ ix: usize,
+ _window: &mut Window,
+ _cx: &mut Context<Picker<Self>>,
+ ) {
+ self.current_selection = ix;
+ }
- fn match_count(&self) -> usize {
- self.matches.len()
- }
+ fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
+ "Select an action...".into()
+ }
- fn selected_index(&self) -> usize {
- self.current_selection
- }
+ fn update_matches(
+ &mut self,
+ _query: String,
+ _window: &mut Window,
+ _cx: &mut Context<Picker<Self>>,
+ ) -> Task<()> {
+ return Task::ready(());
+ }
- fn set_selected_index(
- &mut self,
- ix: usize,
- _window: &mut Window,
- cx: &mut Context<Picker<Self>>,
- ) {
- self.current_selection = ix;
- self.selector
- .update(cx, |selector, _cx| {
- selector.current_selection = ix;
+ fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ self.dismissed(window, cx);
+ cx.defer_in(window, |this, window, cx| {
+ let this = &this.delegate;
+ this.workspace
+ .update(cx, |workspace, cx| {
+ match this.actions[this.current_selection] {
+ SaveOrReopen::Reopen => {
+ reopen_with_encoding(this.buffer.clone(), workspace, window, cx);
+ }
+ SaveOrReopen::Save => {
+ save_with_encoding(this.buffer.clone(), workspace, window, cx);
+ }
+ }
})
- .log_err();
- }
-
- fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
- "Select an action...".into()
- }
+ .ok();
+ })
+ }
- fn update_matches(
- &mut self,
- query: String,
- window: &mut Window,
- cx: &mut Context<Picker<Self>>,
- ) -> gpui::Task<()> {
- let executor = cx.background_executor().clone();
- let actions = self.actions.clone();
-
- cx.spawn_in(window, async move |this, cx| {
- let matches = if query.is_empty() {
- actions
- .into_iter()
- .enumerate()
- .map(|(index, value)| StringMatch {
- candidate_id: index,
- score: 0.0,
- positions: vec![],
- string: value.string,
- })
- .collect::<Vec<StringMatch>>()
- } else {
- fuzzy::match_strings(
- &actions,
- &query,
- false,
- false,
- 2,
- &AtomicBool::new(false),
- executor,
- )
- .await
- };
+ fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ cx.emit(DismissEvent)
+ }
- this.update(cx, |picker, cx| {
- let delegate = &mut picker.delegate;
- delegate.matches = matches;
- delegate.current_selection = delegate
- .current_selection
- .min(delegate.matches.len().saturating_sub(1));
- delegate
- .selector
- .update(cx, |selector, _cx| {
- selector.current_selection = delegate.current_selection
- })
- .log_err();
- cx.notify();
+ fn render_match(
+ &self,
+ ix: usize,
+ _: bool,
+ _: &mut Window,
+ _: &mut Context<Picker<Self>>,
+ ) -> Option<Self::ListItem> {
+ Some(
+ ListItem::new(ix)
+ .child(match self.actions[ix] {
+ SaveOrReopen::Save => Label::new("Save with encoding"),
+ SaveOrReopen::Reopen => Label::new("Reopen with encoding"),
})
- .log_err();
- })
- }
-
- fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
- self.dismissed(window, cx);
- if self.selector.is_upgradable() {
- self.post_selection(cx, window);
- }
- }
+ .spacing(ui::ListItemSpacing::Sparse),
+ )
+ }
+}
- fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
- self.selector
- .update(cx, |_, cx| cx.emit(DismissEvent))
- .log_err();
- }
+pub struct EncodingSelectorDelegate {
+ current_selection: usize,
+ encodings: Vec<StringMatchCandidate>,
+ matches: Vec<StringMatch>,
+ tx: Option<oneshot::Sender<Encoding>>,
+}
- fn render_match(
- &self,
- ix: usize,
- _: bool,
- _: &mut Window,
- _: &mut Context<Picker<Self>>,
- ) -> Option<Self::ListItem> {
- Some(
- ListItem::new(ix)
- .child(HighlightedLabel::new(
- &self.matches[ix].string,
- self.matches[ix].positions.clone(),
- ))
- .spacing(ui::ListItemSpacing::Sparse),
- )
+impl EncodingSelectorDelegate {
+ pub fn new(
+ encoding: Option<Encoding>,
+ tx: oneshot::Sender<Encoding>,
+ ) -> EncodingSelectorDelegate {
+ let encodings = vec![
+ StringMatchCandidate::new(0, "UTF-8"),
+ StringMatchCandidate::new(1, "UTF-16 LE"),
+ StringMatchCandidate::new(2, "UTF-16 BE"),
+ StringMatchCandidate::new(3, "Windows-1252"),
+ StringMatchCandidate::new(4, "Windows-1251"),
+ StringMatchCandidate::new(5, "Windows-1250"),
+ StringMatchCandidate::new(6, "ISO 8859-2"),
+ StringMatchCandidate::new(7, "ISO 8859-3"),
+ StringMatchCandidate::new(8, "ISO 8859-4"),
+ StringMatchCandidate::new(9, "ISO 8859-5"),
+ StringMatchCandidate::new(10, "ISO 8859-6"),
+ StringMatchCandidate::new(11, "ISO 8859-7"),
+ StringMatchCandidate::new(12, "ISO 8859-8"),
+ StringMatchCandidate::new(13, "ISO 8859-13"),
+ StringMatchCandidate::new(14, "ISO 8859-15"),
+ StringMatchCandidate::new(15, "KOI8-R"),
+ StringMatchCandidate::new(16, "KOI8-U"),
+ StringMatchCandidate::new(17, "MacRoman"),
+ StringMatchCandidate::new(18, "Mac Cyrillic"),
+ StringMatchCandidate::new(19, "Windows-874"),
+ StringMatchCandidate::new(20, "Windows-1253"),
+ StringMatchCandidate::new(21, "Windows-1254"),
+ StringMatchCandidate::new(22, "Windows-1255"),
+ StringMatchCandidate::new(23, "Windows-1256"),
+ StringMatchCandidate::new(24, "Windows-1257"),
+ StringMatchCandidate::new(25, "Windows-1258"),
+ StringMatchCandidate::new(26, "Windows-949"),
+ StringMatchCandidate::new(27, "EUC-JP"),
+ StringMatchCandidate::new(28, "ISO 2022-JP"),
+ StringMatchCandidate::new(29, "GBK"),
+ StringMatchCandidate::new(30, "GB18030"),
+ StringMatchCandidate::new(31, "Big5"),
+ ];
+ let current_selection = if let Some(encoding) = encoding {
+ encodings
+ .iter()
+ .position(|e| encoding.name() == e.string)
+ .unwrap_or_default()
+ } else {
+ 0
+ };
+
+ EncodingSelectorDelegate {
+ current_selection,
+ encodings,
+ matches: Vec::new(),
+ tx: Some(tx),
}
}
}
-/// This module contains the encoding selector for choosing an encoding to save or reopen a file with.
-pub mod encoding {
- use editor::Editor;
- use std::{path::PathBuf, sync::atomic::AtomicBool};
-
- use fuzzy::{StringMatch, StringMatchCandidate};
- use gpui::{
- AppContext, DismissEvent, Entity, EventEmitter, Focusable, WeakEntity, http_client::anyhow,
- };
- use language::Buffer;
- use picker::{Picker, PickerDelegate};
- use ui::{
- Context, HighlightedLabel, ListItem, ListItemSpacing, ParentElement, Render, Styled,
- Window, rems, v_flex,
- };
- use util::ResultExt;
- use workspace::{CloseActiveItem, ModalView, OpenOptions, Workspace};
-
- use crate::encoding_from_name;
-
- /// A modal view that allows the user to select an encoding from a list of encodings.
- pub struct EncodingSelector {
- picker: Entity<Picker<EncodingSelectorDelegate>>,
- workspace: WeakEntity<Workspace>,
- path: Option<PathBuf>,
- }
+impl PickerDelegate for EncodingSelectorDelegate {
+ type ListItem = ListItem;
- pub struct EncodingSelectorDelegate {
- current_selection: usize,
- encodings: Vec<StringMatchCandidate>,
- matches: Vec<StringMatch>,
- selector: WeakEntity<EncodingSelector>,
- buffer: Option<WeakEntity<Buffer>>,
- action: Action,
+ fn match_count(&self) -> usize {
+ self.matches.len()
}
- impl EncodingSelectorDelegate {
- pub fn new(
- selector: WeakEntity<EncodingSelector>,
- buffer: Option<WeakEntity<Buffer>>,
- action: Action,
- ) -> EncodingSelectorDelegate {
- EncodingSelectorDelegate {
- current_selection: 0,
- encodings: vec![
- StringMatchCandidate::new(0, "UTF-8"),
- StringMatchCandidate::new(1, "UTF-16 LE"),
- StringMatchCandidate::new(2, "UTF-16 BE"),
- StringMatchCandidate::new(3, "Windows-1252"),
- StringMatchCandidate::new(4, "Windows-1251"),
- StringMatchCandidate::new(5, "Windows-1250"),
- StringMatchCandidate::new(6, "ISO 8859-2"),
- StringMatchCandidate::new(7, "ISO 8859-3"),
- StringMatchCandidate::new(8, "ISO 8859-4"),
- StringMatchCandidate::new(9, "ISO 8859-5"),
- StringMatchCandidate::new(10, "ISO 8859-6"),
- StringMatchCandidate::new(11, "ISO 8859-7"),
- StringMatchCandidate::new(12, "ISO 8859-8"),
- StringMatchCandidate::new(13, "ISO 8859-13"),
- StringMatchCandidate::new(14, "ISO 8859-15"),
- StringMatchCandidate::new(15, "KOI8-R"),
- StringMatchCandidate::new(16, "KOI8-U"),
- StringMatchCandidate::new(17, "MacRoman"),
- StringMatchCandidate::new(18, "Mac Cyrillic"),
- StringMatchCandidate::new(19, "Windows-874"),
- StringMatchCandidate::new(20, "Windows-1253"),
- StringMatchCandidate::new(21, "Windows-1254"),
- StringMatchCandidate::new(22, "Windows-1255"),
- StringMatchCandidate::new(23, "Windows-1256"),
- StringMatchCandidate::new(24, "Windows-1257"),
- StringMatchCandidate::new(25, "Windows-1258"),
- StringMatchCandidate::new(26, "Windows-949"),
- StringMatchCandidate::new(27, "EUC-JP"),
- StringMatchCandidate::new(28, "ISO 2022-JP"),
- StringMatchCandidate::new(29, "GBK"),
- StringMatchCandidate::new(30, "GB18030"),
- StringMatchCandidate::new(31, "Big5"),
- ],
- matches: Vec::new(),
- selector,
- buffer: buffer,
- action,
- }
- }
+ fn selected_index(&self) -> usize {
+ self.current_selection
}
- impl PickerDelegate for EncodingSelectorDelegate {
- type ListItem = ListItem;
-
- fn match_count(&self) -> usize {
- self.matches.len()
- }
-
- fn selected_index(&self) -> usize {
- self.current_selection
- }
-
- fn set_selected_index(&mut self, ix: usize, _: &mut Window, _: &mut Context<Picker<Self>>) {
- self.current_selection = ix;
- }
+ fn set_selected_index(&mut self, ix: usize, _: &mut Window, _: &mut Context<Picker<Self>>) {
+ self.current_selection = ix;
+ }
- fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
- "Select an encoding...".into()
- }
+ fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
+ "Select an encoding...".into()
+ }
- fn update_matches(
- &mut self,
- query: String,
- window: &mut Window,
- cx: &mut Context<Picker<Self>>,
- ) -> gpui::Task<()> {
- let executor = cx.background_executor().clone();
- let encodings = self.encodings.clone();
-
- cx.spawn_in(window, async move |picker, cx| {
- let matches: Vec<StringMatch>;
-
- if query.is_empty() {
- matches = encodings
- .into_iter()
- .enumerate()
- .map(|(index, value)| StringMatch {
- candidate_id: index,
- score: 0.0,
- positions: Vec::new(),
- string: value.string,
- })
- .collect();
- } else {
- matches = fuzzy::match_strings(
- &encodings,
- &query,
- true,
- false,
- 30,
- &AtomicBool::new(false),
- executor,
- )
- .await
- }
- picker
- .update(cx, |picker, cx| {
- let delegate = &mut picker.delegate;
- delegate.matches = matches;
- delegate.current_selection = delegate
- .current_selection
- .min(delegate.matches.len().saturating_sub(1));
- cx.notify();
+ fn update_matches(
+ &mut self,
+ query: String,
+ window: &mut Window,
+ cx: &mut Context<Picker<Self>>,
+ ) -> Task<()> {
+ let executor = cx.background_executor().clone();
+ let encodings = self.encodings.clone();
+
+ cx.spawn_in(window, async move |picker, cx| {
+ let matches: Vec<StringMatch>;
+
+ if query.is_empty() {
+ matches = encodings
+ .into_iter()
+ .enumerate()
+ .map(|(index, value)| StringMatch {
+ candidate_id: index,
+ score: 0.0,
+ positions: Vec::new(),
+ string: value.string,
})
- .log_err();
- })
- }
-
- fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
- let workspace = self
- .selector
- .upgrade()
- .unwrap()
- .read(cx)
- .workspace
- .upgrade()
- .unwrap();
-
- let weak_workspace = workspace.read(cx).weak_handle();
-
- let current_selection = self.matches[self.current_selection].string.clone();
-
- if let Some(buffer) = &self.buffer
- && let Some(buffer) = buffer.upgrade()
- {
- let path = self
- .selector
- .upgrade()
- .unwrap()
- .read(cx)
- .path
- .clone()
- .unwrap();
-
- let reload = buffer.update(cx, |buffer, cx| buffer.reload(cx));
-
- buffer.update(cx, |buffer, _| {
- buffer.update_encoding(encoding_from_name(¤t_selection).into())
- });
-
- self.dismissed(window, cx);
-
- if self.action == Action::Reopen {
- buffer.update(cx, |_, cx| {
- cx.spawn_in(window, async move |_, cx| {
- if let Err(_) | Ok(None) = reload.await {
- let workspace = weak_workspace.upgrade().unwrap();
-
- workspace
- .update_in(cx, |workspace, window, cx| {
- workspace
- .encoding_options
- .encoding
- .set(encoding_from_name(¤t_selection));
-
- *workspace.encoding_options.force.get_mut() = false;
-
- *workspace.encoding_options.detect_utf16.get_mut() = true;
-
- workspace
- .active_pane()
- .update(cx, |pane, cx| {
- pane.close_active_item(
- &CloseActiveItem::default(),
- window,
- cx,
- )
- })
- .detach();
-
- workspace
- .open_abs_path(path, OpenOptions::default(), window, cx)
- .detach()
- })
- .log_err();
- }
- })
- .detach()
- });
- } else if self.action == Action::Save {
- workspace.update(cx, |workspace, cx| {
- workspace
- .save_active_item(workspace::SaveIntent::Save, window, cx)
- .detach();
- });
- }
+ .collect();
} else {
- if let Some(path) = self.selector.upgrade().unwrap().read(cx).path.clone() {
- workspace.update(cx, |workspace, cx| {
- workspace.active_pane().update(cx, |pane, cx| {
- pane.close_active_item(&CloseActiveItem::default(), window, cx)
- .detach();
- });
- });
-
- let encoding =
- encoding_from_name(self.matches[self.current_selection].string.as_str());
-
- let open_task = workspace.update(cx, |workspace, cx| {
- workspace.encoding_options.encoding.set(encoding);
-
- workspace.open_abs_path(path, OpenOptions::default(), window, cx)
- });
-
- cx.spawn(async move |_, cx| {
- if let Ok(_) = {
- let result = open_task.await;
- workspace
- .update(cx, |workspace, _| {
- *workspace.encoding_options.force.get_mut() = false;
- })
- .unwrap();
-
- result
- } && let Ok(Ok((_, buffer, _))) =
- workspace.read_with(cx, |workspace, cx| {
- if let Some(active_item) = workspace.active_item(cx)
- && let Some(editor) = active_item.act_as::<Editor>(cx)
- {
- Ok(editor.read(cx).active_excerpt(cx).unwrap())
- } else {
- Err(anyhow!("error"))
- }
- })
- {
- buffer
- .update(cx, |buffer, _| buffer.update_encoding(encoding.into()))
- .log_err();
- }
- })
- .detach();
- }
+ matches = fuzzy::match_strings(
+ &encodings,
+ &query,
+ true,
+ false,
+ 30,
+ &AtomicBool::new(false),
+ executor,
+ )
+ .await
}
- }
-
- fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
- self.selector
- .update(cx, |_, cx| cx.emit(DismissEvent))
+ picker
+ .update(cx, |picker, cx| {
+ let delegate = &mut picker.delegate;
+ delegate.matches = matches;
+ delegate.current_selection = delegate
+ .current_selection
+ .min(delegate.matches.len().saturating_sub(1));
+ cx.notify();
+ })
.log_err();
- }
-
- fn render_match(
- &self,
- ix: usize,
- _: bool,
- _: &mut Window,
- _: &mut Context<Picker<Self>>,
- ) -> Option<Self::ListItem> {
- Some(
- ListItem::new(ix)
- .child(HighlightedLabel::new(
- &self.matches[ix].string,
- self.matches[ix].positions.clone(),
- ))
- .spacing(ListItemSpacing::Sparse),
- )
- }
+ })
}
- /// The action to perform after selecting an encoding.
- #[derive(PartialEq, Clone)]
- pub enum Action {
- Save,
- Reopen,
- }
-
- impl EncodingSelector {
- pub fn new(
- window: &mut Window,
- cx: &mut Context<EncodingSelector>,
- action: Action,
- buffer: Option<WeakEntity<Buffer>>,
- workspace: WeakEntity<Workspace>,
- path: Option<PathBuf>,
- ) -> EncodingSelector {
- let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade(), buffer, action);
- let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
-
- EncodingSelector {
- picker,
- workspace,
- path,
- }
+ fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ let current_selection = self.matches[self.current_selection].string.clone();
+ let encoding = Encoding::from_name(¤t_selection);
+ if let Some(tx) = self.tx.take() {
+ tx.send(encoding).log_err();
}
+ self.dismissed(window, cx);
}
- impl EventEmitter<DismissEvent> for EncodingSelector {}
-
- impl Focusable for EncodingSelector {
- fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
- self.picker.focus_handle(cx)
- }
+ fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
+ cx.emit(DismissEvent);
}
- impl ModalView for EncodingSelector {}
-
- impl Render for EncodingSelector {
- fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl ui::IntoElement {
- v_flex().w(rems(34.0)).child(self.picker.clone())
- }
+ fn render_match(
+ &self,
+ ix: usize,
+ _: bool,
+ _: &mut Window,
+ _: &mut Context<Picker<Self>>,
+ ) -> Option<Self::ListItem> {
+ Some(
+ ListItem::new(ix)
+ .child(HighlightedLabel::new(
+ &self.matches[ix].string,
+ self.matches[ix].positions.clone(),
+ ))
+ .spacing(ListItemSpacing::Sparse),
+ )
}
}
@@ -58,7 +58,7 @@ use smol::io::AsyncReadExt;
#[cfg(any(test, feature = "test-support"))]
use std::ffi::OsStr;
-use encodings::{Encoding, EncodingOptions, from_utf8, to_utf8};
+use encodings::{Encoding, EncodingOptions};
#[cfg(any(test, feature = "test-support"))]
pub use fake_git_repo::{LOAD_HEAD_TEXT_TASK, LOAD_INDEX_TEXT_TASK};
@@ -121,9 +121,9 @@ pub trait Fs: Send + Sync {
&self,
path: &Path,
options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
- ) -> Result<String> {
- Ok(to_utf8(self.load_bytes(path).await?, options, buffer_encoding).await?)
+ ) -> Result<(Encoding, String)> {
+ let bytes = self.load_bytes(path).await?;
+ options.process(bytes)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
@@ -689,21 +689,12 @@ impl Fs for RealFs {
let file = smol::fs::File::create(path).await?;
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
- // BOM for UTF-16 is written at the start of the file here because
- // if BOM is written in the `encode` function of `fs::encodings`, it would be written
- // twice. Hence, it is written only here.
- if encoding.get() == encodings::UTF_16BE {
- // Write BOM for UTF-16BE
- writer.write_all(&[0xFE, 0xFF]).await?;
- } else if encoding.get() == encodings::UTF_16LE {
- // Write BOM for UTF-16LE
- writer.write_all(&[0xFF, 0xFE]).await?;
+ if let Some(bom) = encoding.bom() {
+ writer.write_all(bom).await?;
}
for chunk in chunks(text, line_ending) {
- writer
- .write_all(&from_utf8(chunk.to_string(), Encoding::new(encoding.get())).await?)
- .await?
+ writer.write_all(&encoding.encode_chunk(chunk)?).await?
}
writer.flush().await?;
@@ -2424,15 +2415,18 @@ impl Fs for FakeFs {
line_ending: LineEnding,
encoding: Encoding,
) -> Result<()> {
- use encodings::from_utf8;
-
self.simulate_random_delay().await;
let path = normalize_path(path);
let content = chunks(text, line_ending).collect::<String>();
if let Some(path) = path.parent() {
self.create_dir(path).await?;
}
- self.write_file_internal(path, from_utf8(content, encoding).await?, false)?;
+ let mut bytes = Vec::new();
+ if let Some(bom) = encoding.bom() {
+ bytes.extend_from_slice(bom);
+ }
+ bytes.extend_from_slice(&encoding.encode_chunk(&content)?);
+ self.write_file_internal(path, bytes, false)?;
Ok(())
}
@@ -127,7 +127,7 @@ pub struct Buffer {
has_unsaved_edits: Cell<(clock::Global, bool)>,
change_bits: Vec<rc::Weak<Cell<bool>>>,
_subscriptions: Vec<gpui::Subscription>,
- pub encoding: Arc<Encoding>,
+ encoding: Encoding,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -332,6 +332,8 @@ pub enum BufferEvent {
DiagnosticsUpdated,
/// The buffer gained or lost editing capabilities.
CapabilityChanged,
+ /// The buffer's encoding was changed.
+ EncodingChanged,
}
/// The file associated with a buffer.
@@ -418,12 +420,7 @@ pub trait LocalFile: File {
fn abs_path(&self, cx: &App) -> PathBuf;
/// Loads the file contents from disk and returns them as a UTF-8 encoded string.
- fn load(
- &self,
- cx: &App,
- options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
- ) -> Task<Result<String>>;
+ fn load(&self, cx: &App, options: EncodingOptions) -> Task<Result<(Encoding, String)>>;
/// Loads the file's contents from disk.
fn load_bytes(&self, cx: &App) -> Task<Result<Vec<u8>>>;
@@ -1029,7 +1026,7 @@ impl Buffer {
has_conflict: false,
change_bits: Default::default(),
_subscriptions: Vec::new(),
- encoding: Arc::new(Encoding::new(encodings::UTF_8)),
+ encoding: Encoding::default(),
}
}
@@ -1365,11 +1362,6 @@ impl Buffer {
/// Reloads the contents of the buffer from disk.
pub fn reload(&mut self, cx: &Context<Self>) -> oneshot::Receiver<Option<Transaction>> {
let (tx, rx) = futures::channel::oneshot::channel();
- let encoding = (*self.encoding).clone();
-
- let buffer_encoding = self.encoding.clone();
- let options = EncodingOptions::default();
- options.encoding.set(encoding.get());
let prev_version = self.text.version();
self.reload_task = Some(cx.spawn(async move |this, cx| {
@@ -1377,14 +1369,20 @@ impl Buffer {
let file = this.file.as_ref()?.as_local()?;
Some((file.disk_state().mtime(), {
- file.load(cx, &options, Some(buffer_encoding))
+ file.load(
+ cx,
+ EncodingOptions {
+ expected: this.encoding,
+ auto_detect: false,
+ },
+ )
}))
})?
else {
return Ok(());
};
- let new_text = new_text.await?;
+ let (new_encoding, new_text) = new_text.await?;
let diff = this
.update(cx, |this, cx| this.diff(new_text.clone(), cx))?
.await;
@@ -1394,6 +1392,9 @@ impl Buffer {
this.apply_diff(diff, cx);
tx.send(this.finalize_last_transaction().cloned()).ok();
this.has_conflict = false;
+ if new_encoding != this.encoding {
+ this.set_encoding(new_encoding, cx);
+ }
this.did_reload(this.version(), this.line_ending(), new_mtime, cx);
} else {
if !diff.edits.is_empty()
@@ -2935,9 +2936,14 @@ impl Buffer {
!self.has_edits_since(&self.preview_version)
}
+ pub fn encoding(&self) -> Encoding {
+ self.encoding
+ }
+
/// Update the buffer
- pub fn update_encoding(&mut self, encoding: Encoding) {
- self.encoding.set(encoding.get());
+ pub fn set_encoding(&mut self, encoding: Encoding, cx: &mut Context<Self>) {
+ self.encoding = encoding;
+ cx.emit(BufferEvent::EncodingChanged);
}
}
@@ -5260,12 +5266,7 @@ impl LocalFile for TestFile {
.join(self.path.as_std_path())
}
- fn load(
- &self,
- _cx: &App,
- _options: &EncodingOptions,
- _buffer_encoding: Option<Arc<Encoding>>,
- ) -> Task<Result<String>> {
+ fn load(&self, _cx: &App, _options: EncodingOptions) -> Task<Result<(Encoding, String)>> {
unimplemented!()
}
@@ -57,7 +57,7 @@ impl ContextProvider for JsonTaskProvider {
let contents = file
.worktree
.update(cx, |this, cx| {
- this.load_file(&file.path, &Default::default(), None, cx)
+ this.load_file(&file.path, &Default::default(), cx)
})
.ok()?
.await
@@ -1730,7 +1730,9 @@ impl MultiBuffer {
self.capability = buffer.read(cx).capability();
return;
}
- BufferEvent::Operation { .. } | BufferEvent::ReloadNeeded => return,
+ BufferEvent::Operation { .. }
+ | BufferEvent::ReloadNeeded
+ | BufferEvent::EncodingChanged => return,
});
}
@@ -389,7 +389,7 @@ impl LocalBufferStore {
let version = buffer.version();
let buffer_id = buffer.remote_id();
let file = buffer.file().cloned();
- let encoding = buffer.encoding.clone();
+ let encoding = buffer.encoding().clone();
if file
.as_ref()
@@ -399,7 +399,7 @@ impl LocalBufferStore {
}
let save = worktree.update(cx, |worktree, cx| {
- worktree.write_file(path.clone(), text, line_ending, (*encoding).clone(), cx)
+ worktree.write_file(path.clone(), text, line_ending, encoding, cx)
});
cx.spawn(async move |this, cx| {
@@ -528,7 +528,6 @@ impl LocalBufferStore {
path: entry.path.clone(),
worktree: worktree.clone(),
is_private: entry.is_private,
- encoding: None,
}
} else {
File {
@@ -538,7 +537,6 @@ impl LocalBufferStore {
path: old_file.path.clone(),
worktree: worktree.clone(),
is_private: old_file.is_private,
- encoding: None,
}
};
@@ -633,20 +631,19 @@ impl LocalBufferStore {
cx: &mut Context<BufferStore>,
) -> Task<Result<Entity<Buffer>>> {
let options = options.clone();
- let encoding = options.encoding.clone();
let load_buffer = worktree.update(cx, |worktree, cx| {
let reservation = cx.reserve_entity();
let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
- let load_file_task = worktree.load_file(path.as_ref(), &options, None, cx);
+ let load_file_task = worktree.load_file(path.as_ref(), &options, cx);
cx.spawn(async move |_, cx| {
let loaded_file = load_file_task.await?;
let background_executor = cx.background_executor().clone();
- let buffer = cx.insert_entity(reservation, |_| {
- Buffer::build(
+ let buffer = cx.insert_entity(reservation, |cx| {
+ let mut buffer = Buffer::build(
text::Buffer::new(
ReplicaId::LOCAL,
buffer_id,
@@ -655,7 +652,9 @@ impl LocalBufferStore {
),
Some(loaded_file.file),
Capability::ReadWrite,
- )
+ );
+ buffer.set_encoding(loaded_file.encoding, cx);
+ buffer
})?;
Ok(buffer)
@@ -682,7 +681,6 @@ impl LocalBufferStore {
entry_id: None,
is_local: true,
is_private: false,
- encoding: Some(encoding.clone()),
})),
Capability::ReadWrite,
)
@@ -710,10 +708,6 @@ impl LocalBufferStore {
anyhow::Ok(())
})??;
- buffer.update(cx, |buffer, _| {
- buffer.update_encoding(encoding.get().into())
- })?;
-
Ok(buffer)
})
}
@@ -605,7 +605,6 @@ impl LocalImageStore {
path: entry.path.clone(),
worktree: worktree.clone(),
is_private: entry.is_private,
- encoding: None,
}
} else {
worktree::File {
@@ -615,7 +614,6 @@ impl LocalImageStore {
path: old_file.path.clone(),
worktree: worktree.clone(),
is_private: old_file.is_private,
- encoding: None,
}
};
@@ -1,122 +0,0 @@
-use std::{path::Path, sync::Arc};
-
-use gpui::{EventEmitter, FocusHandle, Focusable};
-use ui::{
- App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, InteractiveElement,
- KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render, SharedString, Styled as _,
- TintColor, Window, div, h_flex, v_flex,
-};
-use zed_actions::workspace::OpenWithSystem;
-
-use crate::Item;
-
-/// A view to display when a certain buffer fails to open.
-#[derive(Debug)]
-pub struct InvalidItemView {
- /// Which path was attempted to open.
- pub abs_path: Arc<Path>,
- /// An error message, happened when opening the buffer.
- pub error: SharedString,
- is_local: bool,
- focus_handle: FocusHandle,
-}
-
-impl InvalidItemView {
- pub fn new(
- abs_path: &Path,
- is_local: bool,
- e: &anyhow::Error,
- _: &mut Window,
- cx: &mut App,
- ) -> Self {
- Self {
- is_local,
- abs_path: Arc::from(abs_path),
- error: format!("{}", e.root_cause()).into(),
- focus_handle: cx.focus_handle(),
- }
- }
-}
-
-impl Item for InvalidItemView {
- type Event = ();
-
- fn tab_content_text(&self, mut detail: usize, _: &App) -> SharedString {
- // Ensure we always render at least the filename.
- detail += 1;
-
- let path = self.abs_path.as_ref();
-
- let mut prefix = path;
- while detail > 0 {
- if let Some(parent) = prefix.parent() {
- prefix = parent;
- detail -= 1;
- } else {
- break;
- }
- }
-
- let path = if detail > 0 {
- path
- } else {
- path.strip_prefix(prefix).unwrap_or(path)
- };
-
- SharedString::new(path.to_string_lossy())
- }
-}
-
-impl EventEmitter<()> for InvalidItemView {}
-
-impl Focusable for InvalidItemView {
- fn focus_handle(&self, _: &App) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl Render for InvalidItemView {
- fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
- let abs_path = self.abs_path.clone();
-
- v_flex()
- .size_full()
- .track_focus(&self.focus_handle(cx))
- .flex_none()
- .justify_center()
- .overflow_hidden()
- .key_context("InvalidBuffer")
- .child(
- h_flex().size_full().justify_center().items_center().child(
- v_flex()
- .gap_2()
- .max_w_96()
- .child(h_flex().justify_center().child("Could not open file"))
- .child(
- h_flex().justify_center().child(
- div()
- .whitespace_normal()
- .text_center()
- .child(Label::new(self.error.clone()).size(LabelSize::Small)),
- ),
- )
- .when(self.is_local, |contents| {
- contents.child(
- h_flex().justify_center().child(
- Button::new("open-with-system", "Open in Default App")
- .on_click(move |_, _, cx| {
- cx.open_with_system(&abs_path);
- })
- .style(ButtonStyle::Outlined)
- .key_binding(KeyBinding::for_action(
- &OpenWithSystem,
- window,
- cx,
- )),
- ),
- )
- }),
- ),
- )
- }
-}
@@ -27,7 +27,7 @@ mod environment;
use buffer_diff::BufferDiff;
use context_server_store::ContextServerStore;
-use encodings::{Encoding, EncodingOptions};
+use encodings::Encoding;
pub use environment::ProjectEnvironmentEvent;
use git::repository::get_git_committer;
use git_store::{Repository, RepositoryId};
@@ -218,7 +218,6 @@ pub struct Project {
settings_observer: Entity<SettingsObserver>,
toolchain_store: Option<Entity<ToolchainStore>>,
agent_location: Option<AgentLocation>,
- pub encoding_options: EncodingOptions,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -1229,7 +1228,6 @@ impl Project {
toolchain_store: Some(toolchain_store),
agent_location: None,
- encoding_options: EncodingOptions::default(),
}
})
}
@@ -1415,7 +1413,6 @@ impl Project {
toolchain_store: Some(toolchain_store),
agent_location: None,
- encoding_options: EncodingOptions::default(),
};
// remote server -> local machine handlers
@@ -1669,7 +1666,6 @@ impl Project {
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
toolchain_store: None,
agent_location: None,
- encoding_options: EncodingOptions::default(),
};
project.set_role(role, cx);
@@ -2720,7 +2716,7 @@ impl Project {
}
self.buffer_store.update(cx, |buffer_store, cx| {
- buffer_store.open_buffer(path.into(), &self.encoding_options, cx)
+ buffer_store.open_buffer(path.into(), &Default::default(), cx)
})
}
@@ -5403,7 +5399,7 @@ impl Project {
cx.spawn(async move |cx| {
let file = worktree
.update(cx, |worktree, cx| {
- worktree.load_file(&rel_path, &Default::default(), None, cx)
+ worktree.load_file(&rel_path, &Default::default(), cx)
})?
.await
.context("Failed to load settings file")?;
@@ -12,7 +12,7 @@ use buffer_diff::{
BufferDiffEvent, CALCULATE_DIFF_TASK, DiffHunkSecondaryStatus, DiffHunkStatus,
DiffHunkStatusKind, assert_hunks,
};
-use encodings::{Encoding, UTF_8};
+use encodings::Encoding;
use fs::FakeFs;
use futures::{StreamExt, future};
use git::{
@@ -3878,12 +3878,7 @@ async fn test_rename_file_to_new_directory(cx: &mut gpui::TestAppContext) {
assert_eq!(
worktree
.update(cx, |worktree, cx| {
- worktree.load_file(
- rel_path("dir1/dir2/dir3/test.txt"),
- &Default::default(),
- None,
- cx,
- )
+ worktree.load_file(rel_path("dir1/dir2/dir3/test.txt"), &Default::default(), cx)
})
.await
.unwrap()
@@ -3930,12 +3925,7 @@ async fn test_rename_file_to_new_directory(cx: &mut gpui::TestAppContext) {
assert_eq!(
worktree
.update(cx, |worktree, cx| {
- worktree.load_file(
- rel_path("dir1/dir2/test.txt"),
- &Default::default(),
- None,
- cx,
- )
+ worktree.load_file(rel_path("dir1/dir2/test.txt"), &Default::default(), cx)
})
.await
.unwrap()
@@ -4085,15 +4075,13 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
// the next file change occurs.
cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
- let encoding = Encoding::default();
-
// Change the buffer's file on disk, and then wait for the file change
// to be detected by the worktree, so that the buffer starts reloading.
fs.save(
path!("/dir/file1").as_ref(),
&Rope::from_str("the first contents", cx.background_executor()),
Default::default(),
- encoding.clone(),
+ Default::default(),
)
.await
.unwrap();
@@ -4105,7 +4093,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
path!("/dir/file1").as_ref(),
&Rope::from_str("the second contents", cx.background_executor()),
Default::default(),
- encoding,
+ Default::default(),
)
.await
.unwrap();
@@ -4144,15 +4132,13 @@ async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
// the next file change occurs.
cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
- let encoding = Encoding::new(UTF_8);
-
// Change the buffer's file on disk, and then wait for the file change
// to be detected by the worktree, so that the buffer starts reloading.
fs.save(
path!("/dir/file1").as_ref(),
&Rope::from_str("the first contents", cx.background_executor()),
Default::default(),
- encoding,
+ Default::default(),
)
.await
.unwrap();
@@ -4828,13 +4814,11 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
let (new_contents, new_offsets) =
marked_text_offsets("oneΛ\nthree ΛFOURΛ five\nsixtyΛ seven\n");
- let encoding = Encoding::new(UTF_8);
-
fs.save(
path!("/dir/the-file").as_ref(),
&Rope::from_str(new_contents.as_str(), cx.background_executor()),
LineEnding::Unix,
- encoding,
+ Default::default(),
)
.await
.unwrap();
@@ -4862,14 +4846,12 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
assert!(!buffer.has_conflict());
});
- let encoding = Encoding::new(UTF_8);
-
// Change the file on disk again, adding blank lines to the beginning.
fs.save(
path!("/dir/the-file").as_ref(),
&Rope::from_str("\n\n\nAAAA\naaa\nBB\nbbbbb\n", cx.background_executor()),
LineEnding::Unix,
- encoding,
+ Default::default(),
)
.await
.unwrap();
@@ -4916,15 +4898,13 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
assert_eq!(buffer.line_ending(), LineEnding::Windows);
});
- let encoding = Encoding::new(UTF_8);
-
// Change a file's line endings on disk from unix to windows. The buffer's
// state updates correctly.
fs.save(
path!("/dir/file1").as_ref(),
&Rope::from_str("aaa\nb\nc\n", cx.background_executor()),
LineEnding::Windows,
- encoding,
+ Default::default(),
)
.await
.unwrap();
@@ -9016,7 +8996,6 @@ async fn test_ignored_dirs_events(cx: &mut gpui::TestAppContext) {
tree.load_file(
rel_path("project/target/debug/important_text.txt"),
&Default::default(),
- None,
cx,
)
})
@@ -9179,12 +9158,7 @@ async fn test_odd_events_for_ignored_dirs(
let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
tree.update(cx, |tree, cx| {
- tree.load_file(
- rel_path("target/debug/foo.txt"),
- &Default::default(),
- None,
- cx,
- )
+ tree.load_file(rel_path("target/debug/foo.txt"), &Default::default(), cx)
})
.await
.unwrap();
@@ -1,5 +1,4 @@
use std::{path::Path, sync::Arc};
-use ui::TintColor;
use gpui::{EventEmitter, FocusHandle, Focusable, div};
use ui::{
@@ -79,7 +78,6 @@ impl Render for InvalidItemView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
let abs_path = self.abs_path.clone();
let path0 = self.abs_path.clone();
- let path1 = self.abs_path.clone();
v_flex()
.size_full()
@@ -118,46 +116,25 @@ impl Render for InvalidItemView {
),
)
.child(
- h_flex()
- .justify_center()
- .child(
- Button::new(
- "open-with-encoding",
- "Open With a Different Encoding",
- )
- .style(ButtonStyle::Outlined)
- .on_click(
- move |_, window, cx| {
- window.dispatch_action(
- Box::new(
- zed_actions::encodings_ui::Toggle(
- path0.clone(),
- ),
- ),
- cx,
- )
- },
- ),
+ h_flex().justify_center().child(
+ Button::new(
+ "open-with-encoding",
+ "Try a Different Encoding",
)
- .child(
- Button::new(
- "accept-risk-and-open",
- "Accept the Risk and Open",
- )
- .style(ButtonStyle::Tinted(TintColor::Warning))
- .on_click(
- move |_, window, cx| {
- window.dispatch_action(
- Box::new(
- zed_actions::encodings_ui::ForceOpen(
- path1.clone(),
- ),
+ .style(ButtonStyle::Outlined)
+ .on_click(
+ move |_, window, cx| {
+ window.dispatch_action(
+ Box::new(
+ zed_actions::encodings_ui::OpenWithEncoding(
+ path0.clone(),
),
- cx,
- );
- },
- ),
+ ),
+ cx,
+ )
+ },
),
+ ),
)
}),
),
@@ -19,7 +19,6 @@ mod workspace_settings;
pub use crate::notifications::NotificationFrame;
pub use dock::Panel;
-use encodings::Encoding;
pub use path_list::PathList;
pub use toast_layer::{ToastAction, ToastLayer, ToastView};
@@ -32,7 +31,6 @@ use client::{
};
use collections::{HashMap, HashSet, hash_map};
use dock::{Dock, DockPosition, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};
-use encodings::EncodingOptions;
use futures::{
Future, FutureExt, StreamExt,
@@ -625,7 +623,6 @@ type BuildProjectItemForPathFn =
fn(
&Entity<Project>,
&ProjectPath,
- Option<Encoding>,
&mut Window,
&mut App,
) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
@@ -647,11 +644,8 @@ impl ProjectItemRegistry {
},
);
self.build_project_item_for_path_fns
- .push(|project, project_path, encoding, window, cx| {
+ .push(|project, project_path, window, cx| {
let project_path = project_path.clone();
- let encoding = encoding.unwrap_or_default();
-
- project.update(cx, |project, _| project.encoding_options.encoding.set(encoding.get()));
let is_file = project
.read(cx)
@@ -721,17 +715,14 @@ impl ProjectItemRegistry {
&self,
project: &Entity<Project>,
path: &ProjectPath,
- encoding: Option<Encoding>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
- let Some(open_project_item) =
- self.build_project_item_for_path_fns
- .iter()
- .rev()
- .find_map(|open_project_item| {
- open_project_item(project, path, encoding.clone(), window, cx)
- })
+ let Some(open_project_item) = self
+ .build_project_item_for_path_fns
+ .iter()
+ .rev()
+ .find_map(|open_project_item| open_project_item(project, path, window, cx))
else {
return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
};
@@ -1191,7 +1182,6 @@ pub struct Workspace {
session_id: Option<String>,
scheduled_tasks: Vec<Task<()>>,
last_open_dock_positions: Vec<DockPosition>,
- pub encoding_options: EncodingOptions,
}
impl EventEmitter<Event> for Workspace {}
@@ -1534,7 +1524,6 @@ impl Workspace {
session_id: Some(session_id),
scheduled_tasks: Vec::new(),
last_open_dock_positions: Vec::new(),
- encoding_options: Default::default(),
}
}
@@ -1945,10 +1934,6 @@ impl Workspace {
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<()>> {
- // This is done so that we would get an error when we try to open the file with wrong encoding,
- // and not silently use the previously set encoding.
- self.encoding_options.reset();
-
let to_load = if let Some(pane) = pane.upgrade() {
pane.update(cx, |pane, cx| {
window.focus(&pane.focus_handle(cx));
@@ -3578,25 +3563,8 @@ impl Workspace {
window: &mut Window,
cx: &mut App,
) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
- let project = self.project();
-
- project.update(cx, |project, _| {
- project.encoding_options.force.store(
- self.encoding_options
- .force
- .load(std::sync::atomic::Ordering::Acquire),
- std::sync::atomic::Ordering::Release,
- );
- });
-
let registry = cx.default_global::<ProjectItemRegistry>().clone();
- registry.open_path(
- project,
- &path,
- Some((*self.encoding_options.encoding).clone()),
- window,
- cx,
- )
+ registry.open_path(&self.project, &path, window, cx)
}
pub fn find_project_item<T>(
@@ -7623,8 +7591,6 @@ pub fn create_and_open_local_file(
fs.save(
path,
&default_content(cx),
-
-
Default::default(),
Default::default(),
)
@@ -100,6 +100,7 @@ pub enum CreatedEntry {
pub struct LoadedFile {
pub file: Arc<File>,
+ pub encoding: Encoding,
pub text: String,
}
@@ -708,11 +709,10 @@ impl Worktree {
&self,
path: &RelPath,
options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
cx: &Context<Worktree>,
) -> Task<Result<LoadedFile>> {
match self {
- Worktree::Local(this) => this.load_file(path, options, buffer_encoding, cx),
+ Worktree::Local(this) => this.load_file(path, options, cx),
Worktree::Remote(_) => {
Task::ready(Err(anyhow!("remote worktrees can't yet load files")))
}
@@ -741,7 +741,7 @@ impl Worktree {
cx: &Context<Worktree>,
) -> Task<Result<Arc<File>>> {
match self {
- Worktree::Local(this) => this.write_file(path, text, line_ending, cx, encoding),
+ Worktree::Local(this) => this.write_file(path, text, line_ending, encoding, cx),
Worktree::Remote(_) => {
Task::ready(Err(anyhow!("remote worktree can't yet write files")))
}
@@ -1311,7 +1311,6 @@ impl LocalWorktree {
},
is_local: true,
is_private,
- encoding: None,
})
}
};
@@ -1324,7 +1323,6 @@ impl LocalWorktree {
&self,
path: &RelPath,
options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
cx: &Context<Worktree>,
) -> Task<Result<LoadedFile>> {
let path = Arc::from(path);
@@ -1333,7 +1331,6 @@ impl LocalWorktree {
let entry = self.refresh_entry(path.clone(), None, cx);
let is_private = self.is_path_private(path.as_ref());
let options = options.clone();
- let encoding = options.encoding.clone();
let this = cx.weak_entity();
cx.background_spawn(async move {
@@ -1351,9 +1348,7 @@ impl LocalWorktree {
anyhow::bail!("File is too large to load");
}
}
- let text = fs
- .load_with_encoding(&abs_path, &options, buffer_encoding.clone())
- .await?;
+ let (encoding, text) = fs.load_with_encoding(&abs_path, &options).await?;
let worktree = this.upgrade().context("worktree was dropped")?;
let file = match entry.await? {
@@ -1377,12 +1372,15 @@ impl LocalWorktree {
},
is_local: true,
is_private,
- encoding: Some(encoding),
})
}
};
- Ok(LoadedFile { file, text })
+ Ok(LoadedFile {
+ file,
+ encoding,
+ text,
+ })
})
}
@@ -1465,8 +1463,8 @@ impl LocalWorktree {
path: Arc<RelPath>,
text: Rope,
line_ending: LineEnding,
- cx: &Context<Worktree>,
encoding: Encoding,
+ cx: &Context<Worktree>,
) -> Task<Result<Arc<File>>> {
let fs = self.fs.clone();
let is_private = self.is_path_private(&path);
@@ -1512,7 +1510,6 @@ impl LocalWorktree {
entry_id: None,
is_local: true,
is_private,
- encoding: Some(Arc::new(encoding)),
}))
}
})
@@ -3066,7 +3063,7 @@ impl fmt::Debug for Snapshot {
}
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub struct File {
pub worktree: Entity<Worktree>,
pub path: Arc<RelPath>,
@@ -3074,35 +3071,8 @@ pub struct File {
pub entry_id: Option<ProjectEntryId>,
pub is_local: bool,
pub is_private: bool,
- pub encoding: Option<Arc<Encoding>>,
}
-impl PartialEq for File {
- fn eq(&self, other: &Self) -> bool {
- if self.worktree == other.worktree
- && self.path == other.path
- && self.disk_state == other.disk_state
- && self.entry_id == other.entry_id
- && self.is_local == other.is_local
- && self.is_private == other.is_private
- && (if let Some(encoding) = &self.encoding
- && let Some(other_encoding) = &other.encoding
- {
- if encoding.get() != other_encoding.get() {
- false
- } else {
- true
- }
- } else {
- true
- })
- {
- true
- } else {
- false
- }
- }
-}
impl language::File for File {
fn as_local(&self) -> Option<&dyn language::LocalFile> {
if self.is_local { Some(self) } else { None }
@@ -3149,13 +3119,6 @@ impl language::File for File {
fn path_style(&self, cx: &App) -> PathStyle {
self.worktree.read(cx).path_style()
}
- fn encoding(&self) -> Option<Arc<Encoding>> {
- if let Some(encoding) = &self.encoding {
- Some(encoding.clone())
- } else {
- None
- }
- }
}
impl language::LocalFile for File {
@@ -3163,30 +3126,11 @@ impl language::LocalFile for File {
self.worktree.read(cx).absolutize(&self.path)
}
- fn load(
- &self,
- cx: &App,
- options: &EncodingOptions,
- buffer_encoding: Option<Arc<Encoding>>,
- ) -> Task<Result<String>> {
+ fn load(&self, cx: &App, encoding: EncodingOptions) -> Task<Result<(Encoding, String)>> {
let worktree = self.worktree.read(cx).as_local().unwrap();
let abs_path = worktree.absolutize(&self.path);
let fs = worktree.fs.clone();
- let options = EncodingOptions {
- encoding: options.encoding.clone(),
- force: std::sync::atomic::AtomicBool::new(
- options.force.load(std::sync::atomic::Ordering::Acquire),
- ),
- detect_utf16: std::sync::atomic::AtomicBool::new(
- options
- .detect_utf16
- .load(std::sync::atomic::Ordering::Acquire),
- ),
- };
- cx.background_spawn(async move {
- fs.load_with_encoding(&abs_path, &options, buffer_encoding)
- .await
- })
+ cx.background_spawn(async move { fs.load_with_encoding(&abs_path, &encoding).await })
}
fn load_bytes(&self, cx: &App) -> Task<Result<Vec<u8>>> {
@@ -3210,7 +3154,6 @@ impl File {
entry_id: Some(entry.id),
is_local: true,
is_private: entry.is_private,
- encoding: None,
})
}
@@ -3241,7 +3184,6 @@ impl File {
entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
is_local: false,
is_private: false,
- encoding: None,
})
}
@@ -470,7 +470,6 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
tree.load_file(
rel_path("one/node_modules/b/b1.js"),
&Default::default(),
- None,
cx,
)
})
@@ -515,7 +514,6 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
tree.load_file(
rel_path("one/node_modules/a/a2.js"),
&Default::default(),
- None,
cx,
)
})
@@ -1781,8 +1779,8 @@ fn randomly_mutate_worktree(
entry.path.clone(),
Rope::default(),
Default::default(),
- cx,
Default::default(),
+ cx,
);
cx.background_spawn(async move {
task.await?;
@@ -443,9 +443,8 @@ pub fn initialize_workspace(
}
});
- let encoding_indicator = cx.new(|_cx| {
- encodings_ui::EncodingIndicator::new(None, workspace.weak_handle(), None, None)
- });
+ let encoding_indicator =
+ cx.new(|_cx| encodings_ui::EncodingIndicator::new(workspace.weak_handle()));
let cursor_position =
cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
@@ -307,7 +307,7 @@ pub mod encodings_ui {
use serde::Deserialize;
#[derive(PartialEq, Debug, Clone, Action, JsonSchema, Deserialize)]
- pub struct Toggle(pub Arc<std::path::Path>);
+ pub struct OpenWithEncoding(pub Arc<std::path::Path>);
#[derive(PartialEq, Debug, Clone, Action, JsonSchema, Deserialize)]
pub struct ForceOpen(pub Arc<std::path::Path>);
@@ -1986,7 +1986,7 @@ mod tests {
.worktree_for_root_name("closed_source_worktree", cx)
.unwrap();
worktree2.update(cx, |worktree2, cx| {
- worktree2.load_file(rel_path("main.rs"), &Default::default(), None, cx)
+ worktree2.load_file(rel_path("main.rs"), &Default::default(), cx)
})
})
.await