use super::*;
impl Editor {
pub(super) fn turn_into(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
self.edit(EditKind::Structure, cx, |this| {
let held = this.doc.blocks[ix]
.text_at(Part::Body)
.map_or(0, |text| text.text.len());
this.doc
.edit()
.edit_at(Cursor::new(ix, Part::Body, 0), |text| {
*text = Text::default()
});
this.doc.edit().set_kind(ix, kind);
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
.edit()
.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>) {
let (mode, selection) = (self.mode, self.selection);
let step = self.document.update(cx, |document, _| {
document
.history
.undo(mode, &self.doc, selection, &self.anchors)
});
if let Some(step) = step {
self.restore(step, cx);
}
}
pub(super) fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
let (mode, selection) = (self.mode, self.selection);
let step = self.document.update(cx, |document, _| {
document
.history
.redo(mode, &self.doc, selection, &self.anchors)
});
if let Some(step) = step {
self.restore(step, cx);
}
}
pub(super) fn restore(&mut self, step: crate::history::Step, cx: &mut Context<Self>) {
self.doc.set(step.doc);
self.selection = step.selection.clamp(&self.doc);
self.run.interrupt();
self.caret_moved();
self.anchors = step.anchors;
let switched = step.mode != self.mode;
self.mode = step.mode;
self.publish(Change::Replaced, cx);
if switched {
self.dismiss_menus();
cx.emit(EditorEvent::ModeChanged(step.mode));
}
cx.emit(EditorEvent::Changed);
cx.notify();
}
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.edit().move_block(ix, delta) else {
return vec![];
};
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.edit().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.edit().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 }]
});
}
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.edit().set_language(ix, language);
vec![]
});
}
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.edit().set_kind(ix, kind);
this.selection = this.selection.clamp(&this.doc);
vec![]
});
}
pub fn toggle_quote(
&mut self,
ix: usize,
alert: Option<markdown::QuoteKind>,
cx: &mut Context<Self>,
) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.edit().toggle_quote(ix, alert);
this.selection = this.selection.clamp(&this.doc);
vec![]
});
}
pub fn set_code(&mut self, ix: usize, code: String, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.edit().set_code(ix, code);
this.selection = this.selection.clamp(&this.doc);
vec![]
});
}
pub fn place_block(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
if !self.blocks() || ix >= self.doc.blocks.len() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.edit().blocks[ix].kind = kind;
this.selection = this.selection.clamp(&this.doc);
vec![]
});
self.step_past(ix, cx);
}
pub(super) fn step_past(&mut self, ix: usize, cx: &mut Context<Self>) {
if self.doc.blocks.len() <= ix + 1 {
self.edit(EditKind::Structure, cx, |this| {
this.doc
.edit()
.blocks
.push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
vec![]
});
}
let at = Cursor::new(ix + 1, Part::Body, 0).clamp(&self.doc);
self.select(Selection::at(at), cx);
}
pub(super) fn leave_block(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
self.focus_handle.focus(window, cx);
self.step_past(ix, cx);
}
pub fn relink(&mut self, from: &str, to: &str, cx: &mut Context<Self>) {
if self.blocks() {
let found: Vec<(usize, BlockKind)> = self
.doc
.blocks
.iter()
.enumerate()
.filter_map(|(ix, block)| match &block.kind {
BlockKind::Image { url, alt, width } if url == from => Some((
ix,
BlockKind::Image {
url: to.to_owned(),
alt: alt.clone(),
width: *width,
},
)),
_ => None,
})
.collect();
if found.is_empty() {
return;
}
self.edit(EditKind::Structure, cx, |this| {
for (ix, kind) in found {
this.doc.edit().set_kind(ix, kind);
}
this.selection = this.selection.clamp(&this.doc);
vec![]
});
return;
}
let source = self.source_text();
let mut hits: Vec<(Range<usize>, String)> = [
(format!("]({from})"), format!("]({to})")),
(format!("](<{from}>)"), format!("](<{to}>)")),
]
.into_iter()
.flat_map(|(old, new)| {
source
.match_indices(&old)
.map(|(at, _)| (at..at + old.len(), new.clone()))
.collect::<Vec<_>>()
})
.collect();
if hits.is_empty() {
return;
}
hits.sort_by_key(|(range, _)| std::cmp::Reverse(range.start));
let moved = |cursor: Cursor| {
let offset = hits
.iter()
.fold(cursor.offset as isize, |at, (range, new)| {
if cursor.offset >= range.end {
at + new.len() as isize - range.len() as isize
} else if cursor.offset > range.start {
at - (cursor.offset - range.start) as isize
} else {
at
}
});
Cursor {
offset: offset.max(0) as usize,
..cursor
}
};
let selection = Selection::new(moved(self.selection.anchor), moved(self.selection.head));
self.edit(EditKind::Structure, cx, |this| {
let at = |offset| Cursor::new(0, Part::Code, offset);
let splices = hits
.iter()
.map(|(range, new)| {
Delta::Spliced(this.doc.edit().replace(
Selection::new(at(range.start), at(range.end)),
Text::plain(new.as_str()),
))
})
.collect();
this.selection = selection.clamp(&this.doc);
splices
});
}
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
.edit()
.blocks
.get_mut(ix)
.map(|block| &mut block.kind)
{
*at = checked;
}
vec![]
});
self.reveal = None;
}
pub fn add_row(&mut self, ix: usize, row: usize, cx: &mut Context<Self>) {
if !self.blocks() || !matches!(self.kind_at(ix), Some(BlockKind::Table { .. })) {
return;
}
self.edit(EditKind::Structure, cx, |this| {
this.doc.edit().insert_row(ix, row);
let row = row.max(1);
this.selection = Selection::at(Cursor::new(ix, Part::Cell { row, column: 0 }, 0));
vec![Delta::Cells {
block: ix,
row: Some(row),
column: None,
}]
});
}
pub fn add_column(&mut self, ix: usize, column: usize, cx: &mut Context<Self>) {
if !self.blocks() || !matches!(self.kind_at(ix), Some(BlockKind::Table { .. })) {
return;
}
let row = match self.cursor() {
Cursor {
block,
part: Part::Cell { row, .. },
..
} if block == ix => row,
_ => self.doc.blocks[ix]
.parts()
.first()
.map_or(0, |part| match part {
Part::Cell { row, .. } => *row,
_ => 0,
}),
};
self.edit(EditKind::Structure, cx, |this| {
this.doc.edit().insert_column(ix, column);
this.selection = Selection::at(Cursor::new(ix, Part::Cell { row, column }, 0));
vec![Delta::Cells {
block: ix,
row: None,
column: Some(column),
}]
});
}
pub(super) fn step_cell(&mut self, forward: bool, cx: &mut Context<Self>) -> bool {
let at = self.cursor();
let Part::Cell { row, .. } = at.part else {
return false;
};
let parts = self.doc.blocks[at.block].parts();
let Some(here) = parts.iter().position(|part| *part == at.part) else {
return false;
};
let next = match forward {
true => parts.get(here + 1),
false => here.checked_sub(1).and_then(|ix| parts.get(ix)),
};
match next {
Some(part) => {
let end = Cursor::new(at.block, *part, usize::MAX).clamp(&self.doc);
self.head_to(end, false);
cx.notify();
}
None if forward => self.add_row(at.block, row + 1, cx),
None => {}
}
true
}
pub(super) fn cell_down(&mut self, cx: &mut Context<Self>) -> bool {
let at = self.cursor();
let Part::Cell { row, column } = at.part else {
return false;
};
let below = Part::Cell {
row: row + 1,
column,
};
if self.doc.blocks[at.block].text_at(below).is_none() {
self.add_row(at.block, row + 1, cx);
if column > 0 {
self.head_to(Cursor::new(at.block, below, 0).clamp(&self.doc), false);
}
return true;
}
let end = Cursor::new(at.block, below, usize::MAX).clamp(&self.doc);
self.head_to(end, false);
cx.notify();
true
}
pub fn remove_row(&mut self, ix: usize, row: usize, cx: &mut Context<Self>) {
self.remove_line(ix, Line::Row(row), cx);
}
pub fn remove_column(&mut self, ix: usize, column: usize, cx: &mut Context<Self>) {
self.remove_line(ix, Line::Column(column), cx);
}
fn remove_line(&mut self, ix: usize, line: Line, cx: &mut Context<Self>) {
if !self.blocks() {
return;
}
let Some(BlockKind::Table { header, rows, .. }) = self.kind_at(ix) else {
return;
};
let width = rows
.iter()
.map(Vec::len)
.chain([header.len()])
.max()
.unwrap_or(0);
let removable = match line {
Line::Row(row) => (1..=rows.len()).contains(&row),
Line::Column(column) => width > 1 && column < width,
};
if !removable {
return;
}
self.edit(EditKind::Structure, cx, |this| {
match line {
Line::Row(row) => this.doc.edit().remove_row(ix, row),
Line::Column(column) => this.doc.edit().remove_column(ix, column),
};
this.selection = this.selection.clamp(&this.doc);
let (row, column) = match line {
Line::Row(row) => (Some(row), None),
Line::Column(column) => (None, Some(column)),
};
vec![Delta::CellsRemoved {
block: ix,
row,
column,
}]
});
}
fn kind_at(&self, ix: usize) -> Option<&BlockKind> {
self.doc.blocks.get(ix).map(|block| &block.kind)
}
}