diff --git a/..gitignore.swp b/..gitignore.swp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Cargo.lock b/Cargo.lock index 8326916c01..0002c8be84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5272,6 +5272,7 @@ name = "encodings" version = "0.1.0" dependencies = [ "editor", + "encoding", "fuzzy", "gpui", "picker", @@ -20516,6 +20517,7 @@ dependencies = [ "diagnostics", "edit_prediction_button", "editor", + "encoding", "encodings", "env_logger 0.11.8", "extension", diff --git a/crates/encodings/Cargo.toml b/crates/encodings/Cargo.toml index 2d75395810..4b8d877a3a 100644 --- a/crates/encodings/Cargo.toml +++ b/crates/encodings/Cargo.toml @@ -12,6 +12,7 @@ picker.workspace = true util.workspace = true fuzzy.workspace = true editor.workspace = true +encoding = "0.2.33" [lints] workspace = true diff --git a/crates/encodings/src/lib.rs b/crates/encodings/src/lib.rs index bfecfceea3..6387919770 100644 --- a/crates/encodings/src/lib.rs +++ b/crates/encodings/src/lib.rs @@ -1,28 +1,16 @@ 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::{Clickable, ParentElement}; use workspace::{ItemHandle, StatusItemView, Workspace}; 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 encoding: Encoding, + pub encoding: Option<&'static dyn Encoding>, pub workspace: WeakEntity, + observe: Option, } pub mod selectors; @@ -49,14 +37,51 @@ impl Render for EncodingIndicator { impl EncodingIndicator { pub fn get_current_encoding(&self, cx: &mut Context, editor: WeakEntity) {} + + pub fn new( + encoding: Option<&'static dyn encoding::Encoding>, + workspace: WeakEntity, + observe: Option, + ) -> EncodingIndicator { + EncodingIndicator { + encoding, + workspace, + observe, + } + } + + pub fn update( + &mut self, + editor: Entity, + _: &mut Window, + cx: &mut Context, + ) { + 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 { fn set_active_pane_item( &mut self, - _active_pane_item: Option<&dyn ItemHandle>, - _window: &mut Window, - _cx: &mut Context, + active_pane_item: Option<&dyn ItemHandle>, + window: &mut Window, + cx: &mut Context, ) { + match active_pane_item.and_then(|item| item.downcast::()) { + Some(editor) => { + self.observe = Some(cx.observe_in(&editor, window, Self::update)); + self.update(editor, window, cx); + } + None => { + self.encoding = None; + self.observe = None; + } + } } } diff --git a/crates/encodings/src/selectors.rs b/crates/encodings/src/selectors.rs index 368f85d584..fd06c518ef 100644 --- a/crates/encodings/src/selectors.rs +++ b/crates/encodings/src/selectors.rs @@ -3,31 +3,49 @@ pub mod save_or_reopen { use gpui::{AppContext, ParentElement}; use picker::Picker; 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 util::ResultExt; use fuzzy::{StringMatch, StringMatchCandidate}; 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 crate::selectors::encoding::{Action, EncodingSelector, EncodingSelectorDelegate}; + pub struct EncodingSaveOrReopenSelector { picker: Entity>, + pub current_selection: usize, + workspace: WeakEntity, } impl EncodingSaveOrReopenSelector { - pub fn new(window: &mut Window, cx: &mut Context) -> Self { - let delegate = EncodingSaveOrReopenDelegate::new(cx.entity().downgrade()); + pub fn new( + window: &mut Window, + cx: &mut Context, + workspace: WeakEntity, + ) -> Self { + let delegate = + EncodingSaveOrReopenDelegate::new(cx.entity().downgrade(), workspace.clone()); 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) { + let weak_workspace = workspace.weak_handle(); 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 for EncodingSaveOrReopenSelector {} pub struct EncodingSaveOrReopenDelegate { - encoding_selector: WeakEntity, + selector: WeakEntity, current_selection: usize, matches: Vec, pub actions: Vec, + workspace: WeakEntity, } impl EncodingSaveOrReopenDelegate { - pub fn new(selector: WeakEntity) -> Self { + pub fn new( + selector: WeakEntity, + workspace: WeakEntity, + ) -> Self { Self { - encoding_selector: selector, + selector, current_selection: 0, matches: Vec::new(), actions: vec![ StringMatchCandidate::new(0, "Save with encoding"), StringMatchCandidate::new(1, "Reopen with encoding"), ], + workspace, } } pub fn get_actions(&self) -> (&str, &str) { (&self.actions[0].string, &self.actions[1].string) } + + pub fn post_selection( + &self, + cx: &mut Context>, + 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 { @@ -92,9 +139,14 @@ pub mod save_or_reopen { &mut self, ix: usize, _window: &mut Window, - _cx: &mut Context>, + cx: &mut Context>, ) { 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 { @@ -137,24 +189,31 @@ pub mod save_or_reopen { this.update(cx, |picker, cx| { let delegate = &mut picker.delegate; - delegate.current_selection = matches.len().saturating_sub(1); 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(); }) .log_err(); }) } - fn confirm( - &mut self, - secondary: bool, - window: &mut Window, - cx: &mut Context>, - ) { + fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context>) { + self.dismissed(window, cx); + if self.selector.is_upgradable() { + self.post_selection(cx, window); + } } fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { - self.encoding_selector + self.selector .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); } @@ -162,11 +221,18 @@ pub mod save_or_reopen { fn render_match( &self, ix: usize, - selected: bool, - window: &mut Window, - cx: &mut Context>, + _: bool, + _: &mut Window, + _: &mut Context>, ) -> Option { - 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 { - use std::sync::atomic::AtomicBool; + use std::{ + ops::DerefMut, + rc::{Rc, Weak}, + sync::{Arc, atomic::AtomicBool}, + }; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, WeakEntity, + AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, Length, + WeakEntity, actions, }; 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 workspace::{ModalView, Workspace}; pub struct EncodingSelector { - pub picker: Entity>, + picker: Entity>, + action: Action, } pub struct EncodingSelectorDelegate { @@ -204,7 +279,44 @@ pub mod encoding { current_selection: 0, encodings: vec![ 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(), selector, @@ -244,7 +356,6 @@ pub mod encoding { ) -> gpui::Task<()> { let executor = cx.background_executor().clone(); let encodings = self.encodings.clone(); - let current_selection = self.current_selection; cx.spawn_in(window, async move |picker, cx| { let matches: Vec; @@ -264,14 +375,25 @@ pub mod encoding { matches = fuzzy::match_strings( &encodings, &query, + true, false, - false, - 0, + 38, &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(); + }) + .log_err(); }) } @@ -296,20 +418,32 @@ pub mod encoding { window: &mut Window, cx: &mut Context>, ) -> Option { - 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 { - pub fn new(window: &mut Window, cx: &mut Context) -> EncodingSelector { + pub fn new( + window: &mut Window, + cx: &mut Context, + action: Action, + ) -> EncodingSelector { let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade()); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); - EncodingSelector { picker: picker } - } - - pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) { - workspace.toggle_modal(window, cx, |window, cx| EncodingSelector::new(window, cx)); + EncodingSelector { picker, action } } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 85c24ef0ac..610e3f4aaf 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -127,7 +127,7 @@ pub struct Buffer { has_unsaved_edits: Cell<(clock::Global, bool)>, change_bits: Vec>>, _subscriptions: Vec, - encoding: &'static dyn encoding::Encoding, + pub encoding: &'static dyn encoding::Encoding, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a91f13ce58..56db114554 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -168,6 +168,7 @@ zed_actions.workspace = true zeta.workspace = true zlog.workspace = true zlog_settings.workspace = true +encoding = "0.2.33" [target.'cfg(target_os = "windows")'.dependencies] windows.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 36189c1110..403fed5978 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -397,10 +397,8 @@ pub fn initialize_workspace( } }); - let encoding_indicator = cx.new(|_cx| encodings::EncodingIndicator { - encoding: encodings::Encoding::Utf8, - workspace: workspace_handle.downgrade(), - }); + let encoding_indicator = + cx.new(|_cx| encodings::EncodingIndicator::new(None, workspace.weak_handle(), None)); let cursor_position = cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));