From fae2f8ff1cea01bc234c7cbecd81b4cf5eb29739 Mon Sep 17 00:00:00 2001 From: R Aadarsh Date: Tue, 19 Aug 2025 20:23:02 +0530 Subject: [PATCH] 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 --- Cargo.lock | 14 ++ Cargo.toml | 4 +- crates/encodings/Cargo.toml | 17 +++ crates/encodings/src/lib.rs | 210 ++++++++++++++++++++++++++++++ crates/workspace/src/workspace.rs | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 3 + 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 crates/encodings/Cargo.toml create mode 100644 crates/encodings/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 42649b137f..e6f1aabf84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5203,6 +5203,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "encodings" +version = "0.1.0" +dependencies = [ + "fuzzy", + "gpui", + "language", + "picker", + "ui", + "util", + "workspace", +] + [[package]] name = "endi" version = "1.1.0" @@ -20437,6 +20450,7 @@ dependencies = [ "diagnostics", "edit_prediction_button", "editor", + "encodings", "env_logger 0.11.8", "extension", "extension_host", diff --git a/Cargo.toml b/Cargo.toml index 6ec243a9b9..e4d8638bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/diagnostics", "crates/docs_preprocessor", "crates/editor", + "crates/encodings", "crates/eval", "crates/explorer_command_injector", "crates/extension", @@ -214,7 +215,7 @@ members = [ # "tooling/workspace-hack", - "tooling/xtask", + "tooling/xtask", "crates/encodings", ] default-members = ["crates/zed"] @@ -309,6 +310,7 @@ icons = { path = "crates/icons" } image_viewer = { path = "crates/image_viewer" } edit_prediction = { path = "crates/edit_prediction" } edit_prediction_button = { path = "crates/edit_prediction_button" } +encodings = {path = "crates/encodings"} inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } diff --git a/crates/encodings/Cargo.toml b/crates/encodings/Cargo.toml new file mode 100644 index 0000000000..d49dff06ec --- /dev/null +++ b/crates/encodings/Cargo.toml @@ -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 diff --git a/crates/encodings/src/lib.rs b/crates/encodings/src/lib.rs new file mode 100644 index 0000000000..23b7429526 --- /dev/null +++ b/crates/encodings/src/lib.rs @@ -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), +} + +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) -> 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.toggle_modal(window, cx, |window, cx| { + EncodingSaveOrReopenSelector::new(window, cx) + }); + } +} + +pub struct EncodingSaveOrReopenSelector { + picker: Entity>, +} + +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) -> impl ui::IntoElement { + v_flex().w(rems(34.0)).child(self.picker.clone()) + } +} + +impl ModalView for EncodingSaveOrReopenSelector {} + +impl EventEmitter for EncodingSaveOrReopenSelector {} + +pub struct EncodingSaveOrReopenDelegate { + encoding_selector: WeakEntity, + current_selection: usize, + matches: Vec, + pub actions: Vec, +} + +impl EncodingSaveOrReopenDelegate { + pub fn new(selector: WeakEntity) -> 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>, + ) { + self.current_selection = ix; + } + + fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc { + "Select an action...".into() + } + + fn update_matches( + &mut self, + query: String, + window: &mut Window, + cx: &mut Context>, + ) -> 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::>() + } 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>) {} + + fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { + 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>, + ) -> Option { + 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) -> 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, + ) { + } +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 044601df97..f2828dbc3f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -118,6 +118,7 @@ use crate::persistence::{ model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup}, }; + pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200); static ZED_WINDOW_SIZE: LazyLock>> = LazyLock::new(|| { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6f4ead9ebb..a91f13ce58 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -55,6 +55,7 @@ debugger_tools.workspace = true debugger_ui.workspace = true diagnostics.workspace = true editor.workspace = true +encodings.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 638e1dca0e..e780d9d5a0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -397,6 +397,8 @@ pub fn initialize_workspace( } }); + let encoding_indicator = cx.new(|_cx| encodings::Encoding::Utf8(workspace.weak_handle())); + let cursor_position = cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); 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(vim_mode_indicator, 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); });