refactor: split entry resolver (#942)

* refactor: split entry resolver

* refactor: naming
This commit is contained in:
Myriad-Dreamin 2024-12-04 11:05:28 +08:00 committed by GitHub
parent c0d20cd1b2
commit 00aecd9eae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 171 additions and 130 deletions

View file

@ -28,7 +28,7 @@ use typ_server::{CompileServerActor, CompileServerOpts};
impl LanguageState {
/// Restart the primary server.
pub fn restart_primary(&mut self) {
let entry = self.compile_config().determine_default_entry_path();
let entry = self.entry_resolver().resolve_default();
self.restart_server("primary", entry);
}
@ -41,7 +41,7 @@ impl LanguageState {
fn restart_server(&mut self, group: &str, entry: Option<ImmutPath>) {
let server = self.server(
group.to_owned(),
self.compile_config().determine_entry(entry),
self.entry_resolver().resolve(entry),
self.compile_config().determine_inputs(),
self.vfs_snapshot(),
);

View file

@ -49,7 +49,7 @@ use crate::{
stats::{CompilerQueryStats, QueryStatGuard},
task::{ExportTask, ExportUserConfig},
world::{LspCompilerFeat, LspWorld},
CompileConfig,
CompileConfig, EntryResolver,
};
type EditorSender = mpsc::UnboundedSender<EditorRequest>;
@ -287,6 +287,11 @@ impl CompileClientActor {
self.handle.clone().snapshot()
}
/// Get the entry resolver.
pub fn entry_resolver(&self) -> &EntryResolver {
&self.config.entry_resolver
}
/// Snapshot the compiler thread for language queries
pub fn query_snapshot(&self) -> ZResult<QuerySnapFut> {
self.handle.clone().query_snapshot(None)
@ -321,7 +326,7 @@ impl CompileClientActor {
let OnExportRequest { path, kind, open } = req;
let snap = self.snapshot()?;
let entry = self.config.determine_entry(Some(path.as_path().into()));
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
let export = self.handle.export.oneshot(snap, Some(entry), kind);
just_future(async move {
let res = export.await?;
@ -369,7 +374,7 @@ impl CompileClientActor {
return Err(error_once!("entry file must be absolute", path: path.unwrap().display()));
}
let next_entry = self.config.determine_entry(path);
let next_entry = self.entry_resolver().resolve(path);
if next_entry == self.entry {
return Ok(false);
}

View file

@ -456,7 +456,7 @@ impl LanguageState {
let self_path = std::env::current_exe()
.map_err(|e| internal_error(format!("Cannot get typst compiler {e}")))?;
let entry = self.config.compile.determine_entry(Some(path));
let entry = self.entry_resolver().resolve(Some(path));
let snap = self.primary().snapshot().map_err(z_internal_error)?;
let user_action = self.user_action;

View file

@ -70,22 +70,26 @@ impl Initializer for RegularInit {
/// Errors if the configuration could not be updated.
fn initialize(mut self, params: InitializeParams) -> (LanguageState, AnySchedulableResponse) {
// Initialize configurations
let roots = match params.workspace_folders.as_ref() {
Some(roots) => roots
.iter()
.filter_map(|root| root.uri.to_file_path().ok().map(ImmutPath::from))
.collect(),
#[allow(deprecated)] // `params.root_path` is marked as deprecated
None => params
.root_uri
.as_ref()
.and_then(|uri| uri.to_file_path().ok().map(ImmutPath::from))
.or_else(|| Some(Path::new(&params.root_path.as_ref()?).into()))
.into_iter()
.collect(),
};
let mut config = Config {
const_config: ConstConfig::from(&params),
compile: CompileConfig {
roots: match params.workspace_folders.as_ref() {
Some(roots) => roots
.iter()
.filter_map(|root| root.uri.to_file_path().ok().map(ImmutPath::from))
.collect(),
#[allow(deprecated)] // `params.root_path` is marked as deprecated
None => params
.root_uri
.as_ref()
.and_then(|uri| uri.to_file_path().ok().map(ImmutPath::from))
.or_else(|| Some(Path::new(&params.root_path?).into()))
.into_iter()
.collect(),
entry_resolver: EntryResolver {
roots,
..Default::default()
},
font_opts: std::mem::take(&mut self.font_opts),
..CompileConfig::default()
@ -465,17 +469,125 @@ impl From<&InitializeParams> for ConstConfig {
}
}
/// Entry resolver
#[derive(Debug, Default, Clone)]
pub struct EntryResolver {
/// Specifies the root path of the project manually.
pub root_path: Option<ImmutPath>,
/// The workspace roots from initialization.
pub roots: Vec<ImmutPath>,
/// Typst extra arguments.
pub typst_extra_args: Option<CompileExtraOpts>,
}
impl EntryResolver {
/// Resolves the root directory for the entry file.
pub fn root(&self, entry: Option<&ImmutPath>) -> Option<ImmutPath> {
if let Some(path) = &self.root_path {
return Some(path.clone());
}
if let Some(root) = try_(|| self.typst_extra_args.as_ref()?.root_dir.as_ref()) {
return Some(root.clone());
}
if let Some(entry) = entry {
for root in self.roots.iter() {
if entry.starts_with(root) {
return Some(root.clone());
}
}
if !self.roots.is_empty() {
log::warn!("entry is not in any set root directory");
}
if let Some(parent) = entry.parent() {
return Some(parent.into());
}
}
if !self.roots.is_empty() {
return Some(self.roots[0].clone());
}
None
}
/// Resolves the entry state.
pub fn resolve(&self, entry: Option<ImmutPath>) -> EntryState {
// todo: formalize untitled path
// let is_untitled = entry.as_ref().is_some_and(|p| p.starts_with("/untitled"));
// let root_dir = self.determine_root(if is_untitled { None } else {
// entry.as_ref() });
let root_dir = self.root(entry.as_ref());
let entry = match (entry, root_dir) {
// (Some(entry), Some(root)) if is_untitled => Some(EntryState::new_rooted(
// root,
// Some(FileId::new(None, VirtualPath::new(entry))),
// )),
(Some(entry), Some(root)) => match entry.strip_prefix(&root) {
Ok(stripped) => Some(EntryState::new_rooted(
root,
Some(FileId::new(None, VirtualPath::new(stripped))),
)),
Err(err) => {
log::info!("Entry is not in root directory: err {err:?}: entry: {entry:?}, root: {root:?}");
EntryState::new_rootless(entry)
}
},
(Some(entry), None) => EntryState::new_rootless(entry),
(None, Some(root)) => Some(EntryState::new_workspace(root)),
(None, None) => None,
};
entry.unwrap_or_else(|| match self.root(None) {
Some(root) => EntryState::new_workspace(root),
None => EntryState::new_detached(),
})
}
/// Determines the default entry path.
pub fn resolve_default(&self) -> Option<ImmutPath> {
let extras = self.typst_extra_args.as_ref()?;
// todo: pre-compute this when updating config
if let Some(entry) = &extras.entry {
if entry.is_relative() {
let root = self.root(None)?;
return Some(root.join(entry).as_path().into());
}
}
extras.entry.clone()
}
/// Validates the configuration.
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(root) = &self.root_path {
if !root.is_absolute() {
bail!("rootPath must be an absolute path: {root:?}");
}
}
if let Some(extra_args) = &self.typst_extra_args {
if let Some(root) = &extra_args.root_dir {
if !root.is_absolute() {
bail!("typstExtraArgs.root must be an absolute path: {root:?}");
}
}
}
Ok(())
}
}
/// The user configuration read from the editor.
#[derive(Debug, Default, Clone)]
pub struct CompileConfig {
/// The workspace roots from initialization.
pub roots: Vec<ImmutPath>,
/// The output directory for PDF export.
pub output_path: PathPattern,
/// The mode of PDF export.
pub export_pdf: ExportMode,
/// Specifies the root path of the project manually.
pub root_path: Option<ImmutPath>,
/// Specifies the cli font options
pub font_opts: CompileFontArgs,
/// Whether to ignore system fonts
@ -496,6 +608,8 @@ pub struct CompileConfig {
pub has_default_entry_path: bool,
/// The inputs for the language server protocol.
pub lsp_inputs: ImmutDict,
/// The entry resolver.
pub entry_resolver: EntryResolver,
}
impl CompileConfig {
@ -518,7 +632,6 @@ impl CompileConfig {
self.output_path = deser_or_default!("outputPath", PathPattern);
self.export_pdf = deser_or_default!("exportPdf", ExportMode);
self.root_path = try_(|| Some(Path::new(update.get("rootPath")?.as_str()?).into()));
self.notify_status = match try_(|| update.get("compileStatus")?.as_str()) {
Some("enable") => true,
Some("disable") | None => false,
@ -585,7 +698,10 @@ impl CompileConfig {
self.font_paths = try_or_default(|| Vec::<_>::deserialize(update.get("fontPaths")?).ok());
self.system_fonts = try_(|| update.get("systemFonts")?.as_bool());
self.has_default_entry_path = self.determine_default_entry_path().is_some();
self.entry_resolver.root_path =
try_(|| Some(Path::new(update.get("rootPath")?.as_str()?).into()));
self.entry_resolver.typst_extra_args = self.typst_extra_args.clone();
self.has_default_entry_path = self.entry_resolver.resolve_default().is_some();
self.lsp_inputs = {
let mut dict = TypstDict::default();
@ -612,86 +728,6 @@ impl CompileConfig {
self.validate()
}
/// Determines the root directory for the entry file.
pub fn determine_root(&self, entry: Option<&ImmutPath>) -> Option<ImmutPath> {
if let Some(path) = &self.root_path {
return Some(path.clone());
}
if let Some(root) = try_(|| self.typst_extra_args.as_ref()?.root_dir.as_ref()) {
return Some(root.clone());
}
if let Some(entry) = entry {
for root in self.roots.iter() {
if entry.starts_with(root) {
return Some(root.clone());
}
}
if !self.roots.is_empty() {
log::warn!("entry is not in any set root directory");
}
if let Some(parent) = entry.parent() {
return Some(parent.into());
}
}
if !self.roots.is_empty() {
return Some(self.roots[0].clone());
}
None
}
/// Determines the entry state.
pub fn determine_entry(&self, entry: Option<ImmutPath>) -> EntryState {
// todo: formalize untitled path
// let is_untitled = entry.as_ref().is_some_and(|p| p.starts_with("/untitled"));
// let root_dir = self.determine_root(if is_untitled { None } else {
// entry.as_ref() });
let root_dir = self.determine_root(entry.as_ref());
let entry = match (entry, root_dir) {
// (Some(entry), Some(root)) if is_untitled => Some(EntryState::new_rooted(
// root,
// Some(FileId::new(None, VirtualPath::new(entry))),
// )),
(Some(entry), Some(root)) => match entry.strip_prefix(&root) {
Ok(stripped) => Some(EntryState::new_rooted(
root,
Some(FileId::new(None, VirtualPath::new(stripped))),
)),
Err(err) => {
log::info!("Entry is not in root directory: err {err:?}: entry: {entry:?}, root: {root:?}");
EntryState::new_rootless(entry)
}
},
(Some(entry), None) => EntryState::new_rootless(entry),
(None, Some(root)) => Some(EntryState::new_workspace(root)),
(None, None) => None,
};
entry.unwrap_or_else(|| match self.determine_root(None) {
Some(root) => EntryState::new_workspace(root),
None => EntryState::new_detached(),
})
}
/// Determines the default entry path.
pub fn determine_default_entry_path(&self) -> Option<ImmutPath> {
let extras = self.typst_extra_args.as_ref()?;
// todo: pre-compute this when updating config
if let Some(entry) = &extras.entry {
if entry.is_relative() {
let root = self.determine_root(None)?;
return Some(root.join(entry).as_path().into());
}
}
extras.entry.clone()
}
/// Determines the font options.
pub fn determine_font_opts(&self) -> CompileFontArgs {
let mut opts = self.font_opts.clone();
@ -714,7 +750,7 @@ impl CompileConfig {
let root = OnceCell::new();
for path in opts.font_paths.iter_mut() {
if path.is_relative() {
if let Some(root) = root.get_or_init(|| self.determine_root(None)) {
if let Some(root) = root.get_or_init(|| self.entry_resolver.root(None)) {
let p = std::mem::take(path);
*path = root.join(p);
}
@ -800,25 +836,14 @@ impl CompileConfig {
self.system_fonts,
&self.font_paths,
self.typst_extra_args.as_ref().map(|e| &e.font),
self.determine_root(self.determine_default_entry_path().as_ref()),
self.entry_resolver
.root(self.entry_resolver.resolve_default().as_ref()),
)
}
/// Validates the configuration.
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(root) = &self.root_path {
if !root.is_absolute() {
bail!("rootPath must be an absolute path: {root:?}");
}
}
if let Some(extra_args) = &self.typst_extra_args {
if let Some(root) = &extra_args.root_dir {
if !root.is_absolute() {
bail!("typstExtraArgs.root must be an absolute path: {root:?}");
}
}
}
self.entry_resolver.validate()?;
Ok(())
}
@ -1000,7 +1025,10 @@ mod tests {
assert_eq!(config.compile.output_path, PathPattern::new("out"));
assert_eq!(config.compile.export_pdf, ExportMode::OnSave);
assert_eq!(config.compile.root_path, Some(ImmutPath::from(root_path)));
assert_eq!(
config.compile.entry_resolver.root_path,
Some(ImmutPath::from(root_path))
);
assert_eq!(config.semantic_tokens, SemanticTokensMode::Enable);
assert_eq!(config.formatter_mode, FormatterMode::Typstyle);
assert_eq!(

View file

@ -24,7 +24,9 @@ use sync_lsp::{
transport::{with_stdio_transport, MirrorArgs},
LspBuilder, LspClientRoot, LspResult,
};
use tinymist::{CompileConfig, Config, LanguageState, RegularInit, SuperInit, UserActionTask};
use tinymist::{
CompileConfig, Config, EntryResolver, LanguageState, RegularInit, SuperInit, UserActionTask,
};
use tinymist_query::package::PackageInfo;
use typst::foundations::IntoValue;
use typst_shim::utils::LazyHash;
@ -163,10 +165,13 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> anyhow::Result<()> {
with_stdio_transport(args.mirror.clone(), |conn| {
let client_root = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client = client_root.weak();
let roots = vec![ImmutPath::from(root_path)];
let config = Config {
compile: CompileConfig {
roots: vec![ImmutPath::from(root_path)],
entry_resolver: EntryResolver {
roots,
..Default::default()
},
font_opts: args.compile.font,
..CompileConfig::default()
},
@ -205,9 +210,7 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> anyhow::Result<()> {
let state = service.state_mut().unwrap();
let entry = state
.compile_config()
.determine_entry(Some(input.as_path().into()));
let entry = state.entry_resolver().resolve(Some(input.as_path().into()));
let snap = state.primary().snapshot().unwrap();

View file

@ -165,6 +165,11 @@ impl LanguageState {
&self.config.compile
}
/// Get the entry resolver.
pub fn entry_resolver(&self) -> &EntryResolver {
&self.compile_config().entry_resolver
}
/// Get the primary compile server for those commands without task context.
pub fn primary(&self) -> &CompileClientActor {
self.primary.as_ref().expect("primary")
@ -849,7 +854,7 @@ impl LanguageState {
pub fn pin_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
self.pinning = new_entry.is_some();
let entry = new_entry
.or_else(|| self.config.compile.determine_default_entry_path())
.or_else(|| self.entry_resolver().resolve_default())
.or_else(|| self.focusing.clone());
self.do_change_entry(entry).map(|_| ())
}
@ -1047,9 +1052,9 @@ impl LanguageState {
let fut_stat = client.query_snapshot_with_stat(&query)?;
let entry = query
.associated_path()
.map(|path| client.config.determine_entry(Some(path.into())))
.map(|path| client.entry_resolver().resolve(Some(path.into())))
.or_else(|| {
let root = client.config.determine_root(None)?;
let root = client.entry_resolver().root(None)?;
Some(EntryState::new_rooted(root, Some(*DETACHED_ENTRY)))
});