Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:23:29.132Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

encoding_selector.rs

331 lines · 10.1 KB · rust
1mod active_buffer_encoding;
2pub use active_buffer_encoding::ActiveBufferEncoding;
3
4use editor::Editor;
5use encoding_rs::Encoding;
6use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
7use gpui::{
8    App, AppContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
9    InteractiveElement, ParentElement, Render, Task, WeakEntity, Window, actions,
10};
11use language::Buffer;
12use picker::{Picker, PickerDelegate};
13use std::sync::Arc;
14use ui::{HighlightedLabel, ListItem, ListItemSpacing, Toggleable, v_flex};
15use util::ResultExt;
16use workspace::{ModalView, Toast, Workspace, notifications::NotificationId};
17
18actions!(
19    encoding_selector,
20    [
21        /// Toggles the encoding selector modal.
22        Toggle
23    ]
24);
25
26pub fn init(cx: &mut App) {
27    cx.observe_new(EncodingSelector::register).detach();
28}
29
30pub struct EncodingSelector {
31    picker: Entity<Picker<EncodingSelectorDelegate>>,
32}
33
34impl EncodingSelector {
35    fn register(
36        workspace: &mut Workspace,
37        _window: Option<&mut Window>,
38        _: &mut Context<Workspace>,
39    ) {
40        workspace.register_action(move |workspace, _: &Toggle, window, cx| {
41            Self::toggle(workspace, window, cx);
42        });
43    }
44
45    pub fn toggle(
46        workspace: &mut Workspace,
47        window: &mut Window,
48        cx: &mut Context<Workspace>,
49    ) -> Option<()> {
50        let buffer = workspace
51            .active_item(cx)?
52            .act_as::<Editor>(cx)?
53            .read(cx)
54            .active_buffer(cx)?;
55
56        let buffer_handle = buffer.read(cx);
57        let project = workspace.project().read(cx);
58
59        if buffer_handle.is_dirty() {
60            workspace.show_toast(
61                Toast::new(
62                    NotificationId::unique::<EncodingSelector>(),
63                    "Save file to change encoding",
64                ),
65                cx,
66            );
67            return Some(());
68        }
69        if project.is_shared() {
70            workspace.show_toast(
71                Toast::new(
72                    NotificationId::unique::<EncodingSelector>(),
73                    "Cannot change encoding during collaboration",
74                ),
75                cx,
76            );
77            return Some(());
78        }
79        if project.is_via_remote_server() {
80            workspace.show_toast(
81                Toast::new(
82                    NotificationId::unique::<EncodingSelector>(),
83                    "Cannot change encoding of remote server file",
84                ),
85                cx,
86            );
87            return Some(());
88        }
89
90        workspace.toggle_modal(window, cx, move |window, cx| {
91            EncodingSelector::new(buffer, window, cx)
92        });
93        Some(())
94    }
95
96    fn new(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>) -> Self {
97        let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade(), buffer);
98        let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
99        Self { picker }
100    }
101}
102
103impl Render for EncodingSelector {
104    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl gpui::IntoElement {
105        v_flex()
106            .key_context("EncodingSelector")
107            .child(self.picker.clone())
108    }
109}
110
111impl Focusable for EncodingSelector {
112    fn focus_handle(&self, cx: &App) -> FocusHandle {
113        self.picker.focus_handle(cx)
114    }
115}
116
117impl EventEmitter<DismissEvent> for EncodingSelector {}
118impl ModalView for EncodingSelector {}
119
120pub struct EncodingSelectorDelegate {
121    encoding_selector: WeakEntity<EncodingSelector>,
122    buffer: Entity<Buffer>,
123    encodings: Vec<&'static Encoding>,
124    match_candidates: Arc<Vec<StringMatchCandidate>>,
125    matches: Vec<StringMatch>,
126    selected_index: usize,
127}
128
129impl EncodingSelectorDelegate {
130    fn new(encoding_selector: WeakEntity<EncodingSelector>, buffer: Entity<Buffer>) -> Self {
131        let encodings = available_encodings();
132        let match_candidates = encodings
133            .iter()
134            .enumerate()
135            .map(|(id, enc)| StringMatchCandidate::new(id, enc.name()))
136            .collect::<Vec<_>>();
137        Self {
138            encoding_selector,
139            buffer,
140            encodings,
141            match_candidates: Arc::new(match_candidates),
142            matches: vec![],
143            selected_index: 0,
144        }
145    }
146
147    fn render_data_for_match(&self, mat: &StringMatch, cx: &App) -> String {
148        let candidate_encoding = self.encodings[mat.candidate_id];
149        let current_encoding = self.buffer.read(cx).encoding();
150
151        if candidate_encoding.name() == current_encoding.name() {
152            format!("{} (current)", candidate_encoding.name())
153        } else {
154            candidate_encoding.name().to_string()
155        }
156    }
157}
158
159fn available_encodings() -> Vec<&'static Encoding> {
160    let mut encodings = vec![
161        // Unicode
162        encoding_rs::UTF_8,
163        encoding_rs::UTF_16LE,
164        encoding_rs::UTF_16BE,
165        // Japanese
166        encoding_rs::SHIFT_JIS,
167        encoding_rs::EUC_JP,
168        encoding_rs::ISO_2022_JP,
169        // Chinese
170        encoding_rs::GBK,
171        encoding_rs::GB18030,
172        encoding_rs::BIG5,
173        // Korean
174        encoding_rs::EUC_KR,
175        // Windows / Single Byte Series
176        encoding_rs::WINDOWS_1252, // Western (ISO-8859-1 unified)
177        encoding_rs::WINDOWS_1250, // Central European
178        encoding_rs::WINDOWS_1251, // Cyrillic
179        encoding_rs::WINDOWS_1253, // Greek
180        encoding_rs::WINDOWS_1254, // Turkish (ISO-8859-9 unified)
181        encoding_rs::WINDOWS_1255, // Hebrew
182        encoding_rs::WINDOWS_1256, // Arabic
183        encoding_rs::WINDOWS_1257, // Baltic
184        encoding_rs::WINDOWS_1258, // Vietnamese
185        encoding_rs::WINDOWS_874,  // Thai
186        // ISO-8859 Series (others)
187        encoding_rs::ISO_8859_2,
188        encoding_rs::ISO_8859_3,
189        encoding_rs::ISO_8859_4,
190        encoding_rs::ISO_8859_5,
191        encoding_rs::ISO_8859_6,
192        encoding_rs::ISO_8859_7,
193        encoding_rs::ISO_8859_8,
194        encoding_rs::ISO_8859_8_I, // Logical Hebrew
195        encoding_rs::ISO_8859_10,
196        encoding_rs::ISO_8859_13,
197        encoding_rs::ISO_8859_14,
198        encoding_rs::ISO_8859_15,
199        encoding_rs::ISO_8859_16,
200        // Cyrillic / Legacy Misc
201        encoding_rs::KOI8_R,
202        encoding_rs::KOI8_U,
203        encoding_rs::IBM866,
204        encoding_rs::MACINTOSH,
205        encoding_rs::X_MAC_CYRILLIC,
206        // NOTE: The following encodings are intentionally excluded from the list:
207        //
208        // 1. encoding_rs::REPLACEMENT
209        //    Used internally for decoding errors. Not suitable for user selection.
210        //
211        // 2. encoding_rs::X_USER_DEFINED
212        //    Used for binary data emulation (legacy web behavior). Not for general text editing.
213    ];
214
215    encodings.sort_by_key(|enc| enc.name());
216
217    encodings
218}
219
220impl PickerDelegate for EncodingSelectorDelegate {
221    type ListItem = ListItem;
222
223    fn name() -> &'static str {
224        "encoding selector"
225    }
226
227    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
228        "Reopen with encoding...".into()
229    }
230
231    fn match_count(&self) -> usize {
232        self.matches.len()
233    }
234
235    fn selected_index(&self) -> usize {
236        self.selected_index
237    }
238
239    fn set_selected_index(
240        &mut self,
241        ix: usize,
242        _window: &mut Window,
243        _: &mut Context<Picker<Self>>,
244    ) {
245        self.selected_index = ix;
246    }
247
248    fn update_matches(
249        &mut self,
250        query: String,
251        window: &mut Window,
252        cx: &mut Context<Picker<Self>>,
253    ) -> Task<()> {
254        let background = cx.background_executor().clone();
255        let candidates = self.match_candidates.clone();
256
257        cx.spawn_in(window, async move |this, cx| {
258            let matches = if query.is_empty() {
259                candidates
260                    .iter()
261                    .enumerate()
262                    .map(|(index, candidate)| StringMatch {
263                        candidate_id: index,
264                        string: candidate.string.clone(),
265                        positions: Vec::new(),
266                        score: 0.0,
267                    })
268                    .collect()
269            } else {
270                match_strings(
271                    &candidates,
272                    &query,
273                    false,
274                    true,
275                    100,
276                    &Default::default(),
277                    background,
278                )
279                .await
280            };
281
282            this.update(cx, |this, cx| {
283                let delegate = &mut this.delegate;
284                delegate.matches = matches;
285                delegate.selected_index = delegate
286                    .selected_index
287                    .min(delegate.matches.len().saturating_sub(1));
288                cx.notify();
289            })
290            .log_err();
291        })
292    }
293
294    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
295        if let Some(mat) = self.matches.get(self.selected_index) {
296            let selected_encoding = self.encodings[mat.candidate_id];
297
298            self.buffer.update(cx, |buffer, cx| {
299                let _ = buffer.reload_with_encoding(selected_encoding, cx);
300            });
301        }
302        self.dismissed(window, cx);
303    }
304
305    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
306        self.encoding_selector
307            .update(cx, |_, cx| cx.emit(DismissEvent))
308            .log_err();
309    }
310
311    fn render_match(
312        &self,
313        ix: usize,
314        selected: bool,
315        _: &mut Window,
316        cx: &mut Context<Picker<Self>>,
317    ) -> Option<Self::ListItem> {
318        let mat = &self.matches.get(ix)?;
319
320        let label = self.render_data_for_match(mat, cx);
321
322        Some(
323            ListItem::new(ix)
324                .inset(true)
325                .spacing(ListItemSpacing::Sparse)
326                .toggle_state(selected)
327                .child(HighlightedLabel::new(label, mat.positions.clone())),
328        )
329    }
330}
331
Served at tenant.openagents/omega Member data and write actions are omitted.