Add a status indicator to indicate the current file's encoding. When clicked a modal view opens that lets user choose to either reopen or save a file with a particular encoding. The actual implementations are incomplete

This commit is contained in:
R Aadarsh 2025-08-19 20:23:02 +05:30
parent b1b60bb7fe
commit fae2f8ff1c
7 changed files with 249 additions and 1 deletions

14
Cargo.lock generated
View file

@ -5203,6 +5203,19 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "encodings"
version = "0.1.0"
dependencies = [
"fuzzy",
"gpui",
"language",
"picker",
"ui",
"util",
"workspace",
]
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.0" version = "1.1.0"
@ -20437,6 +20450,7 @@ dependencies = [
"diagnostics", "diagnostics",
"edit_prediction_button", "edit_prediction_button",
"editor", "editor",
"encodings",
"env_logger 0.11.8", "env_logger 0.11.8",
"extension", "extension",
"extension_host", "extension_host",

View file

@ -55,6 +55,7 @@ members = [
"crates/diagnostics", "crates/diagnostics",
"crates/docs_preprocessor", "crates/docs_preprocessor",
"crates/editor", "crates/editor",
"crates/encodings",
"crates/eval", "crates/eval",
"crates/explorer_command_injector", "crates/explorer_command_injector",
"crates/extension", "crates/extension",
@ -214,7 +215,7 @@ members = [
# #
"tooling/workspace-hack", "tooling/workspace-hack",
"tooling/xtask", "tooling/xtask", "crates/encodings",
] ]
default-members = ["crates/zed"] default-members = ["crates/zed"]
@ -309,6 +310,7 @@ icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" } image_viewer = { path = "crates/image_viewer" }
edit_prediction = { path = "crates/edit_prediction" } edit_prediction = { path = "crates/edit_prediction" }
edit_prediction_button = { path = "crates/edit_prediction_button" } edit_prediction_button = { path = "crates/edit_prediction_button" }
encodings = {path = "crates/encodings"}
inspector_ui = { path = "crates/inspector_ui" } inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" } install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" } jj = { path = "crates/jj" }

View file

@ -0,0 +1,17 @@
[package]
name = "encodings"
version = "0.1.0"
publish.workspace = true
edition.workspace = true
[dependencies]
ui.workspace = true
workspace.workspace = true
gpui.workspace = true
picker.workspace = true
language.workspace = true
util.workspace = true
fuzzy.workspace = true
[lints]
workspace = true

210
crates/encodings/src/lib.rs Normal file
View file

@ -0,0 +1,210 @@
use std::sync::Weak;
use std::sync::atomic::AtomicBool;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{AppContext, ClickEvent, DismissEvent, Entity, EventEmitter, Focusable, WeakEntity};
use language::Buffer;
use picker::{Picker, PickerDelegate};
use ui::{
Button, ButtonCommon, Context, Label, LabelSize, ListItem, Render, Styled, Tooltip, Window,
div, rems, v_flex,
};
use ui::{Clickable, ParentElement};
use util::ResultExt;
use workspace::{ItemHandle, ModalView, StatusItemView, Workspace};
pub enum Encoding {
Utf8(WeakEntity<Workspace>),
}
impl Encoding {
pub fn as_str(&self) -> &str {
match &self {
Encoding::Utf8(_) => "UTF-8",
}
}
}
impl EncodingSaveOrReopenSelector {
pub fn new(window: &mut Window, cx: &mut Context<EncodingSaveOrReopenSelector>) -> Self {
let delegate = EncodingSaveOrReopenDelegate::new(cx.entity().downgrade());
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
workspace.toggle_modal(window, cx, |window, cx| {
EncodingSaveOrReopenSelector::new(window, cx)
});
}
}
pub struct EncodingSaveOrReopenSelector {
picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
}
impl Focusable for EncodingSaveOrReopenSelector {
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
self.picker.focus_handle(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())
}
}
impl ModalView for EncodingSaveOrReopenSelector {}
impl EventEmitter<DismissEvent> for EncodingSaveOrReopenSelector {}
pub struct EncodingSaveOrReopenDelegate {
encoding_selector: WeakEntity<EncodingSaveOrReopenSelector>,
current_selection: usize,
matches: Vec<StringMatch>,
pub actions: Vec<StringMatchCandidate>,
}
impl EncodingSaveOrReopenDelegate {
pub fn new(selector: WeakEntity<EncodingSaveOrReopenSelector>) -> Self {
Self {
encoding_selector: selector,
current_selection: 0,
matches: Vec::new(),
actions: vec![
StringMatchCandidate::new(0, "Save with encoding"),
StringMatchCandidate::new(1, "Reopen with encoding"),
],
}
}
pub fn get_actions(&self) -> (&str, &str) {
(&self.actions[0].string, &self.actions[1].string)
}
}
impl PickerDelegate for EncodingSaveOrReopenDelegate {
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,
_window: &mut Window,
_cx: &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 action...".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 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
};
this.update(cx, |picker, cx| {
let delegate = &mut picker.delegate;
delegate.current_selection = matches.len().saturating_sub(1);
delegate.matches = matches;
cx.notify();
})
.log_err();
})
}
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.encoding_selector
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
Some(ListItem::new(ix).child(Label::new(&self.matches[ix].string)))
}
}
fn get_current_encoding() -> &'static str {
"UTF-8"
}
impl Render for Encoding {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
let encoding_indicator = div();
encoding_indicator.child(
Button::new("encoding", get_current_encoding())
.label_size(LabelSize::Small)
.tooltip(Tooltip::text("Select Encoding"))
.on_click(cx.listener(|encoding, _: &ClickEvent, window, cx| {
if let Some(workspace) = match encoding {
Encoding::Utf8(workspace) => workspace.upgrade(),
} {
workspace.update(cx, |workspace, cx| {
EncodingSaveOrReopenSelector::toggle(workspace, window, cx)
})
} else {
}
})),
)
}
}
impl StatusItemView for Encoding {
fn set_active_pane_item(
&mut self,
_active_pane_item: Option<&dyn ItemHandle>,
_window: &mut Window,
_cx: &mut Context<Self>,
) {
}
}

View file

@ -118,6 +118,7 @@ use crate::persistence::{
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup}, model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
}; };
pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200); pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| { static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {

View file

@ -55,6 +55,7 @@ debugger_tools.workspace = true
debugger_ui.workspace = true debugger_ui.workspace = true
diagnostics.workspace = true diagnostics.workspace = true
editor.workspace = true editor.workspace = true
encodings.workspace = true
env_logger.workspace = true env_logger.workspace = true
extension.workspace = true extension.workspace = true
extension_host.workspace = true extension_host.workspace = true

View file

@ -397,6 +397,8 @@ pub fn initialize_workspace(
} }
}); });
let encoding_indicator = cx.new(|_cx| encodings::Encoding::Utf8(workspace.weak_handle()));
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));
workspace.status_bar().update(cx, |status_bar, cx| { workspace.status_bar().update(cx, |status_bar, cx| {
@ -409,6 +411,7 @@ pub fn initialize_workspace(
status_bar.add_right_item(active_toolchain_language, window, cx); status_bar.add_right_item(active_toolchain_language, window, cx);
status_bar.add_right_item(vim_mode_indicator, window, cx); status_bar.add_right_item(vim_mode_indicator, window, cx);
status_bar.add_right_item(cursor_position, window, cx); status_bar.add_right_item(cursor_position, window, cx);
status_bar.add_right_item(encoding_indicator, window, cx);
status_bar.add_right_item(image_info, window, cx); status_bar.add_right_item(image_info, window, cx);
}); });