Skip to content

Commit 5c5471e

Browse files
b6k-devlhecker
andauthored
Add filtering & Use MIME aliases for the encoding picker (#255)
Closes #26 Co-authored-by: Maciej Bartczak <[email protected]> Co-authored-by: Leonard Hecker <[email protected]>
1 parent 2ea4c32 commit 5c5471e

File tree

4 files changed

+154
-37
lines changed

4 files changed

+154
-37
lines changed

src/bin/edit/draw_statusbar.rs

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use edit::arena::scratch_arena;
45
use edit::framebuffer::{Attributes, IndexedColor};
6+
use edit::fuzzy::score_fuzzy;
57
use edit::helpers::*;
68
use edit::input::vk;
79
use edit::tui::*;
@@ -194,42 +196,62 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
194196
}
195197

196198
pub fn draw_dialog_encoding_change(ctx: &mut Context, state: &mut State) {
197-
let doc = state.documents.active_mut().unwrap();
199+
let encoding = state.documents.active_mut().map_or("", |doc| doc.buffer.borrow().encoding());
198200
let reopen = state.wants_encoding_change == StateEncodingChange::Reopen;
199201
let width = (ctx.size().width - 20).max(10);
200202
let height = (ctx.size().height - 10).max(10);
201203
let mut change = None;
204+
let mut done = encoding.is_empty();
202205

203206
ctx.modal_begin(
204207
"encode",
205208
if reopen { loc(LocId::EncodingReopen) } else { loc(LocId::EncodingConvert) },
206209
);
207210
{
208-
ctx.scrollarea_begin("scrollarea", Size { width, height });
209-
ctx.attr_background_rgba(ctx.indexed_alpha(IndexedColor::Black, 1, 4));
211+
ctx.table_begin("encoding-search");
212+
ctx.table_set_columns(&[0, COORD_TYPE_SAFE_MAX]);
213+
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
210214
ctx.inherit_focus();
211215
{
212-
let encodings = icu::get_available_encodings();
216+
ctx.table_next_row();
217+
ctx.inherit_focus();
218+
219+
ctx.label("needle-label", loc(LocId::SearchNeedleLabel));
220+
221+
if ctx.editline("needle", &mut state.encoding_picker_needle) {
222+
encoding_picker_update_list(state);
223+
}
224+
ctx.inherit_focus();
225+
}
226+
ctx.table_end();
213227

228+
ctx.scrollarea_begin("scrollarea", Size { width, height });
229+
ctx.attr_background_rgba(ctx.indexed_alpha(IndexedColor::Black, 1, 4));
230+
{
214231
ctx.list_begin("encodings");
215232
ctx.inherit_focus();
216-
for &encoding in encodings {
217-
if ctx.list_item(encoding == doc.buffer.borrow().encoding(), encoding)
218-
== ListSelection::Activated
219-
{
220-
change = Some(encoding);
233+
234+
for enc in state
235+
.encoding_picker_results
236+
.as_deref()
237+
.unwrap_or_else(|| icu::get_available_encodings().preferred)
238+
{
239+
if ctx.list_item(enc.canonical == encoding, enc.label) == ListSelection::Activated {
240+
change = Some(enc.canonical);
221241
break;
222242
}
243+
ctx.attr_overflow(Overflow::TruncateTail);
223244
}
224245
ctx.list_end();
225246
}
226247
ctx.scrollarea_end();
227248
}
228-
if ctx.modal_end() {
229-
state.wants_encoding_change = StateEncodingChange::None;
230-
}
249+
done |= ctx.modal_end();
250+
done |= change.is_some();
231251

232-
if let Some(encoding) = change {
252+
if let Some(encoding) = change
253+
&& let Some(doc) = state.documents.active_mut()
254+
{
233255
if reopen && doc.path.is_some() {
234256
let mut res = Ok(());
235257
if doc.buffer.borrow().is_dirty() {
@@ -244,12 +266,41 @@ pub fn draw_dialog_encoding_change(ctx: &mut Context, state: &mut State) {
244266
} else {
245267
doc.buffer.borrow_mut().set_encoding(encoding);
246268
}
269+
}
247270

271+
if done {
248272
state.wants_encoding_change = StateEncodingChange::None;
273+
state.encoding_picker_needle.clear();
274+
state.encoding_picker_results = None;
249275
ctx.needs_rerender();
250276
}
251277
}
252278

279+
fn encoding_picker_update_list(state: &mut State) {
280+
state.encoding_picker_results = None;
281+
282+
let needle = state.encoding_picker_needle.trim_ascii();
283+
if needle.is_empty() {
284+
return;
285+
}
286+
287+
let encodings = icu::get_available_encodings();
288+
let scratch = scratch_arena(None);
289+
let mut matches = Vec::new_in(&*scratch);
290+
291+
for enc in encodings.all {
292+
let local_scratch = scratch_arena(Some(&scratch));
293+
let (score, _) = score_fuzzy(&local_scratch, enc.label, needle, true);
294+
295+
if score > 0 {
296+
matches.push((score, *enc));
297+
}
298+
}
299+
300+
matches.sort_by(|a, b| b.0.cmp(&a.0));
301+
state.encoding_picker_results = Some(Vec::from_iter(matches.iter().map(|(_, enc)| *enc)));
302+
}
303+
253304
pub fn draw_document_picker(ctx: &mut Context, state: &mut State) {
254305
ctx.modal_begin("document-picker", "");
255306
{

src/bin/edit/state.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,12 @@ pub struct State {
144144
pub search_options: buffer::SearchOptions,
145145
pub search_success: bool,
146146

147+
pub wants_encoding_picker: bool,
148+
pub encoding_picker_needle: String,
149+
pub encoding_picker_results: Option<Vec<icu::Encoding>>,
150+
147151
pub wants_save: bool,
148152
pub wants_statusbar_focus: bool,
149-
pub wants_encoding_picker: bool,
150153
pub wants_encoding_change: StateEncodingChange,
151154
pub wants_indentation_picker: bool,
152155
pub wants_document_picker: bool,
@@ -189,9 +192,12 @@ impl State {
189192
search_options: Default::default(),
190193
search_success: true,
191194

195+
wants_encoding_picker: false,
196+
encoding_picker_needle: Default::default(),
197+
encoding_picker_results: Default::default(),
198+
192199
wants_save: false,
193200
wants_statusbar_focus: false,
194-
wants_encoding_picker: false,
195201
wants_encoding_change: StateEncodingChange::None,
196202
wants_indentation_picker: false,
197203
wants_document_picker: false,

src/icu.rs

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,32 @@ use crate::buffer::TextBuffer;
1515
use crate::unicode::Utf8Chars;
1616
use crate::{apperr, arena_format, sys};
1717

18-
static mut ENCODINGS: Vec<&'static str> = Vec::new();
18+
#[derive(Clone, Copy)]
19+
pub struct Encoding {
20+
pub label: &'static str,
21+
pub canonical: &'static str,
22+
}
23+
24+
pub struct Encodings {
25+
pub preferred: &'static [Encoding],
26+
pub all: &'static [Encoding],
27+
}
28+
29+
static mut ENCODINGS: Encodings = Encodings { preferred: &[], all: &[] };
1930

2031
/// Returns a list of encodings ICU supports.
21-
pub fn get_available_encodings() -> &'static [&'static str] {
32+
pub fn get_available_encodings() -> &'static Encodings {
2233
// OnceCell for people that want to put it into a static.
2334
#[allow(static_mut_refs)]
2435
unsafe {
25-
if ENCODINGS.is_empty() {
26-
ENCODINGS.push("UTF-8");
27-
ENCODINGS.push("UTF-8 BOM");
36+
if ENCODINGS.all.is_empty() {
37+
let scratch = scratch_arena(None);
38+
let mut preferred = Vec::new_in(&*scratch);
39+
let mut alternative = Vec::new_in(&*scratch);
40+
41+
// These encodings are always available.
42+
preferred.push(Encoding { label: "UTF-8", canonical: "UTF-8" });
43+
preferred.push(Encoding { label: "UTF-8 BOM", canonical: "UTF-8 BOM" });
2844

2945
if let Ok(f) = init_if_needed() {
3046
let mut n = 0;
@@ -34,17 +50,43 @@ pub fn get_available_encodings() -> &'static [&'static str] {
3450
break;
3551
}
3652

53+
n += 1;
54+
3755
let name = CStr::from_ptr(name).to_str().unwrap_unchecked();
38-
// We have already pushed UTF-8 above.
39-
// There is no need to filter UTF-8 BOM here, since ICU does not distinguish it from UTF-8.
40-
if name != "UTF-8" {
41-
ENCODINGS.push(name);
56+
// We have already pushed UTF-8 above and can skip it.
57+
// There is no need to filter UTF-8 BOM here,
58+
// since ICU does not distinguish it from UTF-8.
59+
if name.is_empty() || name == "UTF-8" {
60+
continue;
4261
}
4362

44-
n += 1;
63+
let mut status = icu_ffi::U_ZERO_ERROR;
64+
let mime = (f.ucnv_getStandardName)(
65+
name.as_ptr(),
66+
c"MIME".as_ptr() as *const _,
67+
&mut status,
68+
);
69+
if !mime.is_null() && status.is_success() {
70+
let mime = CStr::from_ptr(mime).to_str().unwrap_unchecked();
71+
preferred.push(Encoding { label: mime, canonical: name });
72+
} else {
73+
alternative.push(Encoding { label: name, canonical: name });
74+
}
4575
}
4676
}
77+
78+
let preferred_len = preferred.len();
79+
80+
// Combine the preferred and alternative encodings into a single list.
81+
let mut all = Vec::with_capacity(preferred.len() + alternative.len());
82+
all.extend(preferred);
83+
all.extend(alternative);
84+
85+
let all = all.leak();
86+
ENCODINGS.preferred = &all[..preferred_len];
87+
ENCODINGS.all = &all[..];
4788
}
89+
4890
&ENCODINGS
4991
}
5092
}
@@ -827,23 +869,35 @@ pub fn fold_case<'a>(arena: &'a Arena, input: &str) -> ArenaString<'a> {
827869
result
828870
}
829871

872+
// NOTE:
873+
// To keep this neat, fields are ordered by prefix (= `ucol_` before `uregex_`),
874+
// followed by functions in this order:
875+
// * Static methods (e.g. `ucnv_getAvailableName`)
876+
// * Constructors (e.g. `ucnv_open`)
877+
// * Destructors (e.g. `ucnv_close`)
878+
// * Methods, grouped by relationship
879+
// (e.g. `uregex_start64` and `uregex_end64` are near each other)
880+
//
830881
// WARNING:
831882
// The order of the fields MUST match the order of strings in the following two arrays.
832883
#[allow(non_snake_case)]
833884
#[repr(C)]
834885
struct LibraryFunctions {
835886
// LIBICUUC_PROC_NAMES
836887
u_errorName: icu_ffi::u_errorName,
888+
ucasemap_open: icu_ffi::ucasemap_open,
889+
ucasemap_utf8FoldCase: icu_ffi::ucasemap_utf8FoldCase,
837890
ucnv_getAvailableName: icu_ffi::ucnv_getAvailableName,
891+
ucnv_getStandardName: icu_ffi::ucnv_getStandardName,
838892
ucnv_open: icu_ffi::ucnv_open,
839893
ucnv_close: icu_ffi::ucnv_close,
840894
ucnv_convertEx: icu_ffi::ucnv_convertEx,
841-
ucasemap_open: icu_ffi::ucasemap_open,
842-
ucasemap_utf8FoldCase: icu_ffi::ucasemap_utf8FoldCase,
843895
utext_setup: icu_ffi::utext_setup,
844896
utext_close: icu_ffi::utext_close,
845897

846898
// LIBICUI18N_PROC_NAMES
899+
ucol_open: icu_ffi::ucol_open,
900+
ucol_strcollUTF8: icu_ffi::ucol_strcollUTF8,
847901
uregex_open: icu_ffi::uregex_open,
848902
uregex_close: icu_ffi::uregex_close,
849903
uregex_setTimeLimit: icu_ffi::uregex_setTimeLimit,
@@ -852,25 +906,26 @@ struct LibraryFunctions {
852906
uregex_findNext: icu_ffi::uregex_findNext,
853907
uregex_start64: icu_ffi::uregex_start64,
854908
uregex_end64: icu_ffi::uregex_end64,
855-
ucol_open: icu_ffi::ucol_open,
856-
ucol_strcollUTF8: icu_ffi::ucol_strcollUTF8,
857909
}
858910

859-
const LIBICUUC_PROC_NAMES: [&CStr; 9] = [
860-
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
911+
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
912+
const LIBICUUC_PROC_NAMES: [&CStr; 10] = [
861913
c"u_errorName",
914+
c"ucasemap_open",
915+
c"ucasemap_utf8FoldCase",
862916
c"ucnv_getAvailableName",
917+
c"ucnv_getStandardName",
863918
c"ucnv_open",
864919
c"ucnv_close",
865920
c"ucnv_convertEx",
866-
c"ucasemap_open",
867-
c"ucasemap_utf8FoldCase",
868921
c"utext_setup",
869922
c"utext_close",
870923
];
871924

925+
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
872926
const LIBICUI18N_PROC_NAMES: [&CStr; 10] = [
873-
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
927+
c"ucol_open",
928+
c"ucol_strcollUTF8",
874929
c"uregex_open",
875930
c"uregex_close",
876931
c"uregex_setTimeLimit",
@@ -879,8 +934,6 @@ const LIBICUI18N_PROC_NAMES: [&CStr; 10] = [
879934
c"uregex_findNext",
880935
c"uregex_start64",
881936
c"uregex_end64",
882-
c"ucol_open",
883-
c"ucol_strcollUTF8",
884937
];
885938

886939
enum LibraryFunctionsState {
@@ -1020,7 +1073,13 @@ mod icu_ffi {
10201073

10211074
pub struct UConverter;
10221075

1023-
pub type ucnv_getAvailableName = unsafe extern "C" fn(n: i32) -> *mut c_char;
1076+
pub type ucnv_getAvailableName = unsafe extern "C" fn(n: i32) -> *const c_char;
1077+
1078+
pub type ucnv_getStandardName = unsafe extern "C" fn(
1079+
name: *const u8,
1080+
standard: *const u8,
1081+
status: &mut UErrorCode,
1082+
) -> *const c_char;
10241083

10251084
pub type ucnv_open =
10261085
unsafe extern "C" fn(converter_name: *const u8, status: &mut UErrorCode) -> *mut UConverter;

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod buffer;
2222
pub mod cell;
2323
pub mod document;
2424
pub mod framebuffer;
25+
pub mod fuzzy;
2526
pub mod hash;
2627
pub mod helpers;
2728
pub mod icu;

0 commit comments

Comments
 (0)