Skip to content

Commit ba7d039

Browse files
authored
Do proper Unicode collation on room names (#440)
1 parent 9ed9400 commit ba7d039

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

Cargo.lock

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ clap = {version = "~4.3", features = ["derive"]}
3434
css-color-parser = "0.1.2"
3535
dirs = "4.0.0"
3636
emojis = "0.5"
37+
feruca = "0.10.1"
3738
futures = "0.3"
3839
gethostname = "0.4.1"
3940
html5ever = "0.26.0"

src/base.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,9 @@ pub struct ChatStore {
14861486

14871487
/// Whether the application is currently focused
14881488
pub focused: bool,
1489+
1490+
/// Collator for locale-aware text sorting.
1491+
pub collator: feruca::Collator,
14891492
}
14901493

14911494
impl ChatStore {
@@ -1500,6 +1503,7 @@ impl ChatStore {
15001503
cmds: crate::commands::setup_commands(),
15011504
emojis: emoji_map(),
15021505

1506+
collator: Default::default(),
15031507
names: Default::default(),
15041508
rooms: Default::default(),
15051509
presences: Default::default(),

src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! # Logic for loading and validating application configuration
22
use std::borrow::Cow;
33
use std::collections::hash_map::DefaultHasher;
4-
use std::collections::HashMap;
4+
use std::collections::{BTreeMap, HashMap};
55
use std::fmt;
66
use std::fs::File;
77
use std::hash::{Hash, Hasher};
@@ -105,7 +105,7 @@ fn validate_profile_name(name: &str) -> bool {
105105
name.chars().all(is_profile_char)
106106
}
107107

108-
fn validate_profile_names(names: &HashMap<String, ProfileConfig>) {
108+
fn validate_profile_names(names: &BTreeMap<String, ProfileConfig>) {
109109
for name in names.keys() {
110110
if validate_profile_name(name.as_str()) {
111111
continue;
@@ -787,7 +787,7 @@ pub struct ProfileConfig {
787787

788788
#[derive(Clone, Deserialize)]
789789
pub struct IambConfig {
790-
pub profiles: HashMap<String, ProfileConfig>,
790+
pub profiles: BTreeMap<String, ProfileConfig>,
791791
pub default_profile: Option<String>,
792792
pub settings: Option<Tunables>,
793793
pub dirs: Option<Directories>,

src/windows/mod.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ use crate::base::{
8282

8383
use self::{room::RoomState, welcome::WelcomeState};
8484
use crate::message::MessageTimeStamp;
85+
use feruca::Collator;
8586

8687
pub mod room;
8788
pub mod welcome;
@@ -170,7 +171,12 @@ fn user_cmp(a: &MemberItem, b: &MemberItem, field: &SortFieldUser) -> Ordering {
170171
}
171172
}
172173

173-
fn room_cmp<T: RoomLikeItem>(a: &T, b: &T, field: &SortFieldRoom) -> Ordering {
174+
fn room_cmp<T: RoomLikeItem>(
175+
a: &T,
176+
b: &T,
177+
field: &SortFieldRoom,
178+
collator: &mut Collator,
179+
) -> Ordering {
174180
match field {
175181
SortFieldRoom::Favorite => {
176182
let fava = a.has_tag(TagName::Favorite);
@@ -186,7 +192,7 @@ fn room_cmp<T: RoomLikeItem>(a: &T, b: &T, field: &SortFieldRoom) -> Ordering {
186192
// If a has LowPriority and b doesn't, it should sort later in room list.
187193
lowa.cmp(&lowb)
188194
},
189-
SortFieldRoom::Name => a.name().cmp(b.name()),
195+
SortFieldRoom::Name => collator.collate(a.name(), b.name()),
190196
SortFieldRoom::Alias => some_cmp(a.alias(), b.alias(), Ord::cmp),
191197
SortFieldRoom::RoomId => a.room_id().cmp(b.room_id()),
192198
SortFieldRoom::Unread => {
@@ -209,17 +215,18 @@ fn room_fields_cmp<T: RoomLikeItem>(
209215
a: &T,
210216
b: &T,
211217
fields: &[SortColumn<SortFieldRoom>],
218+
collator: &mut Collator,
212219
) -> Ordering {
213220
for SortColumn(field, order) in fields {
214-
match (room_cmp(a, b, field), order) {
221+
match (room_cmp(a, b, field, collator), order) {
215222
(Ordering::Equal, _) => continue,
216223
(o, SortOrder::Ascending) => return o,
217224
(o, SortOrder::Descending) => return o.reverse(),
218225
}
219226
}
220227

221228
// Break ties on ascending room id.
222-
room_cmp(a, b, &SortFieldRoom::RoomId)
229+
room_cmp(a, b, &SortFieldRoom::RoomId, collator)
223230
}
224231

225232
fn user_fields_cmp(
@@ -516,7 +523,8 @@ impl WindowOps<IambInfo> for IambWindow {
516523
.map(|room_info| DirectItem::new(room_info, store))
517524
.collect::<Vec<_>>();
518525
let fields = &store.application.settings.tunables.sort.dms;
519-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
526+
let collator = &mut store.application.collator;
527+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
520528

521529
state.set(items);
522530

@@ -561,7 +569,8 @@ impl WindowOps<IambInfo> for IambWindow {
561569
.map(|room_info| RoomItem::new(room_info, store))
562570
.collect::<Vec<_>>();
563571
let fields = &store.application.settings.tunables.sort.rooms;
564-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
572+
let collator = &mut store.application.collator;
573+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
565574

566575
state.set(items);
567576

@@ -592,7 +601,8 @@ impl WindowOps<IambInfo> for IambWindow {
592601
items.extend(dms);
593602

594603
let fields = &store.application.settings.tunables.sort.chats;
595-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
604+
let collator = &mut store.application.collator;
605+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
596606

597607
state.set(items);
598608

@@ -625,7 +635,8 @@ impl WindowOps<IambInfo> for IambWindow {
625635
items.extend(dms);
626636

627637
let fields = &store.application.settings.tunables.sort.chats;
628-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
638+
let collator = &mut store.application.collator;
639+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
629640

630641
state.set(items);
631642

@@ -645,7 +656,8 @@ impl WindowOps<IambInfo> for IambWindow {
645656
.map(|room| SpaceItem::new(room, store))
646657
.collect::<Vec<_>>();
647658
let fields = &store.application.settings.tunables.sort.spaces;
648-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
659+
let collator = &mut store.application.collator;
660+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
649661

650662
state.set(items);
651663

@@ -1627,6 +1639,8 @@ mod tests {
16271639

16281640
#[test]
16291641
fn test_sort_rooms() {
1642+
let mut collator = Collator::default();
1643+
let collator = &mut collator;
16301644
let server = server_name!("example.com");
16311645

16321646
let room1 = TestRoomItem {
@@ -1659,13 +1673,13 @@ mod tests {
16591673
// Sort by Name ascending.
16601674
let mut rooms = vec![&room1, &room2, &room3];
16611675
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Ascending)];
1662-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1676+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
16631677
assert_eq!(rooms, vec![&room3, &room2, &room1]);
16641678

16651679
// Sort by Name descending.
16661680
let mut rooms = vec![&room1, &room2, &room3];
16671681
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Descending)];
1668-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1682+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
16691683
assert_eq!(rooms, vec![&room1, &room2, &room3]);
16701684

16711685
// Sort by Favorite and Alias before Name to show order matters.
@@ -1675,7 +1689,7 @@ mod tests {
16751689
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
16761690
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
16771691
];
1678-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1692+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
16791693
assert_eq!(rooms, vec![&room1, &room2, &room3]);
16801694

16811695
// Now flip order of Favorite with Descending
@@ -1685,12 +1699,14 @@ mod tests {
16851699
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
16861700
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
16871701
];
1688-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1702+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
16891703
assert_eq!(rooms, vec![&room2, &room3, &room1]);
16901704
}
16911705

16921706
#[test]
16931707
fn test_sort_room_recents() {
1708+
let mut collator = Collator::default();
1709+
let collator = &mut collator;
16941710
let server = server_name!("example.com");
16951711

16961712
let room1 = TestRoomItem {
@@ -1729,18 +1745,20 @@ mod tests {
17291745
// Sort by Recent ascending.
17301746
let mut rooms = vec![&room1, &room2, &room3];
17311747
let fields = &[SortColumn(SortFieldRoom::Recent, SortOrder::Ascending)];
1732-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1748+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
17331749
assert_eq!(rooms, vec![&room2, &room3, &room1]);
17341750

17351751
// Sort by Recent descending.
17361752
let mut rooms = vec![&room1, &room2, &room3];
17371753
let fields = &[SortColumn(SortFieldRoom::Recent, SortOrder::Descending)];
1738-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1754+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
17391755
assert_eq!(rooms, vec![&room1, &room3, &room2]);
17401756
}
17411757

17421758
#[test]
17431759
fn test_sort_room_invites() {
1760+
let mut collator = Collator::default();
1761+
let collator = &mut collator;
17441762
let server = server_name!("example.com");
17451763

17461764
let room1 = TestRoomItem {
@@ -1776,7 +1794,7 @@ mod tests {
17761794
SortColumn(SortFieldRoom::Invite, SortOrder::Ascending),
17771795
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
17781796
];
1779-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1797+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
17801798
assert_eq!(rooms, vec![&room3, &room1, &room2]);
17811799

17821800
// Sort invites after
@@ -1785,7 +1803,7 @@ mod tests {
17851803
SortColumn(SortFieldRoom::Invite, SortOrder::Descending),
17861804
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
17871805
];
1788-
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
1806+
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
17891807
assert_eq!(rooms, vec![&room1, &room2, &room3]);
17901808
}
17911809
}

src/windows/room/space.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ impl StatefulWidget for Space<'_> {
214214
})
215215
.collect::<Vec<_>>();
216216
let fields = &self.store.application.settings.tunables.sort.rooms;
217-
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
217+
let collator = &mut self.store.application.collator;
218+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
218219

219220
state.list.set(items);
220221
state.last_fetch = Some(Instant::now());

0 commit comments

Comments
 (0)