From ba6b91e2ee9dfa729d21879f27773de7c7731cfe Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 16 Aug 2021 14:33:32 +0200 Subject: [PATCH] Editable source files --- src/eval/mod.rs | 9 ++--- src/font.rs | 2 +- src/image.rs | 2 +- src/lib.rs | 5 +-- src/source.rs | 93 ++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 30b34798d..f2afbafb0 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; - /// 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, + /// Caches imported modules. + pub modules: HashMap, /// 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 313dcf8f0..300774bbf 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 f98c7b1b1..6ccfb11d4 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 ce753f869..41ad1d167 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 0f1637a96..877fd7294 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, 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, 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 for SourceFile { @@ -238,6 +258,24 @@ impl AsRef 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 + '_ { + 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, 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, "", ""); + } }