mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Editable source files
This commit is contained in:
parent
7e6e7e928c
commit
ba6b91e2ee
@ -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(),
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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()),
|
||||
|
@ -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, "", "");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user