feat: add c alias for compile command (#2261)
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled

Adds `c` as an alias for the `compile` subcommand to match `typst-cli`
behavior where `c = compile`.

## Changes

- Added `#[clap(alias = "c")]` attribute to the `Compile` command
variant
- Added test coverage for `tinymist c --help` and actual compilation via
alias

## Usage

```bash
# Both commands now work identically
tinymist compile input.typ output.pdf
tinymist c input.typ output.pdf
```

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Support command alias `c` in tinymist-cli</issue_title>
> <issue_description>### Motivation
> 
> As `typst-cli` has a alias `c = compile`, `tinymist compile` receives
exactly same arguments as `typst compile`, it will be suprise if the
alias itself is not supported.
> 
> May not also applies to other subcommands.
> 
> If this is feasible, I'd be willing to write a PR. This should be
easy.
> 
> ### Description
> 
> _No response_
> 
> ### More Examples/Questions
> 
> _No response_</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>

- Fixes Myriad-Dreamin/tinymist#2259

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/Myriad-Dreamin/tinymist/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com>
This commit is contained in:
Copilot 2025-11-20 03:29:31 +08:00 committed by GitHub
parent 391d40ce42
commit 0714d19e29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 207 additions and 36 deletions

View file

@ -89,6 +89,7 @@ enum Commands {
#[cfg(feature = "preview")]
Preview(tinymist::tool::preview::PreviewCliArgs),
/// Run compile command like `typst-cli compile`
#[clap(alias = "c")]
Compile(CompileArgs),
/// Generate completion script to stdout

View file

@ -363,10 +363,8 @@ export async function openPreviewInWebView({
html = html.replace("preview-arg:state:", `preview-arg:state:${previewStateEncoded}`);
// Forwards the localhost port to the external URL. Since WebSocket runs over HTTP, it should be fine.
// https://code.visualstudio.com/api/advanced-topics/remote-extensions#forwarding-localhost
let wsURI = await vscode.env.asExternalUri(
vscode.Uri.parse(`http://127.0.0.1:${dataPlanePort}`),
);
let wsURIString = wsURI.toString().replace(/^http/, "ws")
let wsURI = await vscode.env.asExternalUri(vscode.Uri.parse(`http://127.0.0.1:${dataPlanePort}`));
let wsURIString = wsURI.toString().replace(/^http/, "ws");
html = html.replace("ws://127.0.0.1:23625", wsURIString);
// Sets the HTML content to the webview panel.

View file

@ -117,7 +117,7 @@ export class Context {
});
assert.ok(
vscode.workspace.workspaceFolders?.length === 1 &&
vscode.workspace.workspaceFolders[0].uri.toString() == resolved.toString(),
vscode.workspace.workspaceFolders[0].uri.toString() == resolved.toString(),
// eslint-disable-next-line @typescript-eslint/no-base-to-string
`Expected workspace folder to be ${resolved.toString()}, got ${vscode.workspace.workspaceFolders}`,
);

View file

@ -118,7 +118,7 @@ export async function loadHTMLFile(context: vscode.ExtensionContext, relativePat
export class DisposeList {
disposes: (() => void)[] = [];
disposed = false;
constructor() { }
constructor() {}
add(d: (() => void) | vscode.Disposable) {
if (this.disposed) {
// console.error("disposed", this.taskId, "for", this.filePath);

View file

@ -93,6 +93,141 @@ fn test_help_lsp() {
");
}
#[test]
fn test_help_compile_alias() {
apply_common_filters!();
insta_cmd::assert_cmd_snapshot!(cli().arg("c").arg("--help"), @r"
success: true
exit_code: 0
----- stdout -----
Run compile command like `typst-cli compile`
Usage: tinymist compile [OPTIONS] <INPUT> [OUTPUT]
Arguments:
<INPUT>
Specify the path to input Typst file
[OUTPUT]
Provide the path to output file (PDF, PNG, SVG, or HTML). Use `-` to write output to
stdout.
For output formats emitting one file per page (PNG & SVG), a page number template must be
present if the source document renders to multiple pages. Use `{p}` for page numbers,
`{0p}` for zero padded page numbers and `{t}` for page count. For example,
`page-{0p}-of-{t}.png` creates `page-01-of-10.png`, `page-02-of-10.png`, and so on.
Options:
--name <NAME>
Give a task name to the document
--root <DIR>
Configure the project root (for absolute paths). If the path is relative, it will be
resolved relative to the current working directory (PWD)
[env: TYPST_ROOT=REDACTED]
--font-path <DIR>
Add additional directories that are recursively searched for fonts.
If multiple paths are specified, they are separated by the system's path separator (`:` on
Unix-like systems and `;` on Windows).
[env: TYPST_FONT_PATHS=REDACTED]
--ignore-system-fonts
Ensure system fonts won't be searched, unless explicitly included via `--font-path`
--package-path <DIR>
Specify a custom path to local packages, defaults to system-dependent location
[env: TYPST_PACKAGE_PATH=REDACTED]
--package-cache-path <DIR>
Specify a custom path to package cache, defaults to system-dependent location
[env: TYPST_PACKAGE_CACHE_PATH=REDACTED]
--when <WHEN>
Configure when to run the task
Possible values:
- never: Never watch to run task
- onSave: Run task on saving the document, i.e. on `textDocument/didSave`
events
- onType: Run task on typing, i.e. on `textDocument/didChange` events
- onDocumentHasTitle: *DEPRECATED* Run task when a document has a title and on saved,
which is useful to filter out template files
- script: Checks by running a typst script
-f, --format <FORMAT>
Specify the format of the output file, inferred from the extension by default
Possible values:
- pdf: Export to PDF
- png: Export to PNG
- svg: Export to SVG
- html: Export to HTML
--pages <PAGES>
Specify which pages to export. When unspecified, all pages are exported.
Pages to export are separated by commas, and can be either simple page numbers (e.g. '2,5'
to export only pages 2 and 5) or page ranges (e.g. '2,3-6,8-' to export page 2, pages 3 to
6 (inclusive), page 8 and any pages after it).
Page numbers are one-indexed and correspond to physical page numbers in the document
(therefore not being affected by the document's page counter).
--pdf-standard <STANDARD>
Specify the PDF standards that Typst will enforce conformance with.
If multiple standards are specified, they are separated by commas.
Possible values:
- 1.4: PDF 1.4
- 1.5: PDF 1.5
- 1.6: PDF 1.6
- 1.7: PDF 1.7
- 2.0: PDF 2.0
- a-1b: PDF/A-1b
- a-1a: PDF/A-1a
- a-2b: PDF/A-2b
- a-2u: PDF/A-2u
- a-2a: PDF/A-2a
- a-3b: PDF/A-3b
- a-3u: PDF/A-3u
- a-3a: PDF/A-3a
- a-4: PDF/A-4
- a-4f: PDF/A-4f
- a-4e: PDF/A-4e
- ua-1: PDF/UA-1
--no-pdf-tags
By default, even when not producing a `PDF/UA-1` document, a tagged PDF document is
written to provide a baseline of accessibility. In some circumstances (for example when
trying to reduce the size of a document) it can be desirable to disable tagged PDF
--ppi <PPI>
Specify the PPI (pixels per inch) to use for PNG export
[default: 144]
--save-lock
Save the compilation arguments to the lock file. If `--lockfile` is not set, the lock file
will be saved in the cwd
--lockfile <LOCKFILE>
Specify the path to the lock file. If the path is set, the lockfile will be saved
(--save-lock)
-h, --help
Print help (see a summary with '-h')
----- stderr -----
");
}
#[test]
fn test_help_compile() {
apply_common_filters!();
@ -448,3 +583,37 @@ fn test_compile() {
})
.expect("test should succeed");
}
#[test]
fn test_compile_alias() {
const INPUT_REL: &str = "tests/workspaces/individuals/tiny.typ";
std::env::set_var("RUST_BACKTRACE", "full");
let cwd = GIT_ROOT.clone();
let root = cwd.join("target/e2e/tinymist-cli");
std::env::set_current_dir(&cwd).expect("should change current directory");
tinymist_std::fs::paths::temp_dir_in(root, |tmp| {
let abs_out = tmp.clean();
let rel_out = abs_out.strip_prefix(&cwd).expect("path should be stripped");
assert!(cwd.is_absolute(), "cwd should be absolute {cwd:?}");
assert!(abs_out.is_absolute(), "abs_out should be absolute {abs_out:?}");
assert!(rel_out.is_relative(), "rel_out should be relative {rel_out:?}");
// Test the 'c' alias with relative INPUT and OUTPUT
insta_cmd::assert_cmd_snapshot!(cli().arg("c").arg(INPUT_REL).arg(rel_out.join("test_alias.pdf")), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
let output = rel_out.join("test_alias.pdf");
assert!(output.exists(), "output file should exist: {output:?}");
Ok(())
})
.expect("test should succeed");
}

View file

@ -114,9 +114,7 @@ const TemplateList = (packages: State<PackageMeta[]>, catState: FilterState) =>
button(
{
class: van.derive(() => {
const activatingCls = catState.getIsFavorite("preview", item.name)
? " active"
: "";
const activatingCls = catState.getIsFavorite("preview", item.name) ? " active" : "";
return "toggle-btn tinymist-template-action" + activatingCls;
}),
title: van.derive(() =>

View file

@ -1,5 +1,11 @@
import { triggerRipple } from "./typst-animation.mjs";
import { PreviewMode, TypstDomHookedElement, TypstDomWindowElement, type GConstructor, type TypstDocumentContext } from "./typst-doc.mjs";
import {
PreviewMode,
TypstDomHookedElement,
TypstDomWindowElement,
type GConstructor,
type TypstDocumentContext,
} from "./typst-doc.mjs";
const enum SourceMappingType {
Text = 0,
@ -107,7 +113,8 @@ export function resolveSourceLeaf(
export function installEditorJumpToHandler(
windowElem: TypstDomWindowElement,
docRoot: TypstDomHookedElement) {
docRoot: TypstDomHookedElement,
) {
const getNthBackgroundRect = (elem: Element, pageNumber: string) => {
let curElem: Element | null = elem;
while (curElem) {
@ -210,7 +217,7 @@ export function installEditorJumpToHandler(
docRoot.addEventListener("click", sourceMappingHandler);
}
export interface TypstDebugJumpDocument { }
export interface TypstDebugJumpDocument {}
export function provideDebugJumpDoc<TBase extends GConstructor<TypstDocumentContext>>(
Base: TBase,
@ -228,12 +235,7 @@ export function provideDebugJumpDoc<TBase extends GConstructor<TypstDocumentCont
}
}
scrollTo(
pageWidth: number,
pageNo: number,
innerLeft: number,
innerTop: number,
) {
scrollTo(pageWidth: number, pageNo: number, innerLeft: number, innerTop: number) {
const scrollElem = this.hookedElem.parentElement!;
if (this.previewMode === PreviewMode.Slide) {
@ -270,8 +272,7 @@ export function provideDebugJumpDoc<TBase extends GConstructor<TypstDocumentCont
// for double-column layout
// console.log('occupied adjustment', widthOccupied, page);
const xOffsetAdjsut =
xOffset > pageAdjust ? pageAdjust : pageAdjustLeft;
const xOffsetAdjsut = xOffset > pageAdjust ? pageAdjust : pageAdjustLeft;
scrollElem.scrollTo({
behavior: "smooth",
@ -320,4 +321,3 @@ export function provideDebugJumpDoc<TBase extends GConstructor<TypstDocumentCont
}
};
}

View file

@ -636,8 +636,7 @@ export function provideSvgDoc<
const computedRevScale = containerWidth ? this.docWidth / containerWidth : 1;
// respect current scale ratio
const revScale = computedRevScale / this.currentScaleRatio;
const left =
(this.windowElem.offsetLeft - containerBRect.left) * revScale;
const left = (this.windowElem.offsetLeft - containerBRect.left) * revScale;
const top = (this.windowElem.offsetTop - containerBRect.top) * revScale;
const width = this.windowElem.clientWidth * revScale;
const height = this.windowElem.clientHeight * revScale;

View file

@ -43,7 +43,7 @@ class GenElem {
public tag: string,
public container: HTMLElement,
public additions?: Record<string, any>,
) { }
) {}
push(child: GenNode) {
this.children.push(child);
@ -88,7 +88,10 @@ class GenContext {
allElemList: GenElem[] = [];
windowElem: TypstDomWindowElement;
constructor(public pages: CanvasPage[], windowElem: TypstDomWindowElement) {
constructor(
public pages: CanvasPage[],
windowElem: TypstDomWindowElement,
) {
this.insertionPoint = new GenElem("outline", document.createElement("div"));
this.parent = this.insertionPoint;
this.windowElem = windowElem;
@ -174,7 +177,7 @@ export function patchOutlineEntry(
prev: HTMLDivElement,
pages: CanvasPage[],
items: OutlineItemData[],
windowElem: TypstDomWindowElement
windowElem: TypstDomWindowElement,
) {
const ctx = new GenContext(pages, windowElem);
// the root element of the generated outline

View file

@ -57,7 +57,7 @@ function retrieveWsArgs() {
/// `buildWs` returns a object, which keeps track of websocket
/// connections.
function buildWs() {
let previousDispose = Promise.resolve(() => { });
let previousDispose = Promise.resolve(() => {});
/// `nextWs` will always hold a global unique websocket connection
/// to the preview backend.
function nextWs(nextWsArgs) {

View file

@ -38,11 +38,11 @@ var overLapping = function (a: Element, b: Element) {
) &&
/// determine overlapping by area
(Math.abs(aRect.left - bRect.left) + Math.abs(aRect.right - bRect.right)) /
Math.max(aRect.width, bRect.width) <
0.5 &&
Math.max(aRect.width, bRect.width) <
0.5 &&
(Math.abs(aRect.bottom - bRect.bottom) + Math.abs(aRect.top - bRect.top)) /
Math.max(aRect.height, bRect.height) <
0.5
Math.max(aRect.height, bRect.height) <
0.5
);
};

View file

@ -1,5 +1,9 @@
import { PreviewMode } from "typst-dom/typst-doc.mjs";
import { TypstPreviewDocument as TypstDocument, TypstDomHookedElement, TypstDomWindowElement } from "typst-dom/index.preview.mjs";
import {
TypstPreviewDocument as TypstDocument,
TypstDomHookedElement,
TypstDomWindowElement,
} from "typst-dom/index.preview.mjs";
import {
rendererBuildInfo,
createTypstRenderer,
@ -33,7 +37,7 @@ export async function wsMain({ url, previewMode, isContentPreview }: WsArgs) {
if (hookedElem) {
hookedElem.innerHTML = "";
}
return () => { };
return () => {};
}
const windowElem = document.getElementById("typst-container")! as TypstDomWindowElement;
@ -89,7 +93,6 @@ export async function wsMain({ url, previewMode, isContentPreview }: WsArgs) {
}),
);
const focusInput = () => {
const inpPageSelector = document.getElementById("typst-page-selector") as
| HTMLSelectElement
@ -183,9 +186,9 @@ export async function wsMain({ url, previewMode, isContentPreview }: WsArgs) {
// https://stackoverflow.com/questions/3464876/javascript-get-window-x-y-position-for-scroll
let top = () => {
let doc = document.documentElement;
return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
};
const scrollDelta = 50
const scrollDelta = 50;
switch (e.key) {
case "ArrowLeft":