mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 10:18:16 +00:00
feat: initiate lockDatabase
project resolution (#1201)
* feat: create a configuration * docs: edit description * docs: edit description * feat: add lock update * test: make configuration work
This commit is contained in:
parent
a325c6f6c8
commit
89c178295a
12 changed files with 216 additions and 49 deletions
|
@ -1,11 +1,32 @@
|
|||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_std::ImmutPath;
|
||||
use tinymist_world::EntryState;
|
||||
use typst::syntax::VirtualPath;
|
||||
|
||||
/// The kind of project resolution.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ProjectResolutionKind {
|
||||
/// Manage typst documents like what we did in Markdown. Each single file is
|
||||
/// an individual document and no project resolution is needed.
|
||||
/// This is the default behavior.
|
||||
#[default]
|
||||
SingleFile,
|
||||
/// Manage typst documents like what we did in Rust. For each workspace,
|
||||
/// tinymist tracks your preview and compilation history, and stores the
|
||||
/// information in a lock file. Tinymist will automatically selects the main
|
||||
/// file to use according to the lock file. This also allows other tools
|
||||
/// push preview and export tasks to language server by updating the
|
||||
/// lock file.
|
||||
LockDatabase,
|
||||
}
|
||||
|
||||
/// Entry resolver
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EntryResolver {
|
||||
/// The kind of project resolution.
|
||||
pub project_resolution: ProjectResolutionKind,
|
||||
/// Specifies the root path of the project manually.
|
||||
pub root_path: Option<ImmutPath>,
|
||||
/// The workspace roots from initialization.
|
||||
|
@ -78,6 +99,22 @@ impl EntryResolver {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn resolve_lock(&self, entry: &EntryState) -> Option<ImmutPath> {
|
||||
match self.project_resolution {
|
||||
ProjectResolutionKind::LockDatabase if entry.is_in_package() => {
|
||||
log::info!("ProjectResolver: no lock for package: {entry:?}");
|
||||
None
|
||||
}
|
||||
ProjectResolutionKind::LockDatabase => {
|
||||
let root = entry.workspace_root();
|
||||
log::info!("ProjectResolver: lock for {entry:?} at {root:?}");
|
||||
|
||||
root
|
||||
}
|
||||
ProjectResolutionKind::SingleFile => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the default entry path.
|
||||
pub fn resolve_default(&self) -> Option<ImmutPath> {
|
||||
let entry = self.entry.as_ref();
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
mod args;
|
||||
mod compiler;
|
||||
mod entry;
|
||||
pub mod font;
|
||||
mod lock;
|
||||
mod model;
|
||||
|
@ -11,6 +12,7 @@ mod watch;
|
|||
pub mod world;
|
||||
pub use args::*;
|
||||
pub use compiler::*;
|
||||
pub use entry::*;
|
||||
pub use lock::*;
|
||||
pub use model::*;
|
||||
pub use watch::*;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use ecow::EcoVec;
|
||||
use reflexo_typst::ImmutPath;
|
||||
use tinymist_std::path::unix_slash;
|
||||
use tinymist_world::EntryReader;
|
||||
use typst::{diag::EcoString, syntax::FileId};
|
||||
|
@ -9,12 +10,11 @@ use crate::model::{Id, ProjectInput, ProjectMaterial, ProjectRoute, ProjectTask,
|
|||
use crate::LspWorld;
|
||||
|
||||
/// Make a new project lock updater.
|
||||
pub fn update_lock(world: &LspWorld) -> Option<ProjectLockUpdater> {
|
||||
let root = world.entry_state().workspace_root()?;
|
||||
Some(ProjectLockUpdater {
|
||||
pub fn update_lock(root: ImmutPath) -> ProjectLockUpdater {
|
||||
ProjectLockUpdater {
|
||||
root,
|
||||
updates: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum LockUpdate {
|
||||
|
|
|
@ -19,8 +19,6 @@ pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt}
|
|||
pub use completion::PostfixSnippet;
|
||||
pub use upstream::with_vm;
|
||||
|
||||
mod entry;
|
||||
pub use entry::*;
|
||||
mod diagnostics;
|
||||
pub use diagnostics::*;
|
||||
mod code_action;
|
||||
|
|
|
@ -137,6 +137,10 @@ impl EntryState {
|
|||
pub fn is_inactive(&self) -> bool {
|
||||
self.main.is_none()
|
||||
}
|
||||
|
||||
pub fn is_in_package(&self) -> bool {
|
||||
self.main.is_some_and(WorkspaceResolver::is_package_file)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
@ -14,8 +14,9 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{json, Map, Value as JsonValue};
|
||||
use strum::IntoEnumIterator;
|
||||
use task::{FormatUserConfig, FormatterConfig};
|
||||
use tinymist_project::{EntryResolver, ProjectResolutionKind};
|
||||
use tinymist_query::analysis::{Modifier, TokenType};
|
||||
use tinymist_query::{CompletionFeat, EntryResolver, PositionEncoding};
|
||||
use tinymist_query::{CompletionFeat, PositionEncoding};
|
||||
use tinymist_render::PeriscopeArgs;
|
||||
use typst::foundations::IntoValue;
|
||||
use typst::syntax::FileId;
|
||||
|
@ -271,6 +272,7 @@ impl Initializer for SuperInit {
|
|||
// region Configuration Items
|
||||
const CONFIG_ITEMS: &[&str] = &[
|
||||
"tinymist",
|
||||
"projectResolution",
|
||||
"outputPath",
|
||||
"exportPdf",
|
||||
"rootPath",
|
||||
|
@ -292,6 +294,8 @@ const CONFIG_ITEMS: &[&str] = &[
|
|||
/// The user configuration read from the editor.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
/// The resolution kind of the project.
|
||||
pub project_resolution: ProjectResolutionKind,
|
||||
/// Constant configuration for the server.
|
||||
pub const_config: ConstConfig,
|
||||
/// The compile configurations
|
||||
|
@ -383,6 +387,7 @@ impl Config {
|
|||
.ok()
|
||||
}
|
||||
|
||||
assign_config!(project_resolution := "projectResolution"?: ProjectResolutionKind);
|
||||
assign_config!(semantic_tokens := "semanticTokens"?: SemanticTokensMode);
|
||||
assign_config!(formatter_mode := "formatterMode"?: FormatterMode);
|
||||
assign_config!(formatter_print_width := "formatterPrintWidth"?: Option<u32>);
|
||||
|
@ -529,6 +534,7 @@ impl CompileConfig {
|
|||
};
|
||||
}
|
||||
|
||||
let project_resolution = deser_or_default!("projectResolution", ProjectResolutionKind);
|
||||
self.output_path = deser_or_default!("outputPath", PathPattern);
|
||||
self.export_pdf = deser_or_default!("exportPdf", ExportMode);
|
||||
self.notify_status = match try_(|| update.get("compileStatus")?.as_str()) {
|
||||
|
@ -597,6 +603,7 @@ impl CompileConfig {
|
|||
self.font_paths = try_or_default(|| Vec::<_>::deserialize(update.get("fontPaths")?).ok());
|
||||
self.system_fonts = try_(|| update.get("systemFonts")?.as_bool());
|
||||
|
||||
self.entry_resolver.project_resolution = project_resolution;
|
||||
self.entry_resolver.root_path =
|
||||
try_(|| Some(Path::new(update.get("rootPath")?.as_str()?).into())).or_else(|| {
|
||||
self.typst_extra_args
|
||||
|
|
|
@ -30,7 +30,8 @@ use tinymist::{
|
|||
};
|
||||
use tinymist::{world::TaskInputs, world::WorldProvider};
|
||||
use tinymist_core::LONG_VERSION;
|
||||
use tinymist_query::{package::PackageInfo, EntryResolver};
|
||||
use tinymist_project::EntryResolver;
|
||||
use tinymist_query::package::PackageInfo;
|
||||
use typst::foundations::IntoValue;
|
||||
use typst_shim::utils::LazyHash;
|
||||
|
||||
|
|
|
@ -25,22 +25,21 @@ use sync_lsp::*;
|
|||
use task::{
|
||||
ExportConfig, ExportTask, ExportUserConfig, FormatTask, FormatterConfig, UserActionTask,
|
||||
};
|
||||
use tinymist_project::ProjectInsId;
|
||||
use tinymist_project::{CompileSnapshot, EntryResolver, ProjectInsId};
|
||||
use tinymist_query::analysis::{Analysis, PeriscopeProvider};
|
||||
use tinymist_query::{
|
||||
to_typst_range, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature,
|
||||
OnExportRequest, PositionEncoding, ServerInfoResponse, SyntaxRequest,
|
||||
to_typst_range, CompilerQueryRequest, CompilerQueryResponse, ExportKind, FoldRequestFeature,
|
||||
LocalContext, LspWorldExt, OnExportRequest, PageSelection, PositionEncoding,
|
||||
ServerInfoResponse, SyntaxRequest, VersionedDocument,
|
||||
};
|
||||
use tinymist_query::{EntryResolver, PageSelection};
|
||||
use tinymist_query::{ExportKind, LocalContext, VersionedDocument};
|
||||
use tinymist_render::PeriscopeRenderer;
|
||||
use tinymist_std::Error;
|
||||
use tinymist_std::ImmutPath;
|
||||
use tinymist_std::{Error, ImmutPath};
|
||||
use tokio::sync::mpsc;
|
||||
use typst::layout::Position as TypstPosition;
|
||||
use typst::{diag::FileResult, syntax::Source};
|
||||
|
||||
use crate::project::LspInterrupt;
|
||||
use crate::project::{update_lock, PROJECT_ROUTE_USER_ACTION_PRIORITY};
|
||||
use crate::project::{CompileServerOpts, ProjectCompiler};
|
||||
use crate::stats::CompilerQueryStats;
|
||||
use crate::world::vfs::{notify::MemoryEvent, FileChangeSet};
|
||||
|
@ -1008,12 +1007,36 @@ impl LanguageState {
|
|||
/// Export the current document.
|
||||
pub fn on_export(&mut self, req: OnExportRequest) -> QueryFuture {
|
||||
let OnExportRequest { path, kind, open } = req;
|
||||
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
|
||||
let lock_dir = self.compile_config().entry_resolver.resolve_lock(&entry);
|
||||
|
||||
let update_dep = lock_dir.clone().map(|lock_dir| {
|
||||
|snap: CompileSnapshot<LspCompilerFeat>| async move {
|
||||
let mut updater = update_lock(lock_dir);
|
||||
let world = snap.world.clone();
|
||||
let doc_id = updater.compiled(&world)?;
|
||||
|
||||
updater.update_materials(doc_id.clone(), snap.world.depended_files());
|
||||
updater.route(doc_id, PROJECT_ROUTE_USER_ACTION_PRIORITY);
|
||||
|
||||
updater.commit();
|
||||
|
||||
Some(())
|
||||
}
|
||||
});
|
||||
|
||||
let snap = self.snapshot()?;
|
||||
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
|
||||
let export = self.project.export.factory.oneshot(snap, Some(entry), kind);
|
||||
let task = self.project.export.factory.task();
|
||||
just_future(async move {
|
||||
let res = export.await?;
|
||||
let snap = snap.receive().await?;
|
||||
let snap = snap.task(TaskInputs {
|
||||
entry: Some(entry),
|
||||
..Default::default()
|
||||
});
|
||||
let res = task.oneshot(snap.clone(), kind, lock_dir).await?;
|
||||
if let Some(update_dep) = update_dep {
|
||||
tokio::spawn(update_dep(snap));
|
||||
}
|
||||
|
||||
// See https://github.com/Myriad-Dreamin/tinymist/issues/837
|
||||
// Also see https://github.com/Byron/open-rs/issues/105
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
use std::str::FromStr;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::project::{CompiledArtifact, ExportSignal};
|
||||
use crate::project::{
|
||||
CompiledArtifact, ExportHtmlTask, ExportMarkdownTask, ExportPdfTask, ExportPngTask,
|
||||
ExportSignal, ExportTextTask, ProjectTask, TaskWhen,
|
||||
};
|
||||
use anyhow::{bail, Context};
|
||||
use reflexo::ImmutPath;
|
||||
use reflexo_typst::TypstDatetime;
|
||||
use tinymist_project::{EntryReader, EntryState, TaskInputs};
|
||||
use tinymist_project::{CompileSnapshot, EntryReader};
|
||||
use tinymist_query::{ExportKind, PageSelection};
|
||||
use tokio::sync::mpsc;
|
||||
use typlite::Typlite;
|
||||
|
@ -20,8 +24,7 @@ use typst_pdf::PdfOptions;
|
|||
|
||||
use crate::tool::text::FullTextDigest;
|
||||
use crate::{
|
||||
actor::editor::EditorRequest, project::WorldSnapFut, tool::word_count, world::LspCompilerFeat,
|
||||
ExportMode, PathPattern,
|
||||
actor::editor::EditorRequest, tool::word_count, world::LspCompilerFeat, ExportMode, PathPattern,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
@ -63,25 +66,10 @@ impl ExportTask {
|
|||
}
|
||||
}
|
||||
|
||||
impl SyncTaskFactory<ExportConfig> {
|
||||
pub fn oneshot(
|
||||
&self,
|
||||
snap: WorldSnapFut,
|
||||
entry: Option<EntryState>,
|
||||
kind: ExportKind,
|
||||
) -> impl Future<Output = anyhow::Result<Option<PathBuf>>> {
|
||||
let export = self.task();
|
||||
async move {
|
||||
let snap = snap.receive().await?;
|
||||
let snap = snap.task(TaskInputs {
|
||||
entry,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let artifact = snap.compile();
|
||||
export.do_export(&kind, artifact).await
|
||||
}
|
||||
}
|
||||
pub struct ExportOnceTask<'a> {
|
||||
pub kind: &'a ExportKind,
|
||||
pub artifact: CompiledArtifact<LspCompilerFeat>,
|
||||
pub lock_path: Option<ImmutPath>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -129,7 +117,14 @@ impl ExportConfig {
|
|||
let this = self.clone();
|
||||
let artifact = artifact.clone();
|
||||
Box::pin(async move {
|
||||
log_err(this.do_export(&this.kind, artifact).await);
|
||||
log_err(
|
||||
this.do_export(ExportOnceTask {
|
||||
kind: &this.kind,
|
||||
artifact,
|
||||
lock_path: None,
|
||||
})
|
||||
.await,
|
||||
);
|
||||
Some(())
|
||||
})
|
||||
})?;
|
||||
|
@ -173,16 +168,16 @@ impl ExportConfig {
|
|||
Some(())
|
||||
}
|
||||
|
||||
async fn do_export(
|
||||
&self,
|
||||
kind: &ExportKind,
|
||||
artifact: CompiledArtifact<LspCompilerFeat>,
|
||||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
async fn do_export(&self, task: ExportOnceTask<'_>) -> anyhow::Result<Option<PathBuf>> {
|
||||
use reflexo_vec2svg::DefaultExportFeature;
|
||||
use ExportKind::*;
|
||||
use PageSelection::*;
|
||||
|
||||
let CompiledArtifact { snap, doc, .. } = artifact;
|
||||
let ExportOnceTask {
|
||||
kind,
|
||||
artifact: CompiledArtifact { snap, doc, .. },
|
||||
lock_path: lock_dir,
|
||||
} = task;
|
||||
|
||||
// Prepare the output path.
|
||||
let entry = snap.world.entry_state();
|
||||
|
@ -205,6 +200,68 @@ impl ExportConfig {
|
|||
}
|
||||
}
|
||||
|
||||
let _: Option<()> = lock_dir.and_then(|lock_dir| {
|
||||
let mut updater = crate::project::update_lock(lock_dir);
|
||||
|
||||
let doc_id = updater.compiled(&snap.world)?;
|
||||
let task_id = doc_id.clone();
|
||||
|
||||
let when = match self.config.mode {
|
||||
ExportMode::Never => TaskWhen::Never,
|
||||
ExportMode::OnType => TaskWhen::OnType,
|
||||
ExportMode::OnSave => TaskWhen::OnSave,
|
||||
ExportMode::OnDocumentHasTitle => TaskWhen::OnSave,
|
||||
};
|
||||
|
||||
// todo: page transforms
|
||||
let transforms = vec![];
|
||||
|
||||
use tinymist_project::ExportTask as ProjectExportTask;
|
||||
|
||||
let export = ProjectExportTask {
|
||||
document: doc_id,
|
||||
id: task_id,
|
||||
when,
|
||||
transform: transforms,
|
||||
};
|
||||
|
||||
let task = match kind {
|
||||
Pdf { creation_timestamp } => {
|
||||
let _ = creation_timestamp;
|
||||
ProjectTask::ExportPdf(ExportPdfTask {
|
||||
export,
|
||||
pdf_standards: Default::default(),
|
||||
})
|
||||
}
|
||||
Html {} => ProjectTask::ExportHtml(ExportHtmlTask { export }),
|
||||
Markdown {} => ProjectTask::ExportMarkdown(ExportMarkdownTask { export }),
|
||||
Text {} => ProjectTask::ExportText(ExportTextTask { export }),
|
||||
Query { .. } => {
|
||||
// todo: ignoring query task.
|
||||
return None;
|
||||
}
|
||||
Svg { page } => {
|
||||
// todo: ignoring page selection.
|
||||
let _ = page;
|
||||
return None;
|
||||
}
|
||||
Png { ppi, fill, page } => {
|
||||
// todo: ignoring page fill.
|
||||
let _ = fill;
|
||||
// todo: ignoring page selection.
|
||||
let _ = page;
|
||||
|
||||
let ppi = ppi.unwrap_or(144.) as f32;
|
||||
ProjectTask::ExportPng(ExportPngTask { export, ppi })
|
||||
}
|
||||
};
|
||||
|
||||
updater.task(task);
|
||||
updater.commit();
|
||||
|
||||
Some(())
|
||||
});
|
||||
|
||||
// Prepare the document.
|
||||
let doc = doc.map_err(|_| anyhow::anyhow!("no document"))?;
|
||||
|
||||
|
@ -327,6 +384,21 @@ impl ExportConfig {
|
|||
log::info!("RenderActor({kind:?}): export complete");
|
||||
Ok(Some(to))
|
||||
}
|
||||
|
||||
pub async fn oneshot(
|
||||
&self,
|
||||
snap: CompileSnapshot<LspCompilerFeat>,
|
||||
kind: ExportKind,
|
||||
lock_path: Option<ImmutPath>,
|
||||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
let artifact = snap.compile();
|
||||
self.do_export(ExportOnceTask {
|
||||
kind: &kind,
|
||||
artifact,
|
||||
lock_path,
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_color(fill: String) -> anyhow::Result<Color> {
|
||||
|
|
|
@ -34,7 +34,7 @@ impl<T: Clone> SyncTaskFactory<T> {
|
|||
*w = Arc::new(config);
|
||||
}
|
||||
|
||||
fn task(&self) -> Arc<T> {
|
||||
pub fn task(&self) -> Arc<T> {
|
||||
self.0.read().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue