dev: stateful requests now accept snapshot (#1581)

* dev: stateful requests now accept snapshot

* dev: add some convenient methods

* dev: remove unused latest_doc state

* dev: use graph

* docs: comment

* fix: bad flag
This commit is contained in:
Myriad-Dreamin 2025-03-25 16:28:00 +08:00 committed by GitHub
parent 5b42231a77
commit 10ec787cc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 383 additions and 504 deletions

View file

@ -1,5 +1,6 @@
//! Linked definition analysis
use tinymist_std::typst::TypstDocument;
use typst::foundations::{IntoValue, Label, Selector, Type};
use typst::introspection::Introspector;
use typst::model::BibliographyElem;
@ -7,7 +8,6 @@ use typst::model::BibliographyElem;
use super::{prelude::*, InsTy, SharedContext};
use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass, VarClass};
use crate::ty::DocSource;
use crate::VersionedDocument;
/// A linked definition in the source code
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@ -59,7 +59,7 @@ impl Definition {
pub fn definition(
ctx: &Arc<SharedContext>,
source: &Source,
document: Option<&VersionedDocument>,
document: Option<&TypstDocument>,
syntax: SyntaxClass,
) -> Option<Definition> {
match syntax {
@ -81,7 +81,7 @@ pub fn definition(
_ => return None,
};
let introspector = &document?.document.introspector();
let introspector = &document?.introspector();
bib_definition(ctx, introspector, name)
.or_else(|| ref_definition(introspector, name, ref_expr))
}

View file

@ -11,6 +11,7 @@ use rustc_hash::FxHashMap;
use tinymist_project::LspWorld;
use tinymist_std::debug_loc::DataSource;
use tinymist_std::hash::{hash128, FxDashMap};
use tinymist_std::typst::TypstDocument;
use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
use tinymist_world::{EntryReader, DETACHED_ENTRY};
use typst::diag::{eco_format, At, FileError, FileResult, SourceResult, StrResult};
@ -37,7 +38,6 @@ use crate::syntax::{
use crate::upstream::{tooltip_, Tooltip};
use crate::{
ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
VersionedDocument,
};
use super::TypeEnv;
@ -195,7 +195,7 @@ pub trait PeriscopeProvider {
fn periscope_at(
&self,
_ctx: &mut LocalContext,
_doc: VersionedDocument,
_doc: &TypstDocument,
_pos: Position,
) -> Option<String> {
None
@ -796,7 +796,7 @@ impl SharedContext {
pub(crate) fn def_of_span(
self: &Arc<Self>,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
span: Span,
) -> Option<Definition> {
let syntax = self.classify_span(source, span)?;
@ -814,7 +814,7 @@ impl SharedContext {
pub(crate) fn def_of_syntax(
self: &Arc<Self>,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
syntax: SyntaxClass,
) -> Option<Definition> {
definition(self, source, doc, syntax)

View file

@ -41,11 +41,7 @@ pub struct CompletionRequest {
impl StatefulRequest for CompletionRequest {
type Response = CompletionList;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
// These trigger characters are for completion on positional arguments,
// which follows the configuration item
// `tinymist.completion.triggerOnSnippetPlaceholders`.
@ -55,7 +51,7 @@ impl StatefulRequest for CompletionRequest {
return None;
}
let document = doc.as_ref().map(|doc| &doc.document);
let document = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let cursor = ctx.to_typst_pos_offset(&source, self.position, 0)?;
@ -134,7 +130,7 @@ mod tests {
let mut includes = HashSet::new();
let mut excludes = HashSet::new();
let doc = compile_doc_for_test(ctx, &properties);
let graph = compile_doc_for_test(ctx, &properties);
for kk in properties.get("contains").iter().flat_map(|v| v.split(',')) {
// split first char
@ -188,7 +184,7 @@ mod tests {
trigger_character,
};
let result = request
.request(ctx, doc.clone())
.request(ctx, graph.clone())
.map(|list| CompletionList {
is_incomplete: list.is_incomplete,
items: get_items(list.items),

View file

@ -98,13 +98,8 @@ pub struct DocumentMetricsRequest {
impl StatefulRequest for DocumentMetricsRequest {
type Response = DocumentMetricsResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
let doc = doc?;
let doc = doc.document;
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref()?;
let mut worker = DocumentMetricsWorker {
ctx,
@ -113,7 +108,7 @@ impl StatefulRequest for DocumentMetricsRequest {
font_info: Default::default(),
};
worker.work(&doc)?;
worker.work(doc)?;
let font_info = worker.compute()?;
let span_info = SpanInfo {

View file

@ -26,16 +26,13 @@ pub struct GotoDefinitionRequest {
impl StatefulRequest for GotoDefinitionRequest {
type Response = GotoDefinitionResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax)?;
let def = ctx.def_of_syntax(&source, doc, syntax)?;
let (fid, def_range) = def.location(ctx.shared())?;
let uri = ctx.uri_for_id(fid).ok()?;

View file

@ -1,5 +1,6 @@
use core::fmt::{self, Write};
use tinymist_std::typst::TypstDocument;
use typst::foundations::repr::separated_list;
use typst_shim::syntax::LinkedNodeExt;
@ -26,11 +27,8 @@ pub struct HoverRequest {
impl StatefulRequest for HoverRequest {
type Response = Hover;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.clone();
let source = ctx.source_by_path(&self.path).ok()?;
let offset = ctx.to_typst_pos(self.position, &source)?;
// the typst's cursor is 1-based, so we need to add 1 to the offset
@ -80,7 +78,7 @@ impl StatefulRequest for HoverRequest {
struct HoverWorker<'a> {
ctx: &'a mut LocalContext,
source: Source,
doc: Option<VersionedDocument>,
doc: Option<TypstDocument>,
cursor: usize,
def: Vec<String>,
value: Vec<String>,
@ -250,19 +248,19 @@ impl HoverWorker<'_> {
// Preview results
let provider = self.ctx.analysis.periscope.clone()?;
let doc = self.doc.as_ref()?;
let position = jump_from_cursor(&doc.document, &self.source, self.cursor);
let position = jump_from_cursor(doc, &self.source, self.cursor);
let position = position.or_else(|| {
for idx in 1..100 {
let next_cursor = self.cursor + idx;
if next_cursor < self.source.text().len() {
let position = jump_from_cursor(&doc.document, &self.source, next_cursor);
let position = jump_from_cursor(doc, &self.source, next_cursor);
if position.is_some() {
return position;
}
}
let prev_cursor = self.cursor.checked_sub(idx);
if let Some(prev_cursor) = prev_cursor {
let position = jump_from_cursor(&doc.document, &self.source, prev_cursor);
let position = jump_from_cursor(doc, &self.source, prev_cursor);
if position.is_some() {
return position;
}
@ -274,7 +272,7 @@ impl HoverWorker<'_> {
log::info!("telescope position: {position:?}");
let preview_content = provider.periscope_at(self.ctx, doc.clone(), position?)?;
let preview_content = provider.periscope_at(self.ctx, doc, position?)?;
self.preview.push(preview_content);
Some(())
}
@ -390,15 +388,16 @@ mod tests {
#[test]
fn test() {
snapshot_testing("hover", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("hover", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = HoverRequest {
path: path.clone(),
position: find_test_position(&source),
};
let result = request.request(world, None);
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let result = request.request(ctx, snap);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}

View file

@ -7,97 +7,88 @@
//! code. Currently it provides:
//! + language queries defined by the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
mod adt;
pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt};
pub use completion::{CompletionRequest, PostfixSnippet};
pub use typlite::ColorTheme;
pub use upstream::with_vm;
pub use code_action::*;
pub use code_context::*;
pub use code_lens::*;
pub use color_presentation::*;
pub use diagnostics::*;
pub use document_color::*;
pub use document_highlight::*;
pub use document_link::*;
pub use document_metrics::*;
pub use document_symbol::*;
pub use folding_range::*;
pub use goto_declaration::*;
pub use goto_definition::*;
pub use hover::*;
pub use inlay_hint::*;
pub use jump::*;
pub use lsp_typst_boundary::*;
pub use on_enter::*;
pub use prepare_rename::*;
pub use references::*;
pub use rename::*;
pub use selection_range::*;
pub use semantic_tokens_delta::*;
pub use semantic_tokens_full::*;
pub use signature_help::*;
pub use symbol::*;
pub use will_rename_files::*;
pub use workspace_label::*;
pub mod analysis;
pub mod docs;
pub mod package;
pub mod syntax;
pub mod testing;
pub mod ty;
mod upstream;
pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt};
pub use completion::PostfixSnippet;
pub use upstream::with_vm;
mod diagnostics;
pub use diagnostics::*;
mod code_action;
pub use code_action::*;
mod code_context;
pub use code_context::*;
mod code_lens;
pub use code_lens::*;
mod completion;
pub use completion::CompletionRequest;
mod color_presentation;
pub use color_presentation::*;
mod document_color;
pub use document_color::*;
mod document_highlight;
pub use document_highlight::*;
mod document_symbol;
pub use document_symbol::*;
mod document_link;
pub use document_link::*;
mod workspace_label;
pub use workspace_label::*;
mod document_metrics;
pub use document_metrics::*;
mod folding_range;
pub use folding_range::*;
mod goto_declaration;
pub use goto_declaration::*;
mod goto_definition;
pub use goto_definition::*;
mod hover;
pub use hover::*;
mod inlay_hint;
pub use inlay_hint::*;
mod jump;
pub use jump::*;
mod will_rename_files;
pub use will_rename_files::*;
mod rename;
pub use rename::*;
mod selection_range;
pub use selection_range::*;
mod semantic_tokens_full;
pub use semantic_tokens_full::*;
mod semantic_tokens_delta;
pub use semantic_tokens_delta::*;
mod signature_help;
pub use signature_help::*;
mod symbol;
pub use symbol::*;
mod on_enter;
pub use on_enter::*;
mod prepare_rename;
pub use prepare_rename::*;
mod references;
pub use references::*;
mod lsp_typst_boundary;
pub use lsp_typst_boundary::*;
mod prelude;
use tinymist_std::typst::TypstDocument;
use typst::syntax::Source;
/// The physical position in a document.
pub type FramePosition = typst::layout::Position;
pub use typlite::ColorTheme;
mod adt;
mod lsp_typst_boundary;
mod prelude;
/// A compiled document with an self-incremented logical version.
#[derive(Debug, Clone)]
pub struct VersionedDocument {
/// The version of the document.
pub version: usize,
/// The compiled document.
pub document: TypstDocument,
}
mod code_action;
mod code_context;
mod code_lens;
mod color_presentation;
mod completion;
mod diagnostics;
mod document_color;
mod document_highlight;
mod document_link;
mod document_metrics;
mod document_symbol;
mod folding_range;
mod goto_declaration;
mod goto_definition;
mod hover;
mod inlay_hint;
mod jump;
mod on_enter;
mod prepare_rename;
mod references;
mod rename;
mod selection_range;
mod semantic_tokens_delta;
mod semantic_tokens_full;
mod signature_help;
mod symbol;
mod upstream;
mod will_rename_files;
mod workspace_label;
use typst::syntax::Source;
use tinymist_analysis::log_debug_ct;
use tinymist_project::LspComputeGraph;
/// A request handler with given syntax information.
pub trait SyntaxRequest {
@ -121,22 +112,16 @@ pub trait SemanticRequest {
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response>;
}
/// A request handler with given (semantic) analysis context and a versioned
/// document.
/// A request handler with given (semantic) analysis context and a project
/// snapshot.
pub trait StatefulRequest {
/// The response type of the request.
type Response;
/// Request the information from the given context.
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response>;
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response>;
}
use tinymist_analysis::log_debug_ct;
#[allow(missing_docs)]
mod polymorphic {
use completion::CompletionList;

View file

@ -17,6 +17,7 @@ pub use lsp_types::{
SignatureHelp, SignatureInformation, SymbolInformation, TextEdit, Url, WorkspaceEdit,
};
pub use serde_json::Value as JsonValue;
pub use tinymist_project::LspComputeGraph;
pub use tinymist_std::DefId;
pub use typst::diag::{EcoString, Tracepoint};
pub use typst::foundations::Value;
@ -35,4 +36,4 @@ pub use crate::lsp_typst_boundary::{
};
pub use crate::syntax::{classify_syntax, Decl, DefKind};
pub(crate) use crate::ty::PathPreference;
pub use crate::{SemanticRequest, StatefulRequest, VersionedDocument};
pub use crate::{SemanticRequest, StatefulRequest};

View file

@ -34,11 +34,8 @@ pub struct PrepareRenameRequest {
impl StatefulRequest for PrepareRenameRequest {
type Response = PrepareRenameResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
@ -48,7 +45,7 @@ impl StatefulRequest for PrepareRenameRequest {
}
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
let def = ctx.def_of_syntax(&source, doc, syntax.clone())?;
let (name, range) = prepare_renaming(ctx, &syntax, &def)?;
@ -136,15 +133,16 @@ mod tests {
#[test]
fn prepare() {
snapshot_testing("rename", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("rename", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = PrepareRenameRequest {
path: path.clone(),
position: find_test_position(&source),
};
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let result = request.request(world, None);
let result = request.request(ctx, snap);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}

View file

@ -1,5 +1,6 @@
use std::sync::OnceLock;
use tinymist_std::typst::TypstDocument;
use typst::syntax::Span;
use crate::{
@ -25,15 +26,12 @@ pub struct ReferencesRequest {
impl StatefulRequest for ReferencesRequest {
type Response = Vec<LspLocation>;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let locations = find_references(ctx, &source, doc.as_ref(), syntax)?;
let locations = find_references(ctx, &source, doc, syntax)?;
crate::log_debug_ct!("references: {locations:?}");
Some(locations)
@ -43,7 +41,7 @@ impl StatefulRequest for ReferencesRequest {
pub(crate) fn find_references(
ctx: &mut LocalContext,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
syntax: SyntaxClass<'_>,
) -> Option<Vec<LspLocation>> {
let finding_label = match syntax {

View file

@ -36,15 +36,13 @@ pub struct RenameRequest {
impl StatefulRequest for RenameRequest {
type Response = WorkspaceEdit;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
let def = ctx.def_of_syntax(&source, doc, syntax.clone())?;
prepare_renaming(ctx, &syntax, &def)?;
@ -95,7 +93,7 @@ impl StatefulRequest for RenameRequest {
})
}
_ => {
let references = find_references(ctx, &source, doc.as_ref(), syntax)?;
let references = find_references(ctx, &source, doc, syntax)?;
let mut edits = HashMap::new();
@ -321,16 +319,17 @@ mod tests {
#[test]
fn test() {
snapshot_testing("rename", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("rename", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = RenameRequest {
path: path.clone(),
position: find_test_position(&source),
new_name: "new_name".to_string(),
};
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let mut result = request.request(world, None);
let mut result = request.request(ctx, snap);
// sort the edits to make the snapshot stable
if let Some(r) = result.as_mut().and_then(|r| r.changes.as_mut()) {
for edits in r.values_mut() {

View file

@ -10,14 +10,12 @@ use std::{
use once_cell::sync::Lazy;
use serde_json::{ser::PrettyFormatter, Serializer, Value};
use tinymist_project::{CompileFontArgs, ExportTarget};
use tinymist_project::{CompileFontArgs, ExportTarget, LspCompileSnapshot, LspComputeGraph};
use tinymist_std::debug_loc::LspRange;
use tinymist_std::typst::TypstDocument;
use tinymist_world::package::PackageSpec;
use tinymist_world::vfs::WorkspaceResolver;
use tinymist_world::EntryState;
use tinymist_world::TaskInputs;
use tinymist_world::{EntryManager, EntryReader, ShadowApi};
use tinymist_world::{EntryManager, EntryReader, EntryState, ShadowApi, TaskInputs};
use typst::foundations::Bytes;
use typst::syntax::ast::{self, AstNode};
use typst::syntax::{LinkedNode, Source, SyntaxKind, VirtualPath};
@ -26,12 +24,11 @@ pub use insta::assert_snapshot;
pub use serde::Serialize;
pub use serde_json::json;
pub use tinymist_project::{LspUniverse, LspUniverseBuilder};
pub use tinymist_world::WorldComputeGraph;
use typst_shim::syntax::LinkedNodeExt;
use crate::syntax::find_module_level_docs;
use crate::{
analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding, VersionedDocument,
};
use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding};
use crate::{to_lsp_position, CompletionFeat, LspWorldExt};
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut LocalContext, PathBuf)) {
@ -122,13 +119,13 @@ pub fn get_test_properties(s: &str) -> HashMap<&'_ str, &'_ str> {
pub fn compile_doc_for_test(
ctx: &mut LocalContext,
properties: &HashMap<&str, &str>,
) -> Option<VersionedDocument> {
) -> LspComputeGraph {
let prev = ctx.world.entry_state();
let next = match properties.get("compile")?.trim() {
"true" => prev.clone(),
"false" => return None,
path if path.ends_with(".typ") => prev.select_in_workspace(Path::new(path)),
v => panic!("invalid value for 'compile' property: {v}"),
let next = match properties.get("compile").map(|s| s.trim()) {
Some("true") => prev.clone(),
None | Some("false") => return WorldComputeGraph::from_world(ctx.world.clone()),
Some(path) if path.ends_with(".typ") => prev.select_in_workspace(Path::new(path)),
v => panic!("invalid value for 'compile' property: {v:?}"),
};
let mut world = Cow::Borrowed(&ctx.world);
@ -138,14 +135,12 @@ pub fn compile_doc_for_test(
..Default::default()
}));
}
let mut world = world.into_owned();
world.set_is_compiling(true);
let mut snap = LspCompileSnapshot::from_world(world.into_owned());
snap.world.set_is_compiling(true);
let doc = typst::compile(&world).output.unwrap();
Some(VersionedDocument {
version: 0,
document: TypstDocument::Paged(Arc::new(doc)),
})
let doc = typst::compile(&snap.world).output.unwrap();
snap.success_doc = Some(TypstDocument::Paged(Arc::new(doc)));
WorldComputeGraph::new(snap)
}
pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBuf) -> T) -> T {

View file

@ -15,11 +15,7 @@ pub struct WillRenameFilesRequest {
impl StatefulRequest for WillRenameFilesRequest {
type Response = WorkspaceEdit;
fn request(
self,
ctx: &mut LocalContext,
_doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, _graph: LspComputeGraph) -> Option<Self::Response> {
let mut edits: HashMap<Url, Vec<TextEdit>> = HashMap::new();
self.paths