summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/mod.rs9
-rw-r--r--src/font.rs2
-rw-r--r--src/image.rs2
-rw-r--r--src/lib.rs5
-rw-r--r--src/source.rs93
5 files changed, 86 insertions, 25 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 30b34798..f2afbafb 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -51,9 +51,6 @@ pub fn eval(
Ok(Module { scope: ctx.scopes.top, template })
}
-/// Caches evaluated modules.
-pub type ModuleCache = HashMap<SourceId, Module>;
-
/// An evaluated module, ready for importing or execution.
#[derive(Debug, Clone, PartialEq)]
pub struct Module {
@@ -71,10 +68,10 @@ pub struct EvalContext<'a> {
pub sources: &'a mut SourceStore,
/// Stores decoded images.
pub images: &'a mut ImageStore,
- /// Caches evaluated modules.
- pub modules: &'a mut ModuleCache,
/// The stack of imported files that led to evaluation of the current file.
pub route: Vec<SourceId>,
+ /// Caches imported modules.
+ pub modules: HashMap<SourceId, Module>,
/// The active scopes.
pub scopes: Scopes<'a>,
/// The expression map for the currently built template.
@@ -88,8 +85,8 @@ impl<'a> EvalContext<'a> {
loader: ctx.loader.as_ref(),
sources: &mut ctx.sources,
images: &mut ctx.images,
- modules: &mut ctx.modules,
route: vec![source],
+ modules: HashMap::new(),
scopes: Scopes::new(Some(&ctx.std)),
map: ExprMap::new(),
}
diff --git a/src/font.rs b/src/font.rs
index 313dcf8f..300774bb 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -140,7 +140,7 @@ impl FontStore {
/// Get a reference to a loaded face.
///
- /// This panics if no face with this id was loaded. This function should
+ /// This panics if no face with this `id` was loaded. This function should
/// only be called with ids returned by this store's
/// [`select()`](Self::select) method.
#[track_caller]
diff --git a/src/image.rs b/src/image.rs
index f98c7b1b..6ccfb11d 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -79,7 +79,7 @@ impl ImageStore {
/// Get a reference to a loaded image.
///
- /// This panics if no image with this id was loaded. This function should
+ /// This panics if no image with this `id` was loaded. This function should
/// only be called with ids returned by this store's [`load()`](Self::load)
/// method.
#[track_caller]
diff --git a/src/lib.rs b/src/lib.rs
index ce753f86..41ad1d16 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -50,7 +50,7 @@ pub mod util;
use std::rc::Rc;
use crate::diag::TypResult;
-use crate::eval::{ModuleCache, Scope};
+use crate::eval::Scope;
use crate::exec::State;
use crate::font::FontStore;
use crate::image::ImageStore;
@@ -70,8 +70,6 @@ pub struct Context {
pub fonts: FontStore,
/// Stores decoded images.
pub images: ImageStore,
- /// Caches evaluated modules.
- pub modules: ModuleCache,
/// Caches layouting artifacts.
#[cfg(feature = "layout-cache")]
pub layouts: LayoutCache,
@@ -144,7 +142,6 @@ impl ContextBuilder {
fonts: FontStore::new(Rc::clone(&loader)),
images: ImageStore::new(Rc::clone(&loader)),
loader,
- modules: ModuleCache::new(),
#[cfg(feature = "layout-cache")]
layouts: LayoutCache::new(),
std: self.std.unwrap_or(library::new()),
diff --git a/src/source.rs b/src/source.rs
index 0f1637a9..877fd729 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -100,9 +100,18 @@ impl SourceStore {
id
}
+ /// Edit a source file by replacing the given range.
+ ///
+ /// This panics if no source file with this `id` exists or if the `replace`
+ /// range is out of bounds for the source file identified by `id`.
+ #[track_caller]
+ pub fn edit(&mut self, id: SourceId, replace: Range<usize>, with: &str) {
+ self.sources[id.0 as usize].edit(replace, with);
+ }
+
/// Get a reference to a loaded source file.
///
- /// This panics if no source file with this id was loaded. This function
+ /// This panics if no source file with this `id` exists. This function
/// should only be called with ids returned by this store's
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
#[track_caller]
@@ -126,17 +135,7 @@ impl SourceFile {
/// Create a new source file.
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
let mut line_starts = vec![0];
- let mut s = Scanner::new(&src);
-
- while let Some(c) = s.eat() {
- if is_newline(c) {
- if c == '\r' {
- s.eat_if('\n');
- }
- line_starts.push(s.index());
- }
- }
-
+ line_starts.extend(newlines(&src));
Self {
id,
path: path.normalize(),
@@ -230,6 +229,27 @@ impl SourceFile {
}
Some(range.start + (line.len() - chars.as_str().len()))
}
+
+ /// Edit the source file by replacing the given range.
+ ///
+ /// This panics if the `replace` range is out of bounds.
+ pub fn edit(&mut self, replace: Range<usize>, with: &str) {
+ let start = replace.start;
+ self.src.replace_range(replace, with);
+
+ // Remove invalidated line starts.
+ let line = self.byte_to_line(start).unwrap();
+ self.line_starts.truncate(line + 1);
+
+ // Handle adjoining of \r and \n.
+ if self.src[.. start].ends_with('\r') && with.starts_with('\n') {
+ self.line_starts.pop();
+ }
+
+ // Recalculate the line starts after the edit.
+ self.line_starts
+ .extend(newlines(&self.src[start ..]).map(|idx| start + idx));
+ }
}
impl AsRef<str> for SourceFile {
@@ -238,6 +258,24 @@ impl AsRef<str> for SourceFile {
}
}
+/// The indices at which lines start (right behind newlines).
+///
+/// The beginning of the string (index 0) is not returned.
+fn newlines(string: &str) -> impl Iterator<Item = usize> + '_ {
+ let mut s = Scanner::new(string);
+ std::iter::from_fn(move || {
+ while let Some(c) = s.eat() {
+ if is_newline(c) {
+ if c == '\r' {
+ s.eat_if('\n');
+ }
+ return Some(s.index());
+ }
+ }
+ None
+ })
+}
+
#[cfg(feature = "codespan-reporting")]
impl<'a> Files<'a> for SourceStore {
type FileId = SourceId;
@@ -297,7 +335,7 @@ mod tests {
#[test]
fn test_source_file_new() {
let source = SourceFile::detached(TEST);
- assert_eq!(source.line_starts, vec![0, 7, 15, 18]);
+ assert_eq!(source.line_starts, [0, 7, 15, 18]);
}
#[test]
@@ -340,4 +378,33 @@ mod tests {
roundtrip(&source, 12);
roundtrip(&source, 21);
}
+
+ #[test]
+ fn test_source_file_edit() {
+ #[track_caller]
+ fn test(prev: &str, range: Range<usize>, with: &str, after: &str) {
+ let mut source = SourceFile::detached(prev);
+ let result = SourceFile::detached(after);
+ source.edit(range, with);
+ assert_eq!(source.src, result.src);
+ assert_eq!(source.line_starts, result.line_starts);
+ }
+
+ // Test inserting at the begining.
+ test("abc\n", 0 .. 0, "hi\n", "hi\nabc\n");
+ test("\nabc", 0 .. 0, "hi\r", "hi\r\nabc");
+
+ // Test editing in the middle.
+ test(TEST, 4 .. 16, "❌", "ä\tc❌i\rjkl");
+
+ // Test appending.
+ test("abc\ndef", 7 .. 7, "hi", "abc\ndefhi");
+ test("abc\ndef\n", 8 .. 8, "hi", "abc\ndef\nhi");
+
+ // Test appending with adjoining \r and \n.
+ test("abc\ndef\r", 8 .. 8, "\nghi", "abc\ndef\r\nghi");
+
+ // Test removing everything.
+ test(TEST, 0 .. 21, "", "");
+ }
}