use gpui::{Context, EntityId, EventEmitter};
use markdown::{AppExt as _, Block, BlockKind, Cursor, Doc, Part, Selection, Synced, Text};
use std::{cell::RefCell, rc::Rc};
use crate::{
anchor::{Anchor, Delta, SourceAnchor, SourceRanges},
editor::{
Mode,
mode::{source_of, switch},
},
history::History,
};
#[derive(Clone)]
pub(crate) struct Tracked {
doc: Rc<Doc>,
revision: u64,
}
impl Tracked {
fn new(doc: Rc<Doc>) -> Self {
Self { 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 share(&mut self, doc: Rc<Doc>) {
self.revision += 1;
self.doc = doc;
}
pub(crate) fn shared(&self) -> Rc<Doc> {
self.doc.clone()
}
pub(crate) fn ensure_block(&mut self) -> bool {
if !self.doc.blocks.is_empty() {
return false;
}
self.edit()
.blocks
.push(Block::new(BlockKind::Paragraph(Text::default())));
true
}
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) synced: Rc<RefCell<Synced>>,
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 synced = Synced::new(source, &marks);
let mut doc = Tracked::new(synced.doc().clone());
doc.ensure_block();
Self {
doc,
mode: Mode::default(),
synced: Rc::new(RefCell::new(synced)),
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 = self.source();
self.marks = marks;
let synced = Synced::new(source, &self.marks);
self.doc.share(synced.doc().clone());
self.doc.ensure_block();
*self.synced.borrow_mut() = synced;
self
}
pub fn with_mode(mut self, mode: Mode) -> Self {
if mode != self.mode {
switch(
&mut self.doc,
&mut self.synced.borrow_mut(),
&mut self.anchors,
mode,
Default::default(),
);
self.mode = mode;
}
self
}
pub fn source(&self) -> String {
crate::editor::mode::source(&self.doc, self.mode, &self.synced)
}
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 synced = Synced::new(source, &self.marks);
self.doc.share(synced.doc().clone());
self.doc.ensure_block();
*self.synced.borrow_mut() = synced;
self.anchors.clear();
if self.mode == Mode::Source {
switch(
&mut self.doc,
&mut self.synced.borrow_mut(),
&mut self.anchors,
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 => {
let mut synced = self.synced.borrow_mut();
synced.set_doc(self.doc.shared());
let offsets = synced.offsets(&ends);
(synced.text().to_owned(), offsets)
}
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<SourceAnchor>,
cx: &mut Context<Self>,
) {
let offsets: Vec<usize> = anchors
.iter()
.flat_map(|anchor| [anchor.range.start, anchor.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, anchor)| {
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: anchor.id,
range: range.clamp(&self.doc),
state: anchor.state,
actionable: anchor.actionable,
}
})
.collect();
cx.emit(DocumentEvent {
by: None,
change: Change::Anchors,
});
cx.notify();
}
}