refactor: encoding in EncodingIndicator is now an optional trait object

feat: Add all supported encodings, and open the encoding selector when an action(save or reopen) is chosen.
This commit is contained in:
R Aadarsh 2025-08-24 17:16:02 +05:30
parent 5723987b59
commit 5fec768e37
8 changed files with 221 additions and 60 deletions

0
..gitignore.swp Normal file
View file

2
Cargo.lock generated
View file

@ -5272,6 +5272,7 @@ name = "encodings"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"editor", "editor",
"encoding",
"fuzzy", "fuzzy",
"gpui", "gpui",
"picker", "picker",
@ -20516,6 +20517,7 @@ dependencies = [
"diagnostics", "diagnostics",
"edit_prediction_button", "edit_prediction_button",
"editor", "editor",
"encoding",
"encodings", "encodings",
"env_logger 0.11.8", "env_logger 0.11.8",
"extension", "extension",

View file

@ -12,6 +12,7 @@ picker.workspace = true
util.workspace = true util.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
editor.workspace = true editor.workspace = true
encoding = "0.2.33"
[lints] [lints]
workspace = true workspace = true

View file

@ -1,28 +1,16 @@
use editor::Editor; use editor::Editor;
use gpui::{ClickEvent, Entity, WeakEntity}; use encoding::Encoding;
use gpui::{ClickEvent, Entity, Subscription, WeakEntity};
use ui::{Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div}; use ui::{Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div};
use ui::{Clickable, ParentElement}; use ui::{Clickable, ParentElement};
use workspace::{ItemHandle, StatusItemView, Workspace}; use workspace::{ItemHandle, StatusItemView, Workspace};
use crate::selectors::save_or_reopen::{EncodingSaveOrReopenSelector, get_current_encoding}; use crate::selectors::save_or_reopen::{EncodingSaveOrReopenSelector, get_current_encoding};
pub enum Encoding {
Utf8,
Iso8859_1,
}
impl Encoding {
pub fn as_str(&self) -> &str {
match &self {
Encoding::Utf8 => "UTF-8",
Encoding::Iso8859_1 => "ISO 8859-1",
}
}
}
pub struct EncodingIndicator { pub struct EncodingIndicator {
pub encoding: Encoding, pub encoding: Option<&'static dyn Encoding>,
pub workspace: WeakEntity<Workspace>, pub workspace: WeakEntity<Workspace>,
observe: Option<Subscription>,
} }
pub mod selectors; pub mod selectors;
@ -49,14 +37,51 @@ impl Render for EncodingIndicator {
impl EncodingIndicator { impl EncodingIndicator {
pub fn get_current_encoding(&self, cx: &mut Context<Self>, editor: WeakEntity<Editor>) {} pub fn get_current_encoding(&self, cx: &mut Context<Self>, editor: WeakEntity<Editor>) {}
pub fn new(
encoding: Option<&'static dyn encoding::Encoding>,
workspace: WeakEntity<Workspace>,
observe: Option<Subscription>,
) -> EncodingIndicator {
EncodingIndicator {
encoding,
workspace,
observe,
}
}
pub fn update(
&mut self,
editor: Entity<Editor>,
_: &mut Window,
cx: &mut Context<EncodingIndicator>,
) {
let editor = editor.read(cx);
if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
let encoding = buffer.read(cx).encoding;
self.encoding = Some(encoding);
}
cx.notify();
}
} }
impl StatusItemView for EncodingIndicator { impl StatusItemView for EncodingIndicator {
fn set_active_pane_item( fn set_active_pane_item(
&mut self, &mut self,
_active_pane_item: Option<&dyn ItemHandle>, active_pane_item: Option<&dyn ItemHandle>,
_window: &mut Window, window: &mut Window,
_cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match active_pane_item.and_then(|item| item.downcast::<Editor>()) {
Some(editor) => {
self.observe = Some(cx.observe_in(&editor, window, Self::update));
self.update(editor, window, cx);
}
None => {
self.encoding = None;
self.observe = None;
}
}
} }
} }

View file

@ -3,31 +3,49 @@ pub mod save_or_reopen {
use gpui::{AppContext, ParentElement}; use gpui::{AppContext, ParentElement};
use picker::Picker; use picker::Picker;
use picker::PickerDelegate; use picker::PickerDelegate;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use util::ResultExt; use util::ResultExt;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, WeakEntity}; use gpui::{DismissEvent, Entity, EventEmitter, Focusable, WeakEntity};
use ui::{Context, Label, ListItem, Render, Window, rems, v_flex}; use ui::{Context, HighlightedLabel, Label, ListItem, Render, Window, rems, v_flex};
use workspace::{ModalView, Workspace}; use workspace::{ModalView, Workspace};
use crate::selectors::encoding::{Action, EncodingSelector, EncodingSelectorDelegate};
pub struct EncodingSaveOrReopenSelector { pub struct EncodingSaveOrReopenSelector {
picker: Entity<Picker<EncodingSaveOrReopenDelegate>>, picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
pub current_selection: usize,
workspace: WeakEntity<Workspace>,
} }
impl EncodingSaveOrReopenSelector { impl EncodingSaveOrReopenSelector {
pub fn new(window: &mut Window, cx: &mut Context<EncodingSaveOrReopenSelector>) -> Self { pub fn new(
let delegate = EncodingSaveOrReopenDelegate::new(cx.entity().downgrade()); window: &mut Window,
cx: &mut Context<EncodingSaveOrReopenSelector>,
workspace: WeakEntity<Workspace>,
) -> Self {
let delegate =
EncodingSaveOrReopenDelegate::new(cx.entity().downgrade(), workspace.clone());
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker } Self {
picker,
current_selection: 0,
workspace,
}
} }
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) { 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| { workspace.toggle_modal(window, cx, |window, cx| {
EncodingSaveOrReopenSelector::new(window, cx) EncodingSaveOrReopenSelector::new(window, cx, weak_workspace)
}); });
} }
} }
@ -53,28 +71,57 @@ pub mod save_or_reopen {
impl EventEmitter<DismissEvent> for EncodingSaveOrReopenSelector {} impl EventEmitter<DismissEvent> for EncodingSaveOrReopenSelector {}
pub struct EncodingSaveOrReopenDelegate { pub struct EncodingSaveOrReopenDelegate {
encoding_selector: WeakEntity<EncodingSaveOrReopenSelector>, selector: WeakEntity<EncodingSaveOrReopenSelector>,
current_selection: usize, current_selection: usize,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
pub actions: Vec<StringMatchCandidate>, pub actions: Vec<StringMatchCandidate>,
workspace: WeakEntity<Workspace>,
} }
impl EncodingSaveOrReopenDelegate { impl EncodingSaveOrReopenDelegate {
pub fn new(selector: WeakEntity<EncodingSaveOrReopenSelector>) -> Self { pub fn new(
selector: WeakEntity<EncodingSaveOrReopenSelector>,
workspace: WeakEntity<Workspace>,
) -> Self {
Self { Self {
encoding_selector: selector, selector,
current_selection: 0, current_selection: 0,
matches: Vec::new(), matches: Vec::new(),
actions: vec![ actions: vec![
StringMatchCandidate::new(0, "Save with encoding"), StringMatchCandidate::new(0, "Save with encoding"),
StringMatchCandidate::new(1, "Reopen with encoding"), StringMatchCandidate::new(1, "Reopen with encoding"),
], ],
workspace,
} }
} }
pub fn get_actions(&self) -> (&str, &str) { pub fn get_actions(&self) -> (&str, &str) {
(&self.actions[0].string, &self.actions[1].string) (&self.actions[0].string, &self.actions[1].string)
} }
pub fn post_selection(
&self,
cx: &mut Context<Picker<EncodingSaveOrReopenDelegate>>,
window: &mut Window,
) {
if self.current_selection == 0 {
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
EncodingSelector::new(window, cx, Action::Save)
})
});
}
} else if self.current_selection == 1 {
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
EncodingSelector::new(window, cx, Action::Reopen)
})
});
}
}
}
} }
impl PickerDelegate for EncodingSaveOrReopenDelegate { impl PickerDelegate for EncodingSaveOrReopenDelegate {
@ -92,9 +139,14 @@ pub mod save_or_reopen {
&mut self, &mut self,
ix: usize, ix: usize,
_window: &mut Window, _window: &mut Window,
_cx: &mut Context<Picker<Self>>, cx: &mut Context<Picker<Self>>,
) { ) {
self.current_selection = ix; self.current_selection = ix;
self.selector
.update(cx, |selector, cx| {
selector.current_selection = ix;
})
.log_err();
} }
fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> { fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
@ -137,24 +189,31 @@ pub mod save_or_reopen {
this.update(cx, |picker, cx| { this.update(cx, |picker, cx| {
let delegate = &mut picker.delegate; let delegate = &mut picker.delegate;
delegate.current_selection = matches.len().saturating_sub(1);
delegate.matches = matches; 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(); cx.notify();
}) })
.log_err(); .log_err();
}) })
} }
fn confirm( fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
&mut self, self.dismissed(window, cx);
secondary: bool, if self.selector.is_upgradable() {
window: &mut Window, self.post_selection(cx, window);
cx: &mut Context<Picker<Self>>, }
) {
} }
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) { fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.encoding_selector self.selector
.update(cx, |_, cx| cx.emit(DismissEvent)) .update(cx, |_, cx| cx.emit(DismissEvent))
.log_err(); .log_err();
} }
@ -162,11 +221,18 @@ pub mod save_or_reopen {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
selected: bool, _: bool,
window: &mut Window, _: &mut Window,
cx: &mut Context<Picker<Self>>, _: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
Some(ListItem::new(ix).child(Label::new(&self.matches[ix].string))) Some(
ListItem::new(ix)
.child(HighlightedLabel::new(
&self.matches[ix].string,
self.matches[ix].positions.clone(),
))
.spacing(ui::ListItemSpacing::Sparse),
)
} }
} }
@ -176,19 +242,28 @@ pub mod save_or_reopen {
} }
pub mod encoding { pub mod encoding {
use std::sync::atomic::AtomicBool; use std::{
ops::DerefMut,
rc::{Rc, Weak},
sync::{Arc, atomic::AtomicBool},
};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, WeakEntity, AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, Length,
WeakEntity, actions,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use ui::{Context, Label, ListItem, ParentElement, Render, Styled, Window, rems, v_flex}; use ui::{
Context, DefiniteLength, HighlightedLabel, Label, ListItem, ListItemSpacing, ParentElement,
Render, Styled, Window, rems, v_flex,
};
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::{ModalView, Workspace}; use workspace::{ModalView, Workspace};
pub struct EncodingSelector { pub struct EncodingSelector {
pub picker: Entity<Picker<EncodingSelectorDelegate>>, picker: Entity<Picker<EncodingSelectorDelegate>>,
action: Action,
} }
pub struct EncodingSelectorDelegate { pub struct EncodingSelectorDelegate {
@ -204,7 +279,44 @@ pub mod encoding {
current_selection: 0, current_selection: 0,
encodings: vec![ encodings: vec![
StringMatchCandidate::new(0, "UTF-8"), StringMatchCandidate::new(0, "UTF-8"),
StringMatchCandidate::new(1, "ISO 8859-1"), StringMatchCandidate::new(1, "UTF-16 LE"),
StringMatchCandidate::new(2, "UTF-16 BE"),
StringMatchCandidate::new(3, "IBM866"),
StringMatchCandidate::new(4, "ISO 8859-1"),
StringMatchCandidate::new(5, "ISO 8859-2"),
StringMatchCandidate::new(6, "ISO 8859-3"),
StringMatchCandidate::new(7, "ISO 8859-4"),
StringMatchCandidate::new(8, "ISO 8859-5"),
StringMatchCandidate::new(9, "ISO 8859-6"),
StringMatchCandidate::new(10, "ISO 8859-7"),
StringMatchCandidate::new(11, "ISO 8859-8"),
StringMatchCandidate::new(12, "ISO 8859-10"),
StringMatchCandidate::new(13, "ISO 8859-13"),
StringMatchCandidate::new(14, "ISO 8859-14"),
StringMatchCandidate::new(15, "ISO 8859-15"),
StringMatchCandidate::new(16, "ISO 8859-16"),
StringMatchCandidate::new(17, "KOI8-R"),
StringMatchCandidate::new(18, "KOI8-U"),
StringMatchCandidate::new(19, "MacRoman"),
StringMatchCandidate::new(20, "Mac Cyrillic"),
StringMatchCandidate::new(21, "Windows-874"),
StringMatchCandidate::new(22, "Windows-1250"),
StringMatchCandidate::new(23, "Windows-1251"),
StringMatchCandidate::new(24, "Windows-1252"),
StringMatchCandidate::new(25, "Windows-1253"),
StringMatchCandidate::new(26, "Windows-1254"),
StringMatchCandidate::new(27, "Windows-1255"),
StringMatchCandidate::new(28, "Windows-1256"),
StringMatchCandidate::new(29, "Windows-1257"),
StringMatchCandidate::new(30, "Windows-1258"),
StringMatchCandidate::new(31, "EUC-KR"),
StringMatchCandidate::new(32, "EUC-JP"),
StringMatchCandidate::new(33, "Shift_JIS"),
StringMatchCandidate::new(34, "ISO 2022-JP"),
StringMatchCandidate::new(35, "GBK"),
StringMatchCandidate::new(36, "GB18030"),
StringMatchCandidate::new(37, "Big5"),
StringMatchCandidate::new(38, "HZ-GB-2312"),
], ],
matches: Vec::new(), matches: Vec::new(),
selector, selector,
@ -244,7 +356,6 @@ pub mod encoding {
) -> gpui::Task<()> { ) -> gpui::Task<()> {
let executor = cx.background_executor().clone(); let executor = cx.background_executor().clone();
let encodings = self.encodings.clone(); let encodings = self.encodings.clone();
let current_selection = self.current_selection;
cx.spawn_in(window, async move |picker, cx| { cx.spawn_in(window, async move |picker, cx| {
let matches: Vec<StringMatch>; let matches: Vec<StringMatch>;
@ -264,14 +375,25 @@ pub mod encoding {
matches = fuzzy::match_strings( matches = fuzzy::match_strings(
&encodings, &encodings,
&query, &query,
true,
false, false,
false, 38,
0,
&AtomicBool::new(false), &AtomicBool::new(false),
executor, executor,
) )
.await .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();
})
.log_err();
}) })
} }
@ -296,20 +418,32 @@ pub mod encoding {
window: &mut Window, window: &mut Window,
cx: &mut Context<Picker<Self>>, cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
Some(ListItem::new(ix).child(Label::new(&self.matches[ix].string))) Some(
ListItem::new(ix)
.child(HighlightedLabel::new(
&self.matches[ix].string,
self.matches[ix].positions.clone(),
))
.spacing(ListItemSpacing::Sparse),
)
} }
} }
pub enum Action {
Save,
Reopen,
}
impl EncodingSelector { impl EncodingSelector {
pub fn new(window: &mut Window, cx: &mut Context<EncodingSelector>) -> EncodingSelector { pub fn new(
window: &mut Window,
cx: &mut Context<EncodingSelector>,
action: Action,
) -> EncodingSelector {
let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade()); let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade());
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
EncodingSelector { picker: picker } EncodingSelector { picker, action }
}
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
workspace.toggle_modal(window, cx, |window, cx| EncodingSelector::new(window, cx));
} }
} }

View file

@ -127,7 +127,7 @@ pub struct Buffer {
has_unsaved_edits: Cell<(clock::Global, bool)>, has_unsaved_edits: Cell<(clock::Global, bool)>,
change_bits: Vec<rc::Weak<Cell<bool>>>, change_bits: Vec<rc::Weak<Cell<bool>>>,
_subscriptions: Vec<gpui::Subscription>, _subscriptions: Vec<gpui::Subscription>,
encoding: &'static dyn encoding::Encoding, pub encoding: &'static dyn encoding::Encoding,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]

View file

@ -168,6 +168,7 @@ zed_actions.workspace = true
zeta.workspace = true zeta.workspace = true
zlog.workspace = true zlog.workspace = true
zlog_settings.workspace = true zlog_settings.workspace = true
encoding = "0.2.33"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true windows.workspace = true

View file

@ -397,10 +397,8 @@ pub fn initialize_workspace(
} }
}); });
let encoding_indicator = cx.new(|_cx| encodings::EncodingIndicator { let encoding_indicator =
encoding: encodings::Encoding::Utf8, cx.new(|_cx| encodings::EncodingIndicator::new(None, workspace.weak_handle(), None));
workspace: workspace_handle.downgrade(),
});
let cursor_position = let cursor_position =
cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));