use gpui::{Context, EntityId, EventEmitter};
use markdown::{Annotation, AppExt as _, Cursor, Doc, Part, Selection};
use std::{ops::Range, rc::Rc};
use crate::{
anchor::{Anchor, AnchorId, Delta, SourceRanges},
editor::{
Mode, ensure_block,
mode::{source_of, switch},
},
history::History,
};
#[derive(Clone)]
pub(crate) struct Tracked {
doc: Rc<Doc>,
revision: u64,
}
impl Tracked {
fn new(doc: Doc) -> Self {
Self {
doc: Rc::new(doc),
revision: 0,
}
}
pub(crate) fn edit(&mut self) -> &mut Doc {
self.revision += 1;
Rc::make_mut(&mut self.doc)
}
pub(crate) fn set(&mut self, doc: Doc) {
*self.edit() = doc;
}
pub(crate) fn revision(&self) -> u64 {
self.revision
}
}
impl std::ops::Deref for Tracked {
type Target = Doc;
fn deref(&self) -> &Doc {
&self.doc
}
}
pub struct Document {
pub(crate) doc: Tracked,
pub(crate) mode: Mode,
pub(crate) marks: markdown::Marks,
pub(crate) anchors: Vec<Anchor>,
pub(crate) history: History,
}
pub struct DocumentEvent {
pub(crate) by: Option<EntityId>,
pub(crate) change: Change,
}
impl DocumentEvent {
pub fn edited(&self) -> bool {
matches!(self.change, Change::Edited(_) | Change::Replaced)
}
}
pub(crate) enum Change {
Edited(Rc<[Delta]>),
Replaced,
Composing,
Reloaded,
Anchors,
}
impl EventEmitter<DocumentEvent> for Document {}
impl Document {
pub fn new(source: &str, cx: &mut Context<Self>) -> Self {
let marks = cx.marks();
let mut doc = markdown::parse_with(source, &marks);
ensure_block(&mut doc);
Self {
doc: Tracked::new(doc),
mode: Mode::default(),
marks,
anchors: Vec::new(),
history: History::default(),
}
}
pub fn with_undo_limit(mut self, limit: usize) -> Self {
self.history = History::with_limit(limit);
self
}
pub fn with_marks(mut self, marks: markdown::Marks) -> Self {
let source = crate::editor::mode::source(&self.doc, self.mode, &self.marks);
self.marks = marks;
let mut doc = markdown::parse_with(&source, &self.marks);
ensure_block(&mut doc);
self.doc.set(doc);
self
}
pub fn with_mode(mut self, mode: Mode) -> Self {
if mode != self.mode {
switch(
&mut self.doc,
&mut self.anchors,
&self.marks,
mode,
Default::default(),
);
self.mode = mode;
}
self
}
pub fn source(&self) -> String {
crate::editor::mode::source(&self.doc, self.mode, &self.marks)
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn doc(&self) -> &Doc {
&self.doc
}
pub fn reload(&mut self, source: &str, cx: &mut Context<Self>) {
let mut doc = markdown::parse_with(source, &self.marks);
ensure_block(&mut doc);
self.doc.set(doc);
self.anchors.clear();
if self.mode == Mode::Source {
switch(
&mut self.doc,
&mut self.anchors,
&self.marks,
Mode::Source,
Cursor::default(),
);
}
self.history.clear();
cx.emit(DocumentEvent {
by: None,
change: Change::Reloaded,
});
cx.notify();
}
pub fn anchors(&self) -> &[Anchor] {
&self.anchors
}
pub fn set_anchors(&mut self, anchors: Vec<Anchor>, cx: &mut Context<Self>) {
self.anchors = anchors;
cx.emit(DocumentEvent {
by: None,
change: Change::Anchors,
});
cx.notify();
}
pub fn source_with_anchors(&self) -> (String, SourceRanges) {
let ends: Vec<_> = self
.anchors
.iter()
.flat_map(|anchor| {
let (start, end) = anchor.range.ordered();
[start, end]
})
.collect();
let (source, offsets) = match self.mode {
Mode::Blocks => markdown::serialize_at_many(&self.doc, &ends, &self.marks),
Mode::Source => {
let source = source_of(&self.doc).to_string();
let offsets = ends
.iter()
.map(|at| (at.offset <= source.len()).then_some(at.offset))
.collect();
(source, offsets)
}
};
let ranges = self
.anchors
.iter()
.enumerate()
.map(|(ix, anchor)| {
let range = offsets[2 * ix].zip(offsets[2 * ix + 1]);
(anchor.id, range.map(|(start, end)| start..end))
})
.collect();
(source, ranges)
}
pub fn set_source_anchors(
&mut self,
source: &str,
anchors: Vec<(AnchorId, Range<usize>, Annotation)>,
cx: &mut Context<Self>,
) {
let offsets: Vec<usize> = anchors
.iter()
.flat_map(|(_, range, _)| [range.start, range.end])
.collect();
let ends: Vec<Option<Cursor>> = match self.mode {
Mode::Blocks => markdown::parse_at_many(source, &offsets, &self.marks).1,
Mode::Source => offsets
.iter()
.map(|offset| {
(*offset <= source.len()).then(|| Cursor::new(0, Part::Code, *offset))
})
.collect(),
};
self.anchors = anchors
.into_iter()
.enumerate()
.map(|(ix, (id, _, state))| {
let range = match (ends[2 * ix], ends[2 * ix + 1]) {
(Some(start), Some(end)) => Selection::new(start, end),
(surviving, other) => Selection::at(surviving.or(other).unwrap_or_default()),
};
Anchor {
id,
range: range.clamp(&self.doc),
state,
}
})
.collect();
cx.emit(DocumentEvent {
by: None,
change: Change::Anchors,
});
cx.notify();
}
}