1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! The articles a project holds, and the two surfaces each one opens.
//!
//! A continuation of [`Workspace`]'s one `impl`, which is why it opens on
//! `use super::*`: these methods work on the same struct and reach the same
//! names as the rest of it.
use super::*;
impl Workspace {
/// A fresh document in the active project, opened as it lands — an empty
/// article has nothing to look at but the caret.
pub fn new_article(&mut self, cx: &mut Context<Self>) -> Option<usize> {
let project = self.active?;
let article = article::create(&self.projects[project].path)?;
// Where a re-read would put it: the list is newest first, and a new one
// appended would sit at the bottom until the next load moved it.
self.projects[project].articles.insert(0, article);
self.open_article(project, 0, cx);
Some(0)
}
pub fn archive_article(&mut self, path: &Path, archived: bool, cx: &mut Context<Self>) {
let Some(article) = self.article_at_mut(path) else {
return;
};
article.archive(archived);
self.prune_archived(cx);
cx.notify();
}
/// The article a file names, wherever it is open — what the sidebar
/// addresses one by, since an index moves when a neighbour is made.
pub fn article_at_mut(&mut self, path: &Path) -> Option<&mut Article> {
self.projects
.iter_mut()
.flat_map(|open| open.articles.iter_mut())
.find(|article| article.path == path)
}
/// Every project's articles are on show, so picking one brings its project
/// forward with it.
pub fn open_article(&mut self, project: usize, ix: usize, cx: &mut Context<Self>) {
let text_size = self.article_font_size();
let Some(article) = self
.projects
.get_mut(project)
.and_then(|open| open.articles.get_mut(ix))
else {
return;
};
article.open(text_size, cx);
self.projects[project].article = Some(ix);
let id = self.projects[project].articles[ix]
.path
.to_string_lossy()
.into_owned();
self.active = Some(project);
self.remember(project, state::Kind::Article, id, cx);
cx.notify();
}
/// Drop the article: the file goes with it.
pub fn delete_article(&mut self, project: usize, ix: usize, cx: &mut Context<Self>) {
let Some(project) = self.projects.get_mut(project) else {
return;
};
if ix >= project.articles.len() {
return;
}
project.articles.remove(ix).remove();
project.article = project
.article
.filter(|open| *open != ix)
.map(|open| if open > ix { open - 1 } else { open });
cx.notify();
}
/// Take the file over the buffer, for an article the watch found had moved
/// underneath one — what the pane's notice offers. See [`Article::revert`].
pub fn revert_article(&mut self, path: &Path, cx: &mut Context<Self>) {
let Some(article) = self.article_at_mut(path) else {
return;
};
article.revert(cx);
cx.notify();
}
/// Keep the buffer instead, and write it over what landed — the other half
/// of the same notice. See [`Article::keep`].
pub fn keep_article(&mut self, path: &Path, cx: &mut Context<Self>) {
let Some(article) = self.article_at_mut(path) else {
return;
};
article.keep(cx);
cx.notify();
}
pub fn active_article(&self) -> Option<&Article> {
let project = self.active_project()?;
project.articles.get(project.article?)
}
/// Put a cover on the open article, or take it off — see
/// [`Article::set_cover`].
pub fn set_cover(&mut self, source: Option<&Path>, cx: &mut Context<Self>) {
if let Some(article) = self.article_mut() {
article.set_cover(source);
cx.notify();
}
}
/// Set the open page across the pane, or back in the column — see
/// [`Article::set_full_width`]. The open one and not one the sidebar names:
/// the width is asked for from the page you are looking at.
pub fn set_full_width(&mut self, wide: Option<bool>, cx: &mut Context<Self>) {
if let Some(article) = self.article_mut() {
article.set_full_width(wide);
cx.notify();
}
}
/// Put the open document into the other form — see [`Article::set_mode`].
pub fn set_article_mode(&mut self, mode: Mode, cx: &mut Context<Self>) {
if let Some(article) = self.article_mut() {
article.set_mode(mode, cx);
cx.notify();
}
}
/// Cut the open article a new cover — see [`Article::shuffle_cover`].
pub fn shuffle_cover(&mut self, cx: &mut Context<Self>) {
if let Some(article) = self.article_mut() {
article.shuffle_cover();
cx.notify();
}
}
fn article_mut(&mut self) -> Option<&mut Article> {
let project = self.projects.get_mut(self.active?)?;
project.articles.get_mut(project.article?)
}
/// The title or the content changed. Found by the entity because an article
/// has two surfaces and either can be the one that moved.
pub fn write_article(&mut self, changed: EntityId, cx: &mut Context<Self>) {
let found = self.projects.iter_mut().find_map(|project| {
project.articles.iter_mut().find(|article| {
article
.field
.as_ref()
.is_some_and(|field| field.entity_id() == changed)
|| article
.editor
.as_ref()
.is_some_and(|editor| editor.entity_id() == changed)
})
});
if found.is_some_and(|article| article.write(cx)) {
cx.notify();
}
}
}