mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
dev: init summary page (#137)
* feat: add summary frontend * dev: init summary page * build: update cargo.lock * feat: init diagnostics frontend * build: update snapshot hash
This commit is contained in:
parent
2e39afde78
commit
7e453872b1
18 changed files with 773 additions and 17 deletions
|
@ -39,6 +39,7 @@ if_chain = "1"
|
|||
percent-encoding = "2"
|
||||
unscanny = "0.1"
|
||||
pathdiff = "0.2"
|
||||
ttf-parser = "0.20.0"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell.workspace = true
|
||||
|
|
|
@ -5,8 +5,9 @@ use std::{
|
|||
};
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use reflexo::{cow_mut::CowMut, ImmutPath};
|
||||
use reflexo::{cow_mut::CowMut, debug_loc::DataSource, ImmutPath};
|
||||
use typst::syntax::FileId as TypstFileId;
|
||||
use typst::text::Font;
|
||||
use typst::{
|
||||
diag::{eco_format, FileError, FileResult, PackageError},
|
||||
syntax::{package::PackageSpec, Source, VirtualPath},
|
||||
|
@ -78,6 +79,11 @@ pub trait AnaylsisResources {
|
|||
|
||||
/// Get all the files in the workspace.
|
||||
fn iter_dependencies(&self, f: &mut dyn FnMut(&ImmutPath, std::time::SystemTime));
|
||||
|
||||
/// Resolve extra font information.
|
||||
fn font_info(&self, _font: Font) -> Option<Arc<DataSource>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for analyzers.
|
||||
|
|
184
crates/tinymist-query/src/document_metrics.rs
Normal file
184
crates/tinymist-query/src/document_metrics.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use reflexo::debug_loc::DataSource;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::text::Font;
|
||||
use typst::{
|
||||
layout::{Frame, FrameItem},
|
||||
model::Document,
|
||||
text::TextItem,
|
||||
};
|
||||
|
||||
use crate::{AnalysisContext, StatefulRequest, VersionedDocument};
|
||||
|
||||
/// Span information for some content.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SpanInfo {
|
||||
/// The sources that are used in the span information.
|
||||
pub sources: Vec<DataSource>,
|
||||
}
|
||||
|
||||
/// Annotated content for a font.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AnnotatedContent {
|
||||
/// A string of the content for slicing.
|
||||
pub content: String,
|
||||
/// The kind of the span encoding.
|
||||
pub span_kind: String,
|
||||
/// Encoded spans.
|
||||
pub spans: Vec<i32>,
|
||||
}
|
||||
|
||||
/// Information about a font.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentFontInfo {
|
||||
/// The display name of the font, which is computed by this crate and
|
||||
/// unnecessary from any fields of the font file.
|
||||
pub name: String,
|
||||
/// The PostScript name of the font.
|
||||
pub postscript_name: Option<String>,
|
||||
/// The Family in font file.
|
||||
pub family: Option<String>,
|
||||
/// The Full Name in font file.
|
||||
pub full_name: Option<String>,
|
||||
/// The Fixed Family used by Typst.
|
||||
pub fixed_family: Option<String>,
|
||||
/// The source of the font.
|
||||
pub source: Option<u32>,
|
||||
/// The index of the font in the source.
|
||||
pub index: Option<u32>,
|
||||
/// The annotated content length of the font.
|
||||
/// If it is None, the uses is not calculated.
|
||||
/// Otherwise, it is the length of the uses.
|
||||
pub uses_scale: Option<u32>,
|
||||
/// The annotated content of the font.
|
||||
/// If it is not None, the uses_scale must be provided.
|
||||
pub uses: Option<AnnotatedContent>,
|
||||
}
|
||||
|
||||
/// The response to a DocumentMetricsRequest.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentMetricsResponse {
|
||||
/// File span information.
|
||||
pub span_info: SpanInfo,
|
||||
/// Font information.
|
||||
pub font_info: Vec<DocumentFontInfo>,
|
||||
}
|
||||
|
||||
/// A request to compute DocumentMetrics for a document.
|
||||
///
|
||||
/// This is not part of the LSP protocol.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentMetricsRequest {
|
||||
/// The path of the document to compute DocumentMetricss.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl StatefulRequest for DocumentMetricsRequest {
|
||||
type Response = DocumentMetricsResponse;
|
||||
|
||||
fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
doc: Option<VersionedDocument>,
|
||||
) -> Option<Self::Response> {
|
||||
let doc = doc?;
|
||||
let doc = doc.document;
|
||||
|
||||
let mut worker = DocumentMetricsWorker {
|
||||
ctx,
|
||||
span_info: Default::default(),
|
||||
span_info2: Default::default(),
|
||||
font_info: Default::default(),
|
||||
};
|
||||
|
||||
worker.work(&doc)?;
|
||||
|
||||
let font_info = worker.compute()?;
|
||||
let span_info = SpanInfo {
|
||||
sources: worker.span_info2,
|
||||
};
|
||||
Some(DocumentMetricsResponse {
|
||||
span_info,
|
||||
font_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentMetricsWorker<'a, 'w> {
|
||||
ctx: &'a mut AnalysisContext<'w>,
|
||||
span_info: HashMap<Arc<DataSource>, u32>,
|
||||
span_info2: Vec<DataSource>,
|
||||
font_info: HashMap<Font, u32>,
|
||||
}
|
||||
|
||||
impl<'a, 'w> DocumentMetricsWorker<'a, 'w> {
|
||||
fn work(&mut self, doc: &Document) -> Option<()> {
|
||||
for page in &doc.pages {
|
||||
self.work_frame(&page.frame)?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn work_frame(&mut self, frame: &Frame) -> Option<()> {
|
||||
for (_, elem) in frame.items() {
|
||||
self.work_elem(elem)?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn work_elem(&mut self, elem: &FrameItem) -> Option<()> {
|
||||
match elem {
|
||||
FrameItem::Text(text) => self.work_text(text),
|
||||
FrameItem::Group(frame) => self.work_frame(&frame.frame),
|
||||
FrameItem::Shape(..) | FrameItem::Image(..) | FrameItem::Meta(..) => Some(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn work_text(&mut self, text: &TextItem) -> Option<()> {
|
||||
let use_cnt = self.font_info.entry(text.font.clone()).or_default();
|
||||
*use_cnt = use_cnt.checked_add(text.glyphs.len() as u32)?;
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn internal_source(&mut self, source: Arc<DataSource>) -> u32 {
|
||||
if let Some(&id) = self.span_info.get(source.as_ref()) {
|
||||
return id;
|
||||
}
|
||||
let id = self.span_info2.len() as u32;
|
||||
self.span_info2.push(source.as_ref().clone());
|
||||
self.span_info.insert(source, id);
|
||||
id
|
||||
}
|
||||
|
||||
fn compute(&mut self) -> Option<Vec<DocumentFontInfo>> {
|
||||
use ttf_parser::name_id::*;
|
||||
let font_info = std::mem::take(&mut self.font_info)
|
||||
.into_iter()
|
||||
.map(|(font, uses)| {
|
||||
let extra = self.ctx.resources.font_info(font.clone());
|
||||
DocumentFontInfo {
|
||||
name: format!("{} ({:?})", font.info().family, font.info().variant),
|
||||
postscript_name: font.find_name(POST_SCRIPT_NAME),
|
||||
full_name: font.find_name(FULL_NAME),
|
||||
family: font.find_name(FAMILY),
|
||||
fixed_family: Some(font.info().family.clone()),
|
||||
source: extra.map(|e| self.internal_source(e)),
|
||||
index: Some(font.index()),
|
||||
uses_scale: Some(uses),
|
||||
uses: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(font_info)
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ pub(crate) mod completion;
|
|||
pub use completion::*;
|
||||
pub(crate) mod document_symbol;
|
||||
pub use document_symbol::*;
|
||||
pub(crate) mod document_metrics;
|
||||
pub use document_metrics::*;
|
||||
pub(crate) mod folding_range;
|
||||
pub use folding_range::*;
|
||||
pub(crate) mod goto_declaration;
|
||||
|
@ -186,6 +188,8 @@ mod polymorphic {
|
|||
Formatting(FormattingRequest),
|
||||
FoldingRange(FoldingRangeRequest),
|
||||
SelectionRange(SelectionRangeRequest),
|
||||
|
||||
DocumentMetrics(DocumentMetricsRequest),
|
||||
}
|
||||
|
||||
impl CompilerQueryRequest {
|
||||
|
@ -211,6 +215,8 @@ mod polymorphic {
|
|||
CompilerQueryRequest::Formatting(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::FoldingRange(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::SelectionRange(..) => ContextFreeUnique,
|
||||
|
||||
CompilerQueryRequest::DocumentMetrics(..) => PinnedFirst,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,6 +241,8 @@ mod polymorphic {
|
|||
CompilerQueryRequest::Formatting(req) => &req.path,
|
||||
CompilerQueryRequest::FoldingRange(req) => &req.path,
|
||||
CompilerQueryRequest::SelectionRange(req) => &req.path,
|
||||
|
||||
CompilerQueryRequest::DocumentMetrics(req) => &req.path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -260,6 +268,8 @@ mod polymorphic {
|
|||
Formatting(Option<Vec<TextEdit>>),
|
||||
FoldingRange(Option<Vec<FoldingRange>>),
|
||||
SelectionRange(Option<Vec<SelectionRange>>),
|
||||
|
||||
DocumentMetrics(Option<DocumentMetricsResponse>),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,8 @@ use typst_ts_compiler::{
|
|||
Time,
|
||||
};
|
||||
use typst_ts_core::{
|
||||
config::compiler::EntryState, error::prelude::*, typst::prelude::EcoVec, Error, ImmutPath,
|
||||
config::compiler::EntryState, debug_loc::DataSource, error::prelude::*, typst::prelude::EcoVec,
|
||||
Error, ImmutPath, TypstFont,
|
||||
};
|
||||
|
||||
use super::typ_server::CompileClient as TsCompileClient;
|
||||
|
@ -219,6 +220,11 @@ impl CompileDriver {
|
|||
use typst_ts_compiler::NotifyApi;
|
||||
self.0.iter_dependencies(f)
|
||||
}
|
||||
|
||||
/// Resolve extra font information.
|
||||
fn font_info(&self, font: TypstFont) -> Option<Arc<DataSource>> {
|
||||
self.0.font_resolver.inner.describe_font(&font)
|
||||
}
|
||||
}
|
||||
|
||||
let w = WrapWorld(w);
|
||||
|
|
|
@ -597,6 +597,7 @@ impl TypstLanguageServer {
|
|||
redirected_command!("tinymist.focusMain", Self::focus_document),
|
||||
redirected_command!("tinymist.doInitTemplate", Self::init_template),
|
||||
redirected_command!("tinymist.doGetTemplateEntry", Self::do_get_template_entry),
|
||||
redirected_command!("tinymist.getDocumentMetrics", Self::get_document_metrics),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -643,6 +644,18 @@ impl TypstLanguageServer {
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
/// Export the current document as some format. The client is responsible
|
||||
/// for passing the correct absolute path of typst document.
|
||||
pub fn get_document_metrics(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = parse_path(arguments.first())?.as_ref().to_owned();
|
||||
|
||||
let res = run_query!(self.DocumentMetrics(path))?;
|
||||
let res = serde_json::to_value(res)
|
||||
.map_err(|e| internal_error(format!("Cannot serialize response {e}")))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Clear all cached resources.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
|
@ -266,6 +266,9 @@ impl TypstLanguageServer {
|
|||
Rename(req) => query_world!(client, Rename, req),
|
||||
PrepareRename(req) => query_world!(client, PrepareRename, req),
|
||||
Symbol(req) => query_world!(client, Symbol, req),
|
||||
|
||||
DocumentMetrics(req) => query_state!(client, DocumentMetrics, req),
|
||||
|
||||
FoldingRange(..)
|
||||
| SelectionRange(..)
|
||||
| SemanticTokensDelta(..)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue