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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Whole-block operations and undo.
use super::*;
impl Editor {
/// Give a block over to the link it holds — a card, or the picture it
/// points at.
///
/// One step, not two: turning the block and giving the caret somewhere to
/// go are one gesture, and undo has to agree.
pub(super) fn turn_into(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
self.edit(EditKind::Structure, cx, |this| {
// The URL is the block's whole text and the new block shows it
// already, so [`Doc::set_kind`] is given nothing to carry across —
// otherwise a picture prints its own URL as its caption.
let held = this.doc.blocks[ix]
.text_at(Part::Body)
.map_or(0, |text| text.text.len());
this.doc.edit_at(Cursor::new(ix, Part::Body, 0), |text| {
*text = Text::default()
});
this.doc.set_kind(ix, kind);
// A caret goes where the block admits one, and otherwise carries on
// in the block after it — a fresh one when it ends the document.
let part = this.doc.blocks[ix].parts().first().copied();
let at = match part {
Some(part) => Cursor::new(ix, part, 0),
None => {
if this.doc.blocks.len() <= ix + 1 {
this.doc
.blocks
.push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
}
Cursor::new(ix + 1, Part::Body, 0)
}
};
this.selection = Selection::at(at.clamp(&this.doc));
vec![Delta::Spliced(Splice {
removed: Selection::new(
Cursor::new(ix, Part::Body, 0),
Cursor::new(ix, Part::Body, held),
),
caret: Cursor::new(ix, Part::Body, 0),
blocks: 0,
})]
});
}
pub(super) fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
if let Some(step) = self
.history
.undo(self.mode, &self.doc, self.selection, &self.anchors)
{
self.restore(step, cx);
}
}
pub(super) fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
if let Some(step) = self
.history
.redo(self.mode, &self.doc, self.selection, &self.anchors)
{
self.restore(step, cx);
}
}
/// Put a whole moment back — document, caret and anchors together.
///
/// The anchors come from the snapshot rather than from mapping, because a
/// step back is not an edit: there is no delta between here and a document
/// two hundred keystrokes ago.
pub(super) fn restore(&mut self, step: crate::history::Step, cx: &mut Context<Self>) {
self.doc = step.doc;
self.selection = step.selection.clamp(&self.doc);
self.caret_moved();
self.anchors = step.anchors;
if step.mode != self.mode {
self.mode = step.mode;
self.dismiss_menus();
cx.emit(EditorEvent::ModeChanged(step.mode));
}
cx.emit(EditorEvent::Changed);
cx.notify();
}
/// Move the caret's block, children and all, and follow it.
///
/// Public because a gutter handle and a menu row reach the same operation
/// as the key does — one vocabulary, not three paths into [`Doc`].
pub fn move_block(&mut self, ix: usize, delta: isize, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
let caret = this.cursor();
let at = this.doc.subtree(ix);
let Some(to) = this.doc.move_block(ix, delta) else {
return vec![];
};
// The caret rides along, keeping its depth within the subtree
// that moved and its offset within its own text.
let block = to + caret.block.saturating_sub(ix);
this.selection = Selection::at(Cursor { block, ..caret }.clamp(&this.doc));
vec![Delta::Moved { at, to: Some(to) }]
});
}
pub fn duplicate_block(&mut self, ix: usize, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
let span = this.doc.subtree(ix);
let Some(copy) = this.doc.duplicate(ix) else {
return vec![];
};
this.selection = Selection::at(Cursor::new(copy, Part::Body, 0).clamp(&this.doc));
vec![Delta::Opened {
at: copy,
count: span.len(),
}]
});
}
pub fn remove_block(&mut self, ix: usize, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
let at = this.doc.subtree(ix);
this.doc.remove_block(ix);
this.selection =
Selection::at(Cursor::new(ix.saturating_sub(1), Part::Body, 0).clamp(&this.doc));
vec![Delta::Moved { at, to: None }]
});
}
/// Tag a fenced block with the language it holds, or `None` for plain.
pub fn set_language(&mut self, ix: usize, language: Option<String>, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.set_language(ix, language);
vec![]
});
}
/// Turn the caret's block into `kind` — what the slash menu and the block
/// menu both do.
pub fn set_block(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.set_kind(ix, kind);
this.selection = this.selection.clamp(&this.doc);
vec![]
});
}
/// Check or uncheck the task block at `ix`. Does nothing to a block that
/// is not one.
///
/// An edit like any other: one undo step, and the caret stays where it
/// was, so toggling a box across the document does not move whatever is
/// being typed.
pub fn toggle_task(&mut self, ix: usize, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
let checked = match self.doc.blocks.get(ix).map(|block| &block.kind) {
Some(BlockKind::Task { checked, .. }) => !checked,
_ => return,
};
self.edit(EditKind::Structure, cx, |this| {
if let Some(BlockKind::Task { checked: at, .. }) =
this.doc.blocks.get_mut(ix).map(|block| &mut block.kind)
{
*at = checked;
}
vec![]
});
// Every other edit is made at the caret and owes it a reveal. This one
// is made where a press landed, and the caret can be pages away: the
// reveal `edit` asked for would scroll the box being checked off the
// screen.
self.reveal = false;
}
}