Skip to content

Commit c3051d1

Browse files
committed
Add space tree on :spaces!
1 parent a9c1e69 commit c3051d1

File tree

4 files changed

+250
-2
lines changed

4 files changed

+250
-2
lines changed

src/base.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,9 @@ pub enum IambId {
15541554
/// The `:spaces` window.
15551555
SpaceList,
15561556

1557+
/// The `:spaces!` window.
1558+
SpaceTree,
1559+
15571560
/// The `:verify` window.
15581561
VerifyList,
15591562

@@ -1582,6 +1585,7 @@ impl Display for IambId {
15821585
IambId::DirectList => f.write_str("iamb://dms"),
15831586
IambId::RoomList => f.write_str("iamb://rooms"),
15841587
IambId::SpaceList => f.write_str("iamb://spaces"),
1588+
IambId::SpaceTree => f.write_str("iamb://spacetree"),
15851589
IambId::VerifyList => f.write_str("iamb://verify"),
15861590
IambId::Welcome => f.write_str("iamb://welcome"),
15871591
IambId::ChatList => f.write_str("iamb://chats"),
@@ -1776,6 +1780,9 @@ pub enum IambBufferId {
17761780
/// The `:spaces` window.
17771781
SpaceList,
17781782

1783+
/// The `:spaces!` window.
1784+
SpaceTree,
1785+
17791786
/// The `:verify` window.
17801787
VerifyList,
17811788

@@ -1799,6 +1806,7 @@ impl IambBufferId {
17991806
IambBufferId::MemberList(room) => IambId::MemberList(room.clone()),
18001807
IambBufferId::RoomList => IambId::RoomList,
18011808
IambBufferId::SpaceList => IambId::SpaceList,
1809+
IambBufferId::SpaceTree => IambId::SpaceTree,
18021810
IambBufferId::VerifyList => IambId::VerifyList,
18031811
IambBufferId::Welcome => IambId::Welcome,
18041812
IambBufferId::ChatList => IambId::ChatList,
@@ -1834,6 +1842,7 @@ impl ApplicationInfo for IambInfo {
18341842
IambBufferId::MemberList(_) => vec![],
18351843
IambBufferId::RoomList => vec![],
18361844
IambBufferId::SpaceList => vec![],
1845+
IambBufferId::SpaceTree => vec![],
18371846
IambBufferId::VerifyList => vec![],
18381847
IambBufferId::Welcome => vec![],
18391848
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,10 +80,11 @@ 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

8686
pub mod room;
87+
pub mod spacetree;
8788
pub mod welcome;
8889

8990
type MatrixRoomInfo = Arc<(MatrixRoom, Option<Tags>)>;
@@ -319,6 +320,7 @@ macro_rules! delegate {
319320
IambWindow::MemberList($id, _, _) => $e,
320321
IambWindow::RoomList($id) => $e,
321322
IambWindow::SpaceList($id) => $e,
323+
IambWindow::SpaceTree($id) => $e,
322324
IambWindow::VerifyList($id) => $e,
323325
IambWindow::Welcome($id) => $e,
324326
IambWindow::ChatList($id) => $e,
@@ -334,6 +336,7 @@ pub enum IambWindow {
334336
VerifyList(VerifyListState),
335337
RoomList(RoomListState),
336338
SpaceList(SpaceListState),
339+
SpaceTree(SpaceTreeState),
337340
Welcome(WelcomeState),
338341
ChatList(ChatListState),
339342
UnreadList(UnreadListState),
@@ -445,6 +448,12 @@ impl From<SpaceListState> for IambWindow {
445448
}
446449
}
447450

451+
impl From<SpaceTreeState> for IambWindow {
452+
fn from(tree: SpaceTreeState) -> Self {
453+
IambWindow::SpaceTree(tree)
454+
}
455+
}
456+
448457
impl From<WelcomeState> for IambWindow {
449458
fn from(win: WelcomeState) -> Self {
450459
IambWindow::Welcome(win)
@@ -506,6 +515,7 @@ impl WindowOps<IambInfo> for IambWindow {
506515
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
507516
match self {
508517
IambWindow::Room(state) => state.draw(area, buf, focused, store),
518+
IambWindow::SpaceTree(state) => state.draw(area, buf, focused, store),
509519
IambWindow::DirectList(state) => {
510520
let mut items = store
511521
.application
@@ -683,6 +693,7 @@ impl WindowOps<IambInfo> for IambWindow {
683693
},
684694
IambWindow::RoomList(w) => w.dup(store).into(),
685695
IambWindow::SpaceList(w) => w.dup(store).into(),
696+
IambWindow::SpaceTree(w) => w.dup(store).into(),
686697
IambWindow::VerifyList(w) => w.dup(store).into(),
687698
IambWindow::Welcome(w) => w.dup(store).into(),
688699
IambWindow::ChatList(w) => w.dup(store).into(),
@@ -724,6 +735,7 @@ impl Window<IambInfo> for IambWindow {
724735
IambWindow::MemberList(_, room_id, _) => IambId::MemberList(room_id.clone()),
725736
IambWindow::RoomList(_) => IambId::RoomList,
726737
IambWindow::SpaceList(_) => IambId::SpaceList,
738+
IambWindow::SpaceTree(_) => IambId::SpaceTree,
727739
IambWindow::VerifyList(_) => IambId::VerifyList,
728740
IambWindow::Welcome(_) => IambId::Welcome,
729741
IambWindow::ChatList(_) => IambId::ChatList,
@@ -736,6 +748,7 @@ impl Window<IambInfo> for IambWindow {
736748
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
737749
IambWindow::RoomList(_) => bold_spans("Rooms"),
738750
IambWindow::SpaceList(_) => bold_spans("Spaces"),
751+
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
739752
IambWindow::VerifyList(_) => bold_spans("Verifications"),
740753
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
741754
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
@@ -764,6 +777,7 @@ impl Window<IambInfo> for IambWindow {
764777
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
765778
IambWindow::RoomList(_) => bold_spans("Rooms"),
766779
IambWindow::SpaceList(_) => bold_spans("Spaces"),
780+
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
767781
IambWindow::VerifyList(_) => bold_spans("Verifications"),
768782
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
769783
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
@@ -814,6 +828,11 @@ impl Window<IambInfo> for IambWindow {
814828

815829
return Ok(list.into());
816830
},
831+
IambId::SpaceTree => {
832+
let tree = SpaceTreeState::new();
833+
834+
return Ok(tree.into());
835+
},
817836
IambId::VerifyList => {
818837
let list = VerifyListState::new(IambBufferId::VerifyList, vec![]);
819838

src/windows/spacetree.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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+
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
94+
95+
state.list.set(items);
96+
state.last_fetch = Some(Instant::now());
97+
}
98+
}
99+
100+
let mut list = List::new(self.store).focus(self.focused);
101+
102+
if let Some(text) = empty_message {
103+
list = list.empty_message(text);
104+
} else {
105+
list = list.empty_message(Text::from("You haven't joined any spaces yet"));
106+
}
107+
108+
list.render(area, buffer, &mut state.list)
109+
}
110+
}
111+
112+
/// State for the list of toplevel spaces
113+
pub struct SpaceTreeState {
114+
list: ListState<RoomItem, IambInfo>,
115+
last_fetch: Option<Instant>,
116+
}
117+
118+
impl SpaceTreeState {
119+
pub fn new() -> Self {
120+
let content = IambBufferId::SpaceTree;
121+
let list = ListState::new(content, vec![]);
122+
123+
SpaceTreeState { list, last_fetch: None }
124+
}
125+
}
126+
127+
impl Editable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
128+
fn editor_command(
129+
&mut self,
130+
act: &EditorAction,
131+
ctx: &ProgramContext,
132+
store: &mut ProgramStore,
133+
) -> EditResult<EditInfo, IambInfo> {
134+
self.list.editor_command(act, ctx, store)
135+
}
136+
}
137+
138+
impl Jumpable<ProgramContext, IambInfo> for SpaceTreeState {
139+
fn jump(
140+
&mut self,
141+
list: PositionList,
142+
dir: MoveDir1D,
143+
count: usize,
144+
ctx: &ProgramContext,
145+
) -> IambResult<usize> {
146+
self.list.jump(list, dir, count, ctx)
147+
}
148+
}
149+
150+
impl Scrollable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
151+
fn scroll(
152+
&mut self,
153+
style: &ScrollStyle,
154+
ctx: &ProgramContext,
155+
store: &mut ProgramStore,
156+
) -> EditResult<EditInfo, IambInfo> {
157+
self.list.scroll(style, ctx, store)
158+
}
159+
}
160+
161+
impl Promptable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
162+
fn prompt(
163+
&mut self,
164+
act: &PromptAction,
165+
ctx: &ProgramContext,
166+
store: &mut ProgramStore,
167+
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
168+
self.list.prompt(act, ctx, store)
169+
}
170+
}
171+
172+
impl TerminalCursor for SpaceTreeState {
173+
fn get_term_cursor(&self) -> Option<TermOffset> {
174+
self.list.get_term_cursor()
175+
}
176+
}
177+
178+
impl WindowOps<IambInfo> for SpaceTreeState {
179+
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
180+
SpaceTree::new(store).focus(focused).render(area, buf, self);
181+
}
182+
183+
fn dup(&self, store: &mut ProgramStore) -> Self {
184+
SpaceTreeState {
185+
list: self.list.dup(store),
186+
last_fetch: self.last_fetch,
187+
}
188+
}
189+
190+
fn close(&mut self, flags: CloseFlags, store: &mut ProgramStore) -> bool {
191+
self.list.close(flags, store)
192+
}
193+
194+
fn get_completions(&self) -> Option<CompletionList> {
195+
self.list.get_completions()
196+
}
197+
198+
fn get_cursor_word(&self, style: &WordStyle) -> Option<String> {
199+
self.list.get_cursor_word(style)
200+
}
201+
202+
fn get_selected_word(&self) -> Option<String> {
203+
self.list.get_selected_word()
204+
}
205+
206+
fn write(
207+
&mut self,
208+
path: Option<&str>,
209+
flags: WriteFlags,
210+
store: &mut ProgramStore,
211+
) -> IambResult<EditInfo> {
212+
self.list.write(path, flags, store)
213+
}
214+
}

0 commit comments

Comments
 (0)