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:
Myriad-Dreamin 2024-04-01 10:16:26 +08:00 committed by GitHub
parent 2e39afde78
commit 7e453872b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 773 additions and 17 deletions

13
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -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

View file

@ -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.

View 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)
}
}

View file

@ -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>),
}
}

View file

@ -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);

View file

@ -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

View file

@ -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(..)

View file

@ -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": {

View file

@ -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;

View file

@ -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) {

View file

@ -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");
}
}

View 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)`),
`;`
)
)
)
);
};

View 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}',
},
};

View file

@ -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}`);

View file

@ -130,3 +130,7 @@ body.typst-preview-light .tinymist-button.warning.activated {
.tinymist-icon path {
fill: WindowText;
}
.hidden {
display: none;
}

View file

@ -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 });
}
}