Skip to content

Commit 0041e69

Browse files
committed
Add space tree on :spaces!
1 parent ba7d039 commit 0041e69

File tree

4 files changed

+251
-2
lines changed

4 files changed

+251
-2
lines changed

src/base.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,9 @@ pub enum IambId {
15741574
/// The `:spaces` window.
15751575
SpaceList,
15761576

1577+
/// The `:spaces!` window.
1578+
SpaceTree,
1579+
15771580
/// The `:verify` window.
15781581
VerifyList,
15791582

@@ -1602,6 +1605,7 @@ impl Display for IambId {
16021605
IambId::DirectList => f.write_str("iamb://dms"),
16031606
IambId::RoomList => f.write_str("iamb://rooms"),
16041607
IambId::SpaceList => f.write_str("iamb://spaces"),
1608+
IambId::SpaceTree => f.write_str("iamb://spacetree"),
16051609
IambId::VerifyList => f.write_str("iamb://verify"),
16061610
IambId::Welcome => f.write_str("iamb://welcome"),
16071611
IambId::ChatList => f.write_str("iamb://chats"),
@@ -1803,6 +1807,9 @@ pub enum IambBufferId {
18031807
/// The `:spaces` window.
18041808
SpaceList,
18051809

1810+
/// The `:spaces!` window.
1811+
SpaceTree,
1812+
18061813
/// The `:verify` window.
18071814
VerifyList,
18081815

@@ -1826,6 +1833,7 @@ impl IambBufferId {
18261833
IambBufferId::MemberList(room) => IambId::MemberList(room.clone()),
18271834
IambBufferId::RoomList => IambId::RoomList,
18281835
IambBufferId::SpaceList => IambId::SpaceList,
1836+
IambBufferId::SpaceTree => IambId::SpaceTree,
18291837
IambBufferId::VerifyList => IambId::VerifyList,
18301838
IambBufferId::Welcome => IambId::Welcome,
18311839
IambBufferId::ChatList => IambId::ChatList,
@@ -1861,6 +1869,7 @@ impl ApplicationInfo for IambInfo {
18611869
IambBufferId::MemberList(_) => vec![],
18621870
IambBufferId::RoomList => vec![],
18631871
IambBufferId::SpaceList => vec![],
1872+
IambBufferId::SpaceTree => vec![],
18641873
IambBufferId::VerifyList => vec![],
18651874
IambBufferId::Welcome => vec![],
18661875
IambBufferId::ChatList => vec![],

src/commands.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,13 @@ fn iamb_spaces(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
337337
return Result::Err(CommandError::InvalidArgument);
338338
}
339339

340-
let open = ctx.switch(OpenTarget::Application(IambId::SpaceList));
340+
let target = if desc.bang {
341+
IambId::SpaceTree
342+
} else {
343+
IambId::SpaceList
344+
};
345+
346+
let open = ctx.switch(OpenTarget::Application(target));
341347
let step = CommandStep::Continue(open, ctx.context.clone());
342348

343349
return Ok(step);

src/windows/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ use crate::base::{
8080
UnreadInfo,
8181
};
8282

83-
use self::{room::RoomState, welcome::WelcomeState};
83+
use self::{room::RoomState, spacetree::SpaceTreeState, welcome::WelcomeState};
8484
use crate::message::MessageTimeStamp;
8585
use feruca::Collator;
8686

8787
pub mod room;
88+
pub mod spacetree;
8889
pub mod welcome;
8990

9091
type MatrixRoomInfo = Arc<(MatrixRoom, Option<Tags>)>;
@@ -326,6 +327,7 @@ macro_rules! delegate {
326327
IambWindow::MemberList($id, _, _) => $e,
327328
IambWindow::RoomList($id) => $e,
328329
IambWindow::SpaceList($id) => $e,
330+
IambWindow::SpaceTree($id) => $e,
329331
IambWindow::VerifyList($id) => $e,
330332
IambWindow::Welcome($id) => $e,
331333
IambWindow::ChatList($id) => $e,
@@ -341,6 +343,7 @@ pub enum IambWindow {
341343
VerifyList(VerifyListState),
342344
RoomList(RoomListState),
343345
SpaceList(SpaceListState),
346+
SpaceTree(SpaceTreeState),
344347
Welcome(WelcomeState),
345348
ChatList(ChatListState),
346349
UnreadList(UnreadListState),
@@ -452,6 +455,12 @@ impl From<SpaceListState> for IambWindow {
452455
}
453456
}
454457

458+
impl From<SpaceTreeState> for IambWindow {
459+
fn from(tree: SpaceTreeState) -> Self {
460+
IambWindow::SpaceTree(tree)
461+
}
462+
}
463+
455464
impl From<WelcomeState> for IambWindow {
456465
fn from(win: WelcomeState) -> Self {
457466
IambWindow::Welcome(win)
@@ -513,6 +522,7 @@ impl WindowOps<IambInfo> for IambWindow {
513522
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
514523
match self {
515524
IambWindow::Room(state) => state.draw(area, buf, focused, store),
525+
IambWindow::SpaceTree(state) => state.draw(area, buf, focused, store),
516526
IambWindow::DirectList(state) => {
517527
let mut items = store
518528
.application
@@ -695,6 +705,7 @@ impl WindowOps<IambInfo> for IambWindow {
695705
},
696706
IambWindow::RoomList(w) => w.dup(store).into(),
697707
IambWindow::SpaceList(w) => w.dup(store).into(),
708+
IambWindow::SpaceTree(w) => w.dup(store).into(),
698709
IambWindow::VerifyList(w) => w.dup(store).into(),
699710
IambWindow::Welcome(w) => w.dup(store).into(),
700711
IambWindow::ChatList(w) => w.dup(store).into(),
@@ -736,6 +747,7 @@ impl Window<IambInfo> for IambWindow {
736747
IambWindow::MemberList(_, room_id, _) => IambId::MemberList(room_id.clone()),
737748
IambWindow::RoomList(_) => IambId::RoomList,
738749
IambWindow::SpaceList(_) => IambId::SpaceList,
750+
IambWindow::SpaceTree(_) => IambId::SpaceTree,
739751
IambWindow::VerifyList(_) => IambId::VerifyList,
740752
IambWindow::Welcome(_) => IambId::Welcome,
741753
IambWindow::ChatList(_) => IambId::ChatList,
@@ -748,6 +760,7 @@ impl Window<IambInfo> for IambWindow {
748760
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
749761
IambWindow::RoomList(_) => bold_spans("Rooms"),
750762
IambWindow::SpaceList(_) => bold_spans("Spaces"),
763+
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
751764
IambWindow::VerifyList(_) => bold_spans("Verifications"),
752765
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
753766
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
@@ -776,6 +789,7 @@ impl Window<IambInfo> for IambWindow {
776789
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
777790
IambWindow::RoomList(_) => bold_spans("Rooms"),
778791
IambWindow::SpaceList(_) => bold_spans("Spaces"),
792+
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
779793
IambWindow::VerifyList(_) => bold_spans("Verifications"),
780794
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
781795
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
@@ -826,6 +840,11 @@ impl Window<IambInfo> for IambWindow {
826840

827841
return Ok(list.into());
828842
},
843+
IambId::SpaceTree => {
844+
let tree = SpaceTreeState::new();
845+
846+
return Ok(tree.into());
847+
},
829848
IambId::VerifyList => {
830849
let list = VerifyListState::new(IambBufferId::VerifyList, vec![]);
831850

src/windows/spacetree.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use std::time::{Duration, Instant};
2+
3+
use modalkit::{
4+
actions::{Editable, EditorAction, Jumpable, PromptAction, Promptable, Scrollable},
5+
editing::completion::CompletionList,
6+
errors::EditResult,
7+
prelude::*,
8+
};
9+
use modalkit_ratatui::{
10+
list::{List, ListState},
11+
TermOffset,
12+
TerminalCursor,
13+
WindowOps,
14+
};
15+
use ratatui::{
16+
buffer::Buffer,
17+
layout::Rect,
18+
style::{Color, Style},
19+
text::{Line, Span, Text},
20+
widgets::StatefulWidget,
21+
};
22+
23+
use crate::base::{
24+
IambBufferId,
25+
IambInfo,
26+
IambResult,
27+
ProgramAction,
28+
ProgramContext,
29+
ProgramStore,
30+
};
31+
32+
use crate::windows::RoomItem;
33+
34+
use super::room_fields_cmp;
35+
36+
const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(15);
37+
38+
/// [StatefulWidget] for Matrix space tree.
39+
pub struct SpaceTree<'a> {
40+
focused: bool,
41+
store: &'a mut ProgramStore,
42+
}
43+
44+
impl<'a> SpaceTree<'a> {
45+
pub fn new(store: &'a mut ProgramStore) -> Self {
46+
SpaceTree { focused: false, store }
47+
}
48+
49+
pub fn focus(mut self, focused: bool) -> Self {
50+
self.focused = focused;
51+
self
52+
}
53+
}
54+
55+
impl StatefulWidget for SpaceTree<'_> {
56+
type State = SpaceTreeState;
57+
58+
fn render(self, area: Rect, buffer: &mut Buffer, state: &mut Self::State) {
59+
let mut empty_message = None;
60+
let need_fetch = match state.last_fetch {
61+
Some(i) => i.elapsed() >= SPACE_HIERARCHY_DEBOUNCE,
62+
None => true,
63+
};
64+
65+
if need_fetch {
66+
let mut children = vec![];
67+
let res = self.store.application.sync_info.spaces.iter().try_for_each(|room| {
68+
let id = room.0.room_id();
69+
let res = self.store.application.worker.space_members(id.to_owned());
70+
71+
res.map(|members| children.extend(members.into_iter().filter(|child| child != id)))
72+
});
73+
74+
if let Err(e) = res {
75+
let lines = vec![
76+
Line::from("Unable to fetch space room hierarchy:"),
77+
Span::styled(e.to_string(), Style::default().fg(Color::Red)).into(),
78+
];
79+
80+
empty_message = Text::from(lines).into();
81+
} else {
82+
let mut items = self
83+
.store
84+
.application
85+
.sync_info
86+
.spaces
87+
.clone()
88+
.into_iter()
89+
.filter(|space| !children.contains(&space.0.room_id().to_owned()))
90+
.map(|room| RoomItem::new(room, self.store))
91+
.collect::<Vec<_>>();
92+
let fields = &self.store.application.settings.tunables.sort.spaces;
93+
let collator = &mut self.store.application.collator;
94+
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));
95+
96+
state.list.set(items);
97+
state.last_fetch = Some(Instant::now());
98+
}
99+
}
100+
101+
let mut list = List::new(self.store).focus(self.focused);
102+
103+
if let Some(text) = empty_message {
104+
list = list.empty_message(text);
105+
} else {
106+
list = list.empty_message(Text::from("You haven't joined any spaces yet"));
107+
}
108+
109+
list.render(area, buffer, &mut state.list)
110+
}
111+
}
112+
113+
/// State for the list of toplevel spaces
114+
pub struct SpaceTreeState {
115+
list: ListState<RoomItem, IambInfo>,
116+
last_fetch: Option<Instant>,
117+
}
118+
119+
impl SpaceTreeState {
120+
pub fn new() -> Self {
121+
let content = IambBufferId::SpaceTree;
122+
let list = ListState::new(content, vec![]);
123+
124+
SpaceTreeState { list, last_fetch: None }
125+
}
126+
}
127+
128+
impl Editable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
129+
fn editor_command(
130+
&mut self,
131+
act: &EditorAction,
132+
ctx: &ProgramContext,
133+
store: &mut ProgramStore,
134+
) -> EditResult<EditInfo, IambInfo> {
135+
self.list.editor_command(act, ctx, store)
136+
}
137+
}
138+
139+
impl Jumpable<ProgramContext, IambInfo> for SpaceTreeState {
140+
fn jump(
141+
&mut self,
142+
list: PositionList,
143+
dir: MoveDir1D,
144+
count: usize,
145+
ctx: &ProgramContext,
146+
) -> IambResult<usize> {
147+
self.list.jump(list, dir, count, ctx)
148+
}
149+
}
150+
151+
impl Scrollable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
152+
fn scroll(
153+
&mut self,
154+
style: &ScrollStyle,
155+
ctx: &ProgramContext,
156+
store: &mut ProgramStore,
157+
) -> EditResult<EditInfo, IambInfo> {
158+
self.list.scroll(style, ctx, store)
159+
}
160+
}
161+
162+
impl Promptable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
163+
fn prompt(
164+
&mut self,
165+
act: &PromptAction,
166+
ctx: &ProgramContext,
167+
store: &mut ProgramStore,
168+
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
169+
self.list.prompt(act, ctx, store)
170+
}
171+
}
172+
173+
impl TerminalCursor for SpaceTreeState {
174+
fn get_term_cursor(&self) -> Option<TermOffset> {
175+
self.list.get_term_cursor()
176+
}
177+
}
178+
179+
impl WindowOps<IambInfo> for SpaceTreeState {
180+
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
181+
SpaceTree::new(store).focus(focused).render(area, buf, self);
182+
}
183+
184+
fn dup(&self, store: &mut ProgramStore) -> Self {
185+
SpaceTreeState {
186+
list: self.list.dup(store),
187+
last_fetch: self.last_fetch,
188+
}
189+
}
190+
191+
fn close(&mut self, flags: CloseFlags, store: &mut ProgramStore) -> bool {
192+
self.list.close(flags, store)
193+
}
194+
195+
fn get_completions(&self) -> Option<CompletionList> {
196+
self.list.get_completions()
197+
}
198+
199+
fn get_cursor_word(&self, style: &WordStyle) -> Option<String> {
200+
self.list.get_cursor_word(style)
201+
}
202+
203+
fn get_selected_word(&self) -> Option<String> {
204+
self.list.get_selected_word()
205+
}
206+
207+
fn write(
208+
&mut self,
209+
path: Option<&str>,
210+
flags: WriteFlags,
211+
store: &mut ProgramStore,
212+
) -> IambResult<EditInfo> {
213+
self.list.write(path, flags, store)
214+
}
215+
}

0 commit comments

Comments
 (0)