mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 01:42:14 +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
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -2775,8 +2775,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reflexo"
|
||||
version = "0.5.0-rc2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "247ea8050cb5c88b41a68b3269f5a2eb7ebff55851a564d96b035643418346e6"
|
||||
source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=9cced415e29e5e341ad4bdcc32ab3e67ffad74db#9cced415e29e5e341ad4bdcc32ab3e67ffad74db"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"bitvec",
|
||||
|
@ -3765,6 +3764,7 @@ dependencies = [
|
|||
"sha2",
|
||||
"strum 0.26.2",
|
||||
"toml 0.8.12",
|
||||
"ttf-parser",
|
||||
"typst",
|
||||
"typst-ts-compiler",
|
||||
"typst-ts-core",
|
||||
|
@ -4262,8 +4262,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "typst-ts-compiler"
|
||||
version = "0.5.0-rc2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c18cf7d96c0c558901b3f7e3f5200ecb7e3d7d3dcc5a1222e94bc875237ff352"
|
||||
source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=9cced415e29e5e341ad4bdcc32ab3e67ffad74db#9cced415e29e5e341ad4bdcc32ab3e67ffad74db"
|
||||
dependencies = [
|
||||
"append-only-vec",
|
||||
"base64 0.22.0",
|
||||
|
@ -4301,8 +4300,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "typst-ts-core"
|
||||
version = "0.5.0-rc2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69135c380eb60efa4aeabd986d27d82ecd1b4c843fd3393992b449409317847"
|
||||
source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=9cced415e29e5e341ad4bdcc32ab3e67ffad74db#9cced415e29e5e341ad4bdcc32ab3e67ffad74db"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64-serde",
|
||||
|
@ -4339,8 +4337,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "typst-ts-svg-exporter"
|
||||
version = "0.5.0-rc2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6063f63c8e3ba3d4d7f4cb1a8fd96b096e8e713f24783278fea98dac0746966"
|
||||
source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=9cced415e29e5e341ad4bdcc32ab3e67ffad74db#9cced415e29e5e341ad4bdcc32ab3e67ffad74db"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"comemo 0.4.0",
|
||||
|
|
|
@ -126,6 +126,11 @@ typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tin
|
|||
# typst-render = { path = "../typst/crates/typst-render" }
|
||||
# typst-syntax = { path = "../typst/crates/typst-syntax" }
|
||||
|
||||
typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" }
|
||||
reflexo = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" }
|
||||
typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" }
|
||||
typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" }
|
||||
|
||||
# typst-ts-svg-exporter = { path = "../typst.ts/exporter/svg" }
|
||||
# reflexo = { path = "../typst.ts/crates/reflexo/" }
|
||||
# typst-ts-core = { path = "../typst.ts/core" }
|
||||
|
|
|
@ -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(..)
|
||||
|
|
|
@ -410,6 +410,11 @@
|
|||
"command": "tinymist.showTemplateGallery",
|
||||
"title": "Show available Typst templates (gallery) for picking up a template to initialize",
|
||||
"category": "Typst"
|
||||
},
|
||||
{
|
||||
"command": "tinymist.showSummary",
|
||||
"title": "Show current document summary",
|
||||
"category": "Typst"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
|
|
@ -38,7 +38,7 @@ export function getUserPackageData(context: vscode.ExtensionContext) {
|
|||
}
|
||||
|
||||
export async function activateEditorTool(context: vscode.ExtensionContext, tool: string) {
|
||||
if (tool !== "template-gallery" && tool !== "tracing") {
|
||||
if (tool !== "template-gallery" && tool !== "tracing" && tool !== "summary") {
|
||||
vscode.window.showErrorMessage(`Unknown editor tool: ${tool}`);
|
||||
return;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ export async function activateEditorTool(context: vscode.ExtensionContext, tool:
|
|||
const title = {
|
||||
"template-gallery": "Template Gallery",
|
||||
tracing: "Tracing",
|
||||
summary: "Summary",
|
||||
}[tool];
|
||||
|
||||
// Create and show a new WebView
|
||||
|
@ -62,6 +63,11 @@ export async function activateEditorTool(context: vscode.ExtensionContext, tool:
|
|||
panel.webview.onDidReceiveMessage(async (message) => {
|
||||
console.log("onDidReceiveMessage", message);
|
||||
switch (message.type) {
|
||||
case "revealPath": {
|
||||
const path = message.path;
|
||||
await vscode.commands.executeCommand("revealFileInOS", vscode.Uri.file(path));
|
||||
break;
|
||||
}
|
||||
case "savePackageData": {
|
||||
const data = message.data;
|
||||
context.globalState.update("userPackageData", {
|
||||
|
@ -113,6 +119,21 @@ export async function activateEditorTool(context: vscode.ExtensionContext, tool:
|
|||
break;
|
||||
case "tracing":
|
||||
break;
|
||||
case "summary":
|
||||
// tinymist.getCurrentDocumentMetrics
|
||||
const result = await vscode.commands.executeCommand(
|
||||
"tinymist.getCurrentDocumentMetrics"
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
vscode.window.showErrorMessage("No document metrics available");
|
||||
panel.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const docMetrics = JSON.stringify(result);
|
||||
html = html.replace(":[[preview:DocumentMetrics]]:", btoa(docMetrics));
|
||||
break;
|
||||
}
|
||||
|
||||
panel.webview.html = html;
|
||||
|
|
|
@ -121,6 +121,11 @@ async function startClient(context: ExtensionContext): Promise<void> {
|
|||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.exportCurrentPdf", () => commandExport("Pdf"))
|
||||
);
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.getCurrentDocumentMetrics", () =>
|
||||
commandGetCurrentDocumentMetrics()
|
||||
)
|
||||
);
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.pinMainToCurrent", () => commandPinMain(true))
|
||||
);
|
||||
|
@ -155,6 +160,9 @@ async function startClient(context: ExtensionContext): Promise<void> {
|
|||
commandShowTemplateGallery(context)
|
||||
)
|
||||
);
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.showSummary", () => commandShowSummary(context))
|
||||
);
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.traceCurrentFile", () => commandShowTrace(context))
|
||||
);
|
||||
|
@ -241,6 +249,24 @@ async function commandExport(mode: string, extraOpts?: any): Promise<string | un
|
|||
return res;
|
||||
}
|
||||
|
||||
async function commandGetCurrentDocumentMetrics(): Promise<any> {
|
||||
const activeEditor = window.activeTextEditor;
|
||||
if (activeEditor === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fsPath = activeEditor.document.uri.fsPath;
|
||||
|
||||
const res = await client?.sendRequest<string | null>("workspace/executeCommand", {
|
||||
command: `tinymist.getDocumentMetrics`,
|
||||
arguments: [fsPath],
|
||||
});
|
||||
if (res === null) {
|
||||
return undefined;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the functionality for the 'Show PDF' button shown in the editor title
|
||||
* if a `.typ` file is opened.
|
||||
|
@ -320,6 +346,10 @@ async function commandShowTemplateGallery(context: vscode.ExtensionContext): Pro
|
|||
await activateEditorTool(context, "template-gallery");
|
||||
}
|
||||
|
||||
async function commandShowSummary(context: vscode.ExtensionContext): Promise<void> {
|
||||
await activateEditorTool(context, "summary");
|
||||
}
|
||||
|
||||
async function commandShowTrace(context: vscode.ExtensionContext): Promise<void> {
|
||||
const activeEditor = window.activeTextEditor;
|
||||
if (activeEditor === undefined) {
|
||||
|
|
|
@ -375,7 +375,7 @@ fn e2e() {
|
|||
});
|
||||
|
||||
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:ce75b72cb95e2b46e0aa64da02b6ef50");
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:464aa731a6971904e9ff21d6ce3f9bf5");
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -386,7 +386,7 @@ fn e2e() {
|
|||
});
|
||||
|
||||
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:f2f40ce6a31dd0423a453e051c56a977");
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:118679388281974949c659d7287827e2");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
128
tools/editor-tools/src/features/diagnostics.ts
Normal file
128
tools/editor-tools/src/features/diagnostics.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
import van from "vanjs-core";
|
||||
const { div, a, br, span, code } = van.tags;
|
||||
|
||||
const DIAG_MOCK = {};
|
||||
|
||||
export const Diagnostics = () => {
|
||||
const diagnostics = van.state(DIAG_MOCK);
|
||||
void diagnostics;
|
||||
|
||||
return div(
|
||||
{
|
||||
class: "flex-col",
|
||||
style: "justify-content: center; align-items: center; gap: 10px;",
|
||||
},
|
||||
div(
|
||||
{ class: `tinymist-card`, style: "flex: 1; width: 100%; padding: 10px" },
|
||||
code(
|
||||
{ width: "100%" },
|
||||
`cannot add integer and alignment`,
|
||||
br(),
|
||||
a({ href: "javascript:void(0)" }, "test.typ(3, 19)"),
|
||||
`: error occurred in this call of function \`f\``,
|
||||
br(),
|
||||
a({ href: "javascript:void(0)" }, `test.typ(6, 2)`),
|
||||
`: error occurred in this call of function \`g\``
|
||||
)
|
||||
),
|
||||
div(
|
||||
{ class: `tinymist-card`, style: "flex: 1; width: 100%; padding: 10px" },
|
||||
"Expression explained.",
|
||||
div(
|
||||
{
|
||||
style: "margin: 0.8em 0",
|
||||
},
|
||||
"The expression is: ",
|
||||
code(a({ href: "javascript:void(0)" }, `x`)),
|
||||
code(` + `),
|
||||
code(a({ href: "javascript:void(0)" }, `y`)),
|
||||
", at location: test.typ(2, 16):",
|
||||
br(),
|
||||
code(
|
||||
{
|
||||
style: "margin: 0.5em",
|
||||
},
|
||||
`#let f(x, y) = `,
|
||||
span({ style: "text-decoration: underline" }, `x + y`),
|
||||
`;`
|
||||
),
|
||||
br(),
|
||||
"where ",
|
||||
code(a({ href: "javascript:void(0)" }, `x`)),
|
||||
" is the ",
|
||||
"1st",
|
||||
" function parameter of ",
|
||||
code(a({ href: "javascript:void(0)" }, `f`)),
|
||||
".",
|
||||
br(),
|
||||
"where ",
|
||||
code(a({ href: "javascript:void(0)" }, `y`)),
|
||||
" is the ",
|
||||
"2nd",
|
||||
" function parameter of ",
|
||||
code(a({ href: "javascript:void(0)" }, `f`)),
|
||||
"."
|
||||
),
|
||||
div(
|
||||
{
|
||||
style: "margin: 0.8em 0",
|
||||
},
|
||||
code(a({ href: "javascript:void(0)" }, `f`)),
|
||||
" is called with arguments ",
|
||||
code("f("),
|
||||
code(a({ href: "javascript:void(0)" }, `x`)),
|
||||
code(` = 1, `),
|
||||
code(a({ href: "javascript:void(0)" }, `y`)),
|
||||
code(" = left)"),
|
||||
", at location: test.typ(3, 19):",
|
||||
br(),
|
||||
code(
|
||||
{
|
||||
style: "margin: 0.5em",
|
||||
},
|
||||
`#let g(x, y, z) = `,
|
||||
span({ style: "text-decoration: underline" }, `f(x, y)`),
|
||||
` + z;`
|
||||
),
|
||||
br(),
|
||||
"where ",
|
||||
code(a({ href: "javascript:void(0)" }, `x`)),
|
||||
" is the ",
|
||||
"1st",
|
||||
" function parameter of ",
|
||||
code(a({ href: "javascript:void(0)" }, `g`)),
|
||||
".",
|
||||
br(),
|
||||
"where ",
|
||||
code(a({ href: "javascript:void(0)" }, `y`)),
|
||||
" is the ",
|
||||
"2nd",
|
||||
" function parameter of ",
|
||||
code(a({ href: "javascript:void(0)" }, `g`)),
|
||||
"."
|
||||
),
|
||||
div(
|
||||
{
|
||||
style: "margin: 0.8em 0",
|
||||
},
|
||||
code(a({ href: "javascript:void(0)" }, `g`)),
|
||||
" is called with arguments ",
|
||||
code("g("),
|
||||
code(`x`),
|
||||
code(` = 1, `),
|
||||
code(`y`),
|
||||
code(" = left, z = red)"),
|
||||
", at location: test.typ(6, 2):",
|
||||
br(),
|
||||
code(
|
||||
{
|
||||
style: "margin: 0.5em",
|
||||
},
|
||||
`#`,
|
||||
span({ style: "text-decoration: underline" }, `g(1, left, red)`),
|
||||
`;`
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
328
tools/editor-tools/src/features/summary.ts
Normal file
328
tools/editor-tools/src/features/summary.ts
Normal file
|
@ -0,0 +1,328 @@
|
|||
import van, { ChildDom } from "vanjs-core";
|
||||
import { requestRevealPath } from "../vscode";
|
||||
const { div, a, span, code, br } = van.tags;
|
||||
|
||||
interface CompileArgs {
|
||||
root: string;
|
||||
fontPaths: string[];
|
||||
inputs: Record<string, string>;
|
||||
}
|
||||
|
||||
export const Summary = () => {
|
||||
const compileArgs = van.state(ARGS_MOCK);
|
||||
const documentMetricsData = `:[[preview:DocumentMetrics]]:`;
|
||||
const docMetrics = van.state<DocumentMetrics>(
|
||||
documentMetricsData.startsWith(":")
|
||||
? DOC_MOCK
|
||||
: JSON.parse(atob(documentMetricsData))
|
||||
);
|
||||
console.log("docMetrics", docMetrics);
|
||||
|
||||
const FontSlot = (font: FontInfo) => {
|
||||
let fontName;
|
||||
if (typeof font.source === "number") {
|
||||
let w = docMetrics.val.spanInfo.sources[font.source];
|
||||
let title;
|
||||
if (w.kind === "fs") {
|
||||
title = w.path;
|
||||
fontName = a(
|
||||
{
|
||||
style:
|
||||
"font-size: 1.2em; text-decoration: underline; cursor: pointer;",
|
||||
title,
|
||||
onclick() {
|
||||
if (w.kind === "fs") {
|
||||
requestRevealPath(w.path);
|
||||
}
|
||||
},
|
||||
},
|
||||
font.name
|
||||
);
|
||||
} else {
|
||||
title = `Embedded: ${w.name}`;
|
||||
fontName = span(
|
||||
{
|
||||
style: "font-size: 1.2em",
|
||||
title,
|
||||
},
|
||||
font.name
|
||||
);
|
||||
}
|
||||
} else {
|
||||
fontName = a({ style: "font-size: 1.2em" }, font.name);
|
||||
}
|
||||
|
||||
return div(
|
||||
{ style: "margin: 1.2em; margin-left: 0.5em" },
|
||||
fontName,
|
||||
" has ",
|
||||
font.usesScale,
|
||||
" use(s).",
|
||||
br(),
|
||||
code("PostScriptName"),
|
||||
": ",
|
||||
code(font.postscriptName),
|
||||
br(),
|
||||
code("Family"),
|
||||
": ",
|
||||
code(font.family || "N/A"),
|
||||
br(),
|
||||
code("Family (Identified by Typst)"),
|
||||
": ",
|
||||
code(font.fixedFamily),
|
||||
br(),
|
||||
code("FullName"),
|
||||
": ",
|
||||
code(font.fullName || "N/A")
|
||||
);
|
||||
};
|
||||
|
||||
const ArgSlots = () => {
|
||||
const res: ChildDom[] = [];
|
||||
let val = compileArgs.val;
|
||||
if (val.root) {
|
||||
res.push(
|
||||
div(
|
||||
a({ href: "javascript:void(0)" }, code("root")),
|
||||
": ",
|
||||
code(val.root)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < val.fontPaths.length; i++) {
|
||||
res.push(
|
||||
div(
|
||||
a({ href: "javascript:void(0)" }, code(`font-path(${i})`)),
|
||||
": ",
|
||||
code(val.fontPaths[i])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (val.inputs) {
|
||||
const codeList: ChildDom[] = [];
|
||||
for (const key of Object.keys(val.inputs)) {
|
||||
codeList.push(
|
||||
span({ style: "color: #DEC76E" }, key),
|
||||
span({ style: "color: #7DCFFF" }, "="),
|
||||
val.inputs[key]
|
||||
);
|
||||
}
|
||||
|
||||
res.push(
|
||||
div(
|
||||
a({ href: "javascript:void(0)" }, code("sys.inputs")),
|
||||
": ",
|
||||
code(...codeList)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
return div(
|
||||
{
|
||||
class: "flex-col",
|
||||
style: "justify-content: center; align-items: center; gap: 10px;",
|
||||
},
|
||||
div(
|
||||
{ class: `tinymist-card`, style: "flex: 1; width: 100%; padding: 10px" },
|
||||
div(
|
||||
van.derive(() => `This document is compiled with following arguments.`)
|
||||
),
|
||||
div({ style: "margin: 1.2em; margin-left: 0.5em" }, ...ArgSlots())
|
||||
),
|
||||
div(
|
||||
{ class: `tinymist-card`, style: "flex: 1; width: 100%; padding: 10px" },
|
||||
div(
|
||||
van.derive(
|
||||
() => `This document uses ${docMetrics.val.fontInfo.length} fonts.`
|
||||
)
|
||||
),
|
||||
(_dom?: Element) =>
|
||||
div(
|
||||
...docMetrics.val.fontInfo
|
||||
.sort((x, y) => {
|
||||
if (x.usesScale === undefined || y.usesScale === undefined) {
|
||||
if (x.usesScale === undefined) {
|
||||
return 1;
|
||||
}
|
||||
if (y.usesScale === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return x.name.localeCompare(y.name);
|
||||
}
|
||||
if (x.usesScale !== y.usesScale) {
|
||||
return y.usesScale - x.usesScale;
|
||||
}
|
||||
return x.name.localeCompare(y.name);
|
||||
})
|
||||
.map(FontSlot)
|
||||
)
|
||||
),
|
||||
div(
|
||||
{
|
||||
class: `tinymist-card hidden`,
|
||||
style: "flex: 1; width: 100%; padding: 10px",
|
||||
},
|
||||
div(`The Tinymist service.`),
|
||||
div(
|
||||
{ style: "margin: 0.8em; margin-left: 0.5em" },
|
||||
div(
|
||||
`Its version is `,
|
||||
a({ href: "javascript:void(0)" }, "0.11.2"),
|
||||
`.`
|
||||
),
|
||||
div(`It is compiled with optimization level `, "3", `.`),
|
||||
div(
|
||||
`It is connecting to the client `,
|
||||
code({ style: "font-style: italic" }, "VSCode 1.87.2"),
|
||||
`.`
|
||||
)
|
||||
)
|
||||
),
|
||||
div(
|
||||
{
|
||||
class: `tinymist-card hidden`,
|
||||
style: "flex: 1; width: 100%; padding: 10px",
|
||||
},
|
||||
div(`The Typst compiler.`),
|
||||
div(
|
||||
{ style: "margin: 0.8em; margin-left: 0.5em" },
|
||||
div(
|
||||
`Its version is `,
|
||||
a({ href: "javascript:void(0)" }, "0.11.0"),
|
||||
`.`
|
||||
),
|
||||
div(
|
||||
`It identifies `,
|
||||
a({ href: "javascript:void(0)" }, "374"),
|
||||
` font variants.`
|
||||
)
|
||||
)
|
||||
),
|
||||
div(
|
||||
{
|
||||
class: `tinymist-card hidden`,
|
||||
style: "flex: 1; width: 100%; padding: 10px",
|
||||
},
|
||||
div(`The Typst formatters.`),
|
||||
div(
|
||||
{ style: "margin: 0.8em; margin-left: 0.5em" },
|
||||
div(`It uses typstyle with following configurations.`),
|
||||
code(
|
||||
{ style: "margin-left: 0.5em" },
|
||||
a({ href: "javascript:void(0)", style: "color: #DEC76E" }, "columns"),
|
||||
span({ style: "color: #7DCFFF" }, "="),
|
||||
"120"
|
||||
),
|
||||
div(
|
||||
`The version of typstyle is `,
|
||||
a({ href: "javascript:void(0)" }, "0.11.7"),
|
||||
`.`
|
||||
),
|
||||
div(
|
||||
`The version of typstfmt is `,
|
||||
a({ href: "javascript:void(0)" }, "0.2.9"),
|
||||
`.`
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
interface SpanInfo {
|
||||
sources: FontSource[];
|
||||
}
|
||||
|
||||
interface FsFontSource {
|
||||
kind: "fs";
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface MemoryFontSource {
|
||||
kind: "memory";
|
||||
name: string;
|
||||
}
|
||||
|
||||
type FontSource = FsFontSource | MemoryFontSource;
|
||||
|
||||
interface AnnotatedContent {
|
||||
content: string;
|
||||
spanKind: string;
|
||||
/// file >= 0, offset, mapped length
|
||||
/// file == -1, delta, mapped length
|
||||
/// file = -2, 0, skipped length
|
||||
spans: number[];
|
||||
}
|
||||
|
||||
interface FontInfo {
|
||||
name: string;
|
||||
postscriptName: string;
|
||||
family?: string;
|
||||
fullName?: string;
|
||||
fixedFamily?: string;
|
||||
source?: number;
|
||||
index?: number;
|
||||
usesScale?: number;
|
||||
uses?: AnnotatedContent;
|
||||
}
|
||||
|
||||
interface DocumentMetrics {
|
||||
spanInfo: SpanInfo;
|
||||
fontInfo: FontInfo[];
|
||||
}
|
||||
|
||||
const DOC_MOCK: DocumentMetrics = {
|
||||
spanInfo: {
|
||||
sources: [
|
||||
{
|
||||
kind: "fs",
|
||||
path: "C:\\Users\\OvO\\work\\assets\\fonts\\SongTi-Regular.ttf",
|
||||
},
|
||||
{
|
||||
kind: "fs",
|
||||
path: "C:\\Users\\OvO\\work\\assets\\fonts\\TimesNewRoman-Regular.ttf",
|
||||
},
|
||||
{
|
||||
kind: "fs",
|
||||
path: "C:\\Users\\OvO\\work\\assets\\fonts\\MicrosoftYaHei-Regular.ttf",
|
||||
},
|
||||
],
|
||||
},
|
||||
fontInfo: [
|
||||
{
|
||||
name: "Song Ti",
|
||||
postscriptName: "SongTi",
|
||||
source: 0,
|
||||
usesScale: 3,
|
||||
},
|
||||
{
|
||||
name: "Times New Roman",
|
||||
postscriptName: "TimesNewRoman",
|
||||
source: 1,
|
||||
usesScale: 4,
|
||||
},
|
||||
{
|
||||
name: "Microsoft YaHei",
|
||||
postscriptName: "MicrosoftYaHei",
|
||||
source: 2,
|
||||
usesScale: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const ARGS_MOCK: CompileArgs = {
|
||||
root: "C:\\Users\\OvO\\work\\rust\\tinymist",
|
||||
fontPaths: [
|
||||
"C:\\Users\\OvO\\work\\rust\\tinymist\\assets\\fonts",
|
||||
"C:\\Users\\OvO\\work\\assets\\fonts",
|
||||
],
|
||||
inputs: {
|
||||
theme: "dark",
|
||||
context: '{"preview":true}',
|
||||
},
|
||||
};
|
|
@ -4,13 +4,15 @@ import van from "vanjs-core";
|
|||
import { setupVscodeChannel } from "./vscode";
|
||||
import { TemplateGallery } from "./features/template-gallery";
|
||||
import { Tracing } from "./features/tracing";
|
||||
import { Summary } from "./features/summary";
|
||||
import { Diagnostics } from "./features/diagnostics";
|
||||
|
||||
// const isDarkMode = () =>
|
||||
// window.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
setupVscodeChannel();
|
||||
|
||||
type PageComponent = "template-gallery" | "tracing";
|
||||
type PageComponent = "template-gallery" | "tracing" | "summary" | "diagnostics";
|
||||
|
||||
interface Arguments {
|
||||
page: PageComponent;
|
||||
|
@ -25,7 +27,7 @@ function retrieveArgs(): Arguments {
|
|||
/// let frontend_html = frontend_html.replace(
|
||||
/// "editor-tools-args:{}", ...);
|
||||
/// ```
|
||||
let mode = `editor-tools-args:{"page": "tracing"}`;
|
||||
let mode = `editor-tools-args:{"page": "summary"}`;
|
||||
/// Remove the placeholder prefix.
|
||||
mode = mode.replace("editor-tools-args:", "");
|
||||
|
||||
|
@ -35,12 +37,19 @@ function retrieveArgs(): Arguments {
|
|||
|
||||
const args = retrieveArgs();
|
||||
|
||||
const appHook = document.querySelector("#tinymist-app")!;
|
||||
switch (args.page) {
|
||||
case "template-gallery":
|
||||
van.add(document.querySelector("#tinymist-app")!, TemplateGallery());
|
||||
van.add(appHook, TemplateGallery());
|
||||
break;
|
||||
case "tracing":
|
||||
van.add(document.querySelector("#tinymist-app")!, Tracing());
|
||||
van.add(appHook, Tracing());
|
||||
break;
|
||||
case "summary":
|
||||
van.add(appHook, Summary());
|
||||
break;
|
||||
case "diagnostics":
|
||||
van.add(appHook, Diagnostics());
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown page: ${args.page}`);
|
||||
|
|
|
@ -130,3 +130,7 @@ body.typst-preview-light .tinymist-button.warning.activated {
|
|||
.tinymist-icon path {
|
||||
fill: WindowText;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -41,3 +41,9 @@ export function requestInitTemplate(packageSpec: string) {
|
|||
vscodeAPI.postMessage({ type: "initTemplate", packageSpec });
|
||||
}
|
||||
}
|
||||
|
||||
export function requestRevealPath(path: string) {
|
||||
if (vscodeAPI?.postMessage) {
|
||||
vscodeAPI.postMessage({ type: "revealPath", path });
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue