Implement the actual encoding selector. There are currently only two encodings in the selector used as placeholders, but more will be added in the future. As of now, the encoding picker is not actually triggered.

This commit is contained in:
R Aadarsh 2025-08-23 20:03:26 +05:30
parent fae2f8ff1c
commit 5723987b59
10 changed files with 453 additions and 191 deletions

68
Cargo.lock generated
View file

@ -5194,6 +5194,70 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
"encoding-index-japanese",
"encoding-index-korean",
"encoding-index-simpchinese",
"encoding-index-singlebyte",
"encoding-index-tradchinese",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -5207,9 +5271,9 @@ dependencies = [
name = "encodings" name = "encodings"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"editor",
"fuzzy", "fuzzy",
"gpui", "gpui",
"language",
"picker", "picker",
"ui", "ui",
"util", "util",
@ -6055,6 +6119,7 @@ dependencies = [
"async-trait", "async-trait",
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"encoding",
"fsevent", "fsevent",
"futures 0.3.31", "futures 0.3.31",
"git", "git",
@ -9035,6 +9100,7 @@ dependencies = [
"ctor", "ctor",
"diffy", "diffy",
"ec4rs", "ec4rs",
"encoding",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"fuzzy", "fuzzy",

View file

@ -238,7 +238,6 @@ activity_indicator = { path = "crates/activity_indicator" }
agent_ui = { path = "crates/agent_ui" } agent_ui = { path = "crates/agent_ui" }
agent_settings = { path = "crates/agent_settings" } agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" } agent_servers = { path = "crates/agent_servers" }
ai = { path = "crates/ai" }
ai_onboarding = { path = "crates/ai_onboarding" } ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" } anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" } askpass = { path = "crates/askpass" }
@ -250,7 +249,6 @@ assistant_tool = { path = "crates/assistant_tool" }
assistant_tools = { path = "crates/assistant_tools" } assistant_tools = { path = "crates/assistant_tools" }
audio = { path = "crates/audio" } audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" } auto_update = { path = "crates/auto_update" }
auto_update_helper = { path = "crates/auto_update_helper" }
auto_update_ui = { path = "crates/auto_update_ui" } auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" } aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" } bedrock = { path = "crates/bedrock" }
@ -264,7 +262,6 @@ clock = { path = "crates/clock" }
cloud_api_client = { path = "crates/cloud_api_client" } cloud_api_client = { path = "crates/cloud_api_client" }
cloud_api_types = { path = "crates/cloud_api_types" } cloud_api_types = { path = "crates/cloud_api_types" }
cloud_llm_client = { path = "crates/cloud_llm_client" } cloud_llm_client = { path = "crates/cloud_llm_client" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" } collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" } collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" } command_palette = { path = "crates/command_palette" }
@ -348,8 +345,6 @@ outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" } panel = { path = "crates/panel" }
paths = { path = "crates/paths" } paths = { path = "crates/paths" }
picker = { path = "crates/picker" } picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" } prettier = { path = "crates/prettier" }
settings_profile_selector = { path = "crates/settings_profile_selector" } settings_profile_selector = { path = "crates/settings_profile_selector" }
project = { path = "crates/project" } project = { path = "crates/project" }
@ -370,7 +365,6 @@ rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" } rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" } rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" } search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" } semantic_version = { path = "crates/semantic_version" }
session = { path = "crates/session" } session = { path = "crates/session" }
settings = { path = "crates/settings" } settings = { path = "crates/settings" }
@ -381,7 +375,6 @@ snippets_ui = { path = "crates/snippets_ui" }
sqlez = { path = "crates/sqlez" } sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" } sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" } story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
streaming_diff = { path = "crates/streaming_diff" } streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" } sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" } supermaven = { path = "crates/supermaven" }
@ -397,7 +390,6 @@ terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" } text = { path = "crates/text" }
theme = { path = "crates/theme" } theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" } theme_extension = { path = "crates/theme_extension" }
theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" } theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" } time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" } title_bar = { path = "crates/title_bar" }
@ -469,7 +461,6 @@ ciborium = "0.2"
circular-buffer = "1.0" circular-buffer = "1.0"
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26" cocoa = "0.26"
cocoa-foundation = "0.2.0"
convert_case = "0.8.0" convert_case = "0.8.0"
core-foundation = "0.10.0" core-foundation = "0.10.0"
core-foundation-sys = "0.8.6" core-foundation-sys = "0.8.6"
@ -545,7 +536,6 @@ pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev =
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
portable-pty = "0.9.0" portable-pty = "0.9.0"
@ -668,7 +658,6 @@ wasmtime = { version = "29", default-features = false, features = [
wasmtime-wasi = "29" wasmtime-wasi = "29"
which = "6.0.0" which = "6.0.0"
windows-core = "0.61" windows-core = "0.61"
wit-component = "0.221"
workspace-hack = "0.1.0" workspace-hack = "0.1.0"
yawc = "0.2.5" yawc = "0.2.5"
zstd = "0.11" zstd = "0.11"
@ -742,11 +731,7 @@ codegen-units = 16
[profile.dev.package] [profile.dev.package]
taffy = { opt-level = 3 } taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 } cranelift-codegen = { opt-level = 3 }
cranelift-codegen-meta = { opt-level = 3 }
cranelift-codegen-shared = { opt-level = 3 }
resvg = { opt-level = 3 } resvg = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
wasmtime-cranelift = { opt-level = 3 } wasmtime-cranelift = { opt-level = 3 }
wasmtime = { opt-level = 3 } wasmtime = { opt-level = 3 }
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster # Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
@ -756,7 +741,6 @@ breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 } collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 } command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 } command_palette_hooks = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
feature_flags = { codegen-units = 1 } feature_flags = { codegen-units = 1 }
file_icons = { codegen-units = 1 } file_icons = { codegen-units = 1 }
fsevent = { codegen-units = 1 } fsevent = { codegen-units = 1 }

View file

@ -9,9 +9,9 @@ ui.workspace = true
workspace.workspace = true workspace.workspace = true
gpui.workspace = true gpui.workspace = true
picker.workspace = true picker.workspace = true
language.workspace = true
util.workspace = true util.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
editor.workspace = true
[lints] [lints]
workspace = true workspace = true

View file

@ -1,194 +1,42 @@
use std::sync::Weak; use editor::Editor;
use std::sync::atomic::AtomicBool; use gpui::{ClickEvent, Entity, WeakEntity};
use ui::{Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div};
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 ui::{Clickable, ParentElement};
use util::ResultExt; use workspace::{ItemHandle, StatusItemView, Workspace};
use workspace::{ItemHandle, ModalView, StatusItemView, Workspace};
use crate::selectors::save_or_reopen::{EncodingSaveOrReopenSelector, get_current_encoding};
pub enum Encoding { pub enum Encoding {
Utf8(WeakEntity<Workspace>), Utf8,
Iso8859_1,
} }
impl Encoding { impl Encoding {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match &self { match &self {
Encoding::Utf8(_) => "UTF-8", Encoding::Utf8 => "UTF-8",
Encoding::Iso8859_1 => "ISO 8859-1",
} }
} }
} }
impl EncodingSaveOrReopenSelector { pub struct EncodingIndicator {
pub fn new(window: &mut Window, cx: &mut Context<EncodingSaveOrReopenSelector>) -> Self { pub encoding: Encoding,
let delegate = EncodingSaveOrReopenDelegate::new(cx.entity().downgrade()); pub workspace: WeakEntity<Workspace>,
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 { pub mod selectors;
picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
}
impl Focusable for EncodingSaveOrReopenSelector { impl Render for EncodingIndicator {
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 { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
let encoding_indicator = div(); let status_element = div();
encoding_indicator.child( status_element.child(
Button::new("encoding", get_current_encoding()) Button::new("encoding", get_current_encoding())
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.tooltip(Tooltip::text("Select Encoding")) .tooltip(Tooltip::text("Select Encoding"))
.on_click(cx.listener(|encoding, _: &ClickEvent, window, cx| { .on_click(cx.listener(|indicator, _: &ClickEvent, window, cx| {
if let Some(workspace) = match encoding { if let Some(workspace) = indicator.workspace.upgrade() {
Encoding::Utf8(workspace) => workspace.upgrade(),
} {
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
EncodingSaveOrReopenSelector::toggle(workspace, window, cx) EncodingSaveOrReopenSelector::toggle(workspace, window, cx)
}) })
@ -199,7 +47,11 @@ impl Render for Encoding {
} }
} }
impl StatusItemView for Encoding { impl EncodingIndicator {
pub fn get_current_encoding(&self, cx: &mut Context<Self>, editor: WeakEntity<Editor>) {}
}
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>,

View file

@ -0,0 +1,331 @@
pub mod save_or_reopen {
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, Label, ListItem, Render, Window, rems, v_flex};
use workspace::{ModalView, Workspace};
pub struct EncodingSaveOrReopenSelector {
picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
}
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)
});
}
}
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)))
}
}
pub fn get_current_encoding() -> &'static str {
"UTF-8"
}
}
pub mod encoding {
use std::sync::atomic::AtomicBool;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, WeakEntity,
};
use picker::{Picker, PickerDelegate};
use ui::{Context, Label, ListItem, ParentElement, Render, Styled, Window, rems, v_flex};
use util::{ResultExt, TryFutureExt};
use workspace::{ModalView, Workspace};
pub struct EncodingSelector {
pub picker: Entity<Picker<EncodingSelectorDelegate>>,
}
pub struct EncodingSelectorDelegate {
current_selection: usize,
encodings: Vec<StringMatchCandidate>,
matches: Vec<StringMatch>,
selector: WeakEntity<EncodingSelector>,
}
impl EncodingSelectorDelegate {
pub fn new(selector: WeakEntity<EncodingSelector>) -> EncodingSelectorDelegate {
EncodingSelectorDelegate {
current_selection: 0,
encodings: vec![
StringMatchCandidate::new(0, "UTF-8"),
StringMatchCandidate::new(1, "ISO 8859-1"),
],
matches: Vec::new(),
selector,
}
}
}
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,
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 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();
let current_selection = self.current_selection;
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,
false,
false,
0,
&AtomicBool::new(false),
executor,
)
.await
}
})
}
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.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)))
}
}
impl EncodingSelector {
pub fn new(window: &mut Window, cx: &mut Context<EncodingSelector>) -> 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>) {
workspace.toggle_modal(window, cx, |window, cx| EncodingSelector::new(window, cx));
}
}
impl EventEmitter<DismissEvent> for EncodingSelector {}
impl Focusable for EncodingSelector {
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
cx.focus_handle()
}
}
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())
}
}
}

View file

@ -34,6 +34,8 @@ text.workspace = true
time.workspace = true time.workspace = true
util.workspace = true util.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
encoding = "0.2.33"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
fsevent.workspace = true fsevent.workspace = true

View file

@ -0,0 +1,21 @@
use encoding::Encoding;
pub enum CharacterEncoding {
Utf8,
Iso8859_1,
Cp865,
}
pub fn to_utf8<'a>(input: Vec<u8>, encoding: &'a impl encoding::Encoding) -> String {
match encoding.decode(&input, encoding::DecoderTrap::Strict) {
Ok(v) => return v,
Err(_) => panic!(),
}
}
pub fn to<'a>(input: String, target: &'a impl encoding::Encoding) -> Vec<u8> {
match target.encode(&input, encoding::EncoderTrap::Strict) {
Ok(v) => v,
Err(_) => panic!(),
}
}

View file

@ -70,6 +70,7 @@ util.workspace = true
watch.workspace = true watch.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
diffy = "0.4.2" diffy = "0.4.2"
encoding = "0.2.33"
[dev-dependencies] [dev-dependencies]
collections = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] }

View file

@ -127,6 +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,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -958,6 +959,7 @@ impl Buffer {
has_conflict: false, has_conflict: false,
change_bits: Default::default(), change_bits: Default::default(),
_subscriptions: Vec::new(), _subscriptions: Vec::new(),
encoding: encoding::all::UTF_8,
} }
} }

View file

@ -397,7 +397,10 @@ pub fn initialize_workspace(
} }
}); });
let encoding_indicator = cx.new(|_cx| encodings::Encoding::Utf8(workspace.weak_handle())); let encoding_indicator = cx.new(|_cx| encodings::EncodingIndicator {
encoding: encodings::Encoding::Utf8,
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));