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 })
|
Ok(Module { scope: ctx.scopes.top, template })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches evaluated modules.
|
|
||||||
pub type ModuleCache = HashMap<SourceId, Module>;
|
|
||||||
|
|
||||||
/// An evaluated module, ready for importing or execution.
|
/// An evaluated module, ready for importing or execution.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
@ -71,10 +68,10 @@ pub struct EvalContext<'a> {
|
|||||||
pub sources: &'a mut SourceStore,
|
pub sources: &'a mut SourceStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: &'a mut ImageStore,
|
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.
|
/// The stack of imported files that led to evaluation of the current file.
|
||||||
pub route: Vec<SourceId>,
|
pub route: Vec<SourceId>,
|
||||||
|
/// Caches imported modules.
|
||||||
|
pub modules: HashMap<SourceId, Module>,
|
||||||
/// The active scopes.
|
/// The active scopes.
|
||||||
pub scopes: Scopes<'a>,
|
pub scopes: Scopes<'a>,
|
||||||
/// The expression map for the currently built template.
|
/// The expression map for the currently built template.
|
||||||
@ -88,8 +85,8 @@ impl<'a> EvalContext<'a> {
|
|||||||
loader: ctx.loader.as_ref(),
|
loader: ctx.loader.as_ref(),
|
||||||
sources: &mut ctx.sources,
|
sources: &mut ctx.sources,
|
||||||
images: &mut ctx.images,
|
images: &mut ctx.images,
|
||||||
modules: &mut ctx.modules,
|
|
||||||
route: vec![source],
|
route: vec![source],
|
||||||
|
modules: HashMap::new(),
|
||||||
scopes: Scopes::new(Some(&ctx.std)),
|
scopes: Scopes::new(Some(&ctx.std)),
|
||||||
map: ExprMap::new(),
|
map: ExprMap::new(),
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ impl FontStore {
|
|||||||
|
|
||||||
/// Get a reference to a loaded face.
|
/// 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
|
/// only be called with ids returned by this store's
|
||||||
/// [`select()`](Self::select) method.
|
/// [`select()`](Self::select) method.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -79,7 +79,7 @@ impl ImageStore {
|
|||||||
|
|
||||||
/// Get a reference to a loaded image.
|
/// 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)
|
/// only be called with ids returned by this store's [`load()`](Self::load)
|
||||||
/// method.
|
/// method.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -50,7 +50,7 @@ pub mod util;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{ModuleCache, Scope};
|
use crate::eval::Scope;
|
||||||
use crate::exec::State;
|
use crate::exec::State;
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
@ -70,8 +70,6 @@ pub struct Context {
|
|||||||
pub fonts: FontStore,
|
pub fonts: FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
/// Caches evaluated modules.
|
|
||||||
pub modules: ModuleCache,
|
|
||||||
/// Caches layouting artifacts.
|
/// Caches layouting artifacts.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub layouts: LayoutCache,
|
pub layouts: LayoutCache,
|
||||||
@ -144,7 +142,6 @@ impl ContextBuilder {
|
|||||||
fonts: FontStore::new(Rc::clone(&loader)),
|
fonts: FontStore::new(Rc::clone(&loader)),
|
||||||
images: ImageStore::new(Rc::clone(&loader)),
|
images: ImageStore::new(Rc::clone(&loader)),
|
||||||
loader,
|
loader,
|
||||||
modules: ModuleCache::new(),
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
layouts: LayoutCache::new(),
|
layouts: LayoutCache::new(),
|
||||||
std: self.std.unwrap_or(library::new()),
|
std: self.std.unwrap_or(library::new()),
|
||||||
|
@ -100,9 +100,18 @@ impl SourceStore {
|
|||||||
id
|
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.
|
/// 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
|
/// should only be called with ids returned by this store's
|
||||||
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
|
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@ -126,17 +135,7 @@ impl SourceFile {
|
|||||||
/// Create a new source file.
|
/// Create a new source file.
|
||||||
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
|
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
|
||||||
let mut line_starts = vec![0];
|
let mut line_starts = vec![0];
|
||||||
let mut s = Scanner::new(&src);
|
line_starts.extend(newlines(&src));
|
||||||
|
|
||||||
while let Some(c) = s.eat() {
|
|
||||||
if is_newline(c) {
|
|
||||||
if c == '\r' {
|
|
||||||
s.eat_if('\n');
|
|
||||||
}
|
|
||||||
line_starts.push(s.index());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
path: path.normalize(),
|
path: path.normalize(),
|
||||||
@ -230,6 +229,27 @@ impl SourceFile {
|
|||||||
}
|
}
|
||||||
Some(range.start + (line.len() - chars.as_str().len()))
|
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 {
|
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")]
|
#[cfg(feature = "codespan-reporting")]
|
||||||
impl<'a> Files<'a> for SourceStore {
|
impl<'a> Files<'a> for SourceStore {
|
||||||
type FileId = SourceId;
|
type FileId = SourceId;
|
||||||
@ -297,7 +335,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_new() {
|
fn test_source_file_new() {
|
||||||
let source = SourceFile::detached(TEST);
|
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]
|
#[test]
|
||||||
@ -340,4 +378,33 @@ mod tests {
|
|||||||
roundtrip(&source, 12);
|
roundtrip(&source, 12);
|
||||||
roundtrip(&source, 21);
|
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